demand

Use Audio Queue to play Audio stream data in real time. Here, a CAF file containing PCM data is used as an example.


Realize the principle of

With the help of a data transfer queue, the audio data regardless of the task data source is put into the queue, and then after the Audio queue is enabled, the audio data is recycled from the queue for playback.


Read the premise

  • Fundamentals of Core Audio: Simple books, nuggets, blogs
  • Audio Queue concept: Short books, nuggets, blogs
  • Audio Session Basics: Simple books, Nuggets, blogs
  • Transmission of audio data queue implementation
  • Basic knowledge of audio and video
  • Basic knowledge of C and C++

Code Address:Audio Queue Player

Address of nuggets:Audio Queue Player

Letter Address:Audio Queue Player

Blog Address:Audio Queue Player


The overall architecture

This example uses queues to transfer audio data. We’re using queues because audio Queue is data-driven to support playback, so data callbacks can only be called continuously, and if we don’t use queues, we can only use audio Queue class from the callback function to take data from the audio files, and assuming that other data source, after the audio playback module code coupling is higher and higher, and here are the benefits of using queue outside whether audio files or audio stream is good, just need to put into the queue open audio module we will from the audio queue after the callback function to retrieve the queue Regardless of the source of the data.

Simple and easy process

  • AudioStreamBasicDescription: Configures the format of incoming audio data
  • AudioQueueNewOutput: Creates an Audio queue
  • AudioQueueAddPropertyListener: Listens to whether the audio queue is working
  • AudioQueueSetParameter: Set the volume
  • AudioQueueAllocateBuffer: Allocates memory to the Audio queue buffer
  • Insert data from the queue into the Audio queue bufferAudioQueueEnqueueBuffer
  • AudioQueueStart: Enable the Audio Queue
  • Enter the play callback function, which retrieves the audio data stored in the queue
  • Join the queue again to play the audio data, after playing will automatically trigger the callback function, cycle in turn

File structure

Quick to use

  • Configure the ASBD of the audio data source

The following is the format in this example. Other files need to be configured according to the file format

    // This is only for the testPCM.caf file.
    AudioStreamBasicDescription audioFormat = {
        .mSampleRate         = 44100,
        .mFormatID           = kAudioFormatLinearPCM,
        .mChannelsPerFrame   = 1,
        .mFormatFlags        = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
        .mBitsPerChannel     = 16,
        .mBytesPerPacket     = 2,
        .mBytesPerFrame      = 2,
        .mFramesPerPacket    = 1,
    };
Copy the code
  • Configure the Audio Queue Player
    // Configure Audio Queue Player
    [[XDXAudioQueuePlayer getInstance] configureAudioPlayerWithAudioFormat:&audioFormat bufferSize:kXDXReadAudioPacketsNum * audioFormat.mBytesPerPacket];
Copy the code
  • Configure the audio file module
    // Configure Audio File
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"testPCM" ofType:@"caf"];
    XDXAudioFileHandler *fileHandler = [XDXAudioFileHandler getInstance];
    [fileHandler configurePlayFilePath:filePath];
Copy the code
  • Start playing

Before starting the audio queue, read the audio data from the file and put it into the queue. Here we cache 5 frames of audio data in the queue and then start the Audio Queue player. I won’t go into too much detail here about audio file reading and queuing. Please refer to the above prerequisites for help.

    // Put audio data from audio file into audio data queue
    [self putAudioDataIntoDataQueue];
    
    // First put 5 frame audio data to work queue then start audio queue to readIt to play. [NSTimer scheduledTimerWithTimeInterval: 0.01 repeats: YES block: ^ (NSTimer * _Nonnull timer) { dispatch_async(dispatch_get_main_queue(), ^{ XDXCustomQueueProcess *audioBufferQueue = [XDXAudioQueuePlayer getInstance]->_audioBufferQueue; int size = audioBufferQueue->GetQueueSize(audioBufferQueue->m_work_queue);if(size > 5) { [[XDXAudioQueuePlayer getInstance] startAudioPlayer]; [timer invalidate]; }}); }];Copy the code

The specific implementation

1. Define a structure to store audio related data

#define kXDXAudioPCMFramesPerPacket 1
#define kXDXAudioPCMBitsPerChannel 16

static const int kNumberBuffers = 3;

struct XDXAudioInfo {
    AudioStreamBasicDescription  mDataFormat;
    AudioQueueRef                mQueue;
    AudioQueueBufferRef          mBuffers[kNumberBuffers];
    int                          mbufferSize;
};
typedef struct XDXAudioInfo *XDXAudioInfoRef;

static XDXAudioInfoRef m_audioInfo;

+ (void)initialize {
    int size = sizeof(XDXAudioInfo);
    m_audioInfo = (XDXAudioInfoRef)malloc(size);
}

Copy the code

2. The initialization

Initialize the audio queue in the initialization method, because this example uses another class for transmission, so this is the instance object for use.

- (instancetype)init {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instace                  = [super init];
        self->_isInitFinish       = NO;
        self->_audioBufferQueue   = new XDXCustomQueueProcess();
    });
    return _instace;
}
Copy the code

3. Configure the audio player

  • Copies the incoming audio format, the size of the audio Buffer, into the instance
- (void)configureAudioPlayerWithAudioFormat:(AudioStreamBasicDescription *)audioFormat bufferSize:(int)bufferSize {
    memcpy(&m_audioInfo->mDataFormat, audioFormat, sizeof(XDXAudioInfo));
    m_audioInfo->mbufferSize = bufferSize;    
    BOOL isSuccess = [self configureAudioPlayerWithAudioInfo:m_audioInfo
                                                playCallback:PlayAudioDataCallback
                                            listenerCallback:AudioQueuePlayerPropertyListenerProc];
    
    self.isInitFinish = isSuccess;
}

Copy the code
  • Create an instance of the Audio Queue object

An audio Queue object is created using the incoming video data format ASBD and the name of the callback function. This class is passed in as an instance so that the callback function can communicate with it.

Note: Because the callback function is in the form of a C function, you cannot call instance methods of the class directly.

    // Create audio queue
    OSStatus status = AudioQueueNewOutput(&audioInfo->mDataFormat,
                                         playCallback,
                                         (__bridge void *)(self),
                                         CFRunLoopGetCurrent(),
                                         kCFRunLoopCommonModes,
                                         0,
                                         &audioInfo->mQueue);
    
    if(status ! = noErr) { NSLog(@"Audio Player: audio queue new output failed status:%d \n",(int)status);
        return NO;
    }
Copy the code
  • Listen for changes in the working status of the Audio Queue instance, such as playing or stopping playing.
// Listen the queue is whether working AudioQueueAddPropertyListener (audioInfo->mQueue, kAudioQueueProperty_IsRunning, listenerCallback, (__bridge void *)(self)); . static void AudioQueuePlayerPropertyListenerProc (void *inUserData,
                                                   AudioQueueRef           inAQ,
                                                   AudioQueuePropertyID    inID) {
    XDXAudioQueuePlayer * instance = (__bridge XDXAudioQueuePlayer *)inUserData;
    UInt32 isRunning = 0;
    UInt32 size = sizeof(isRunning);
    
    if(instance == NULL)
        return ;
    
    OSStatus err = AudioQueueGetProperty (inAQ, kAudioQueueProperty_IsRunning, &isRunning, &size);
    if (err) {
        instance->_isRunning = NO;
    }else {
        instance->_isRunning = isRunning;
    }
    
    NSLog(@"The audio queue work state: %d",instance->_isRunning);
}
Copy the code
  • Verify the set ASBD audio format and set volume
    // Get audio ASBD
    UInt32 size = sizeof(audioInfo->mDataFormat);
    status = AudioQueueGetProperty(audioInfo->mQueue,
                                   kAudioQueueProperty_StreamDescription,
                                   &audioInfo->mDataFormat,
                                   &size);
    if(status ! = noErr) { NSLog(@"Audio Player: get ASBD status:%d",(int)status);
        returnNO; // Set volume status = AudioQueueSetParameter(audioInfo->mQueue, kAudioQueueParam_Volume, 1.0);if(status ! = noErr) { NSLog(@"Audio Player: set volume failed:%d",(int)status);
        return NO;
    }
Copy the code
  • Allocate memory to the audio queue buffer
    // Allocate buffer for audio queue buffer
    for(int i = 0; i ! = kNumberBuffers; i++) { status = AudioQueueAllocateBuffer(audioInfo->mQueue, audioInfo->mbufferSize, &audioInfo->mBuffers[i]);if(status ! = noErr) { NSLog(@"Audio Player: Allocate buffer status:%d",(int)status); }}Copy the code

4. Start the Audio Queue

  • Prequeue several buffers to drive playback

Since the audio queue is the mode that drives playback, data will continue to be queued from the callback function only after it is queued first, that is, we need to queue the previously allocated buffer to complete playback.

Audio data is read from the original audio data queue as follows, first queued, then copied to the AudioQueueBufferRef instance, fetching the required information (this queue can still be expanded).

    for(int i = 0; i ! = kNumberBuffers; i++) { [self receiveAudioDataWithAudioQueueBuffer:audioInfo->mBuffers[i] audioInfo:audioInfo audioBufferQueue:_audioBufferQueue]; }... - (void)receiveAudioDataWithAudioQueueBuffer:(AudioQueueBufferRef)inBuffer audioInfo:(XDXAudioInfoRef)audioInfo audioBufferQueue:(XDXCustomQueueProcess *)audioBufferQueue {
    XDXCustomQueueNode *node = audioBufferQueue->DeQueue(audioBufferQueue->m_work_queue);
    
    if(node ! = NULL) {if (node->size > 0) {
            UInt32 size = (UInt32)node->size;
            inBuffer->mAudioDataByteSize = size;
            memcpy(inBuffer->mAudioData, node->data, size);
            AudioStreamPacketDescription *packetDesc = (AudioStreamPacketDescription *)node->userData;
            AudioQueueEnqueueBuffer (
                                     audioInfo->mQueue,
                                     inBuffer,
                                     (packetDesc ? size : 0),
                                     packetDesc);

        }

        free(node->data);
        node->data = NULL;
        audioBufferQueue->EnQueue(audioBufferQueue->m_free_queue, node);
    }else {
        AudioQueueStop (
                        audioInfo->mQueue,
                        false); }}Copy the code
  • Start to work
    OSStatus status;
    status = AudioQueueStart(m_audioInfo->mQueue, NULL);
    if(status ! = noErr) { NSLog(@"Audio Player: Audio Queue Start failed status:%d \n",(int)status);
        return NO;
    }else {
        NSLog(@"Audio Player: Audio Queue Start successful");
        return YES;
    }
Copy the code

5. Trigger the callback function to play round robin

As mentioned earlier, the playback mode of the Audio queue is data-driven, that is, we have queued several audio queues, and when we start the Audio queue, it will automatically play the data that has been queued before, and when it is finished, it will automatically trigger a callback function to read the data for the next playback.

static void PlayAudioDataCallback(void * aqData,AudioQueueRef inAQ , AudioQueueBufferRef inBuffer) {
    XDXAudioQueuePlayer *instance = (__bridge XDXAudioQueuePlayer *)aqData;
    if(instance == NULL){
        return;
    }
    
    /* Debug
    static Float64 lastTime = 0;
    NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]*1000;
    NSLog(@"Test duration - %f",currentTime - lastTime);
    lastTime = currentTime;
    */
    
    [instance receiveAudioDataWithAudioQueueBuffer:inBuffer
                                         audioInfo:m_audioInfo
                                  audioBufferQueue:instance->_audioBufferQueue];
}
Copy the code

6. Other

Demo also has audio queue pause, resume, stop, destroy and other functions, relatively simple, not explained here.

7. Read audio data from audio files

  • Instantiated through the local file pathCFURLRefobject
- (void)configurePlayFilePath:(NSString *)filePath {
    char path[256];
    [filePath getCString:path maxLength:sizeof(path) encoding:NSUTF8StringEncoding];
    self->m_playFileURL = CFURLCreateFromFileSystemRepresentation (
                                                                   NULL,
                                                                   (const UInt8 *)path,
                                                                   strlen (path),
                                                                   false
                                                                   );
}

Copy the code
  • To use, open the file first

The permission and type of a file can be configured. In this example, the file type is CAF.

        OSStatus status;
        status = AudioFileOpenURL(self->m_playFileURL,
                                  kAudioFileReadPermission,
                                  kAudioFileCAFType,
                                  &self->m_playFile);
        if(status ! = noErr) { NSLog(@"open file failed: %d", (int)status);
        }
Copy the code
  • Reads audio data from a file

The function first specifies how many audio packets are read at a time, and returns the number of bytes that were finally read. In this case, m_playCurrentPacket is used to record the number of audio packets currently read in order to continue reading next time. Close the file after reading.

    UInt32 bytesRead = 0;
    UInt32 numPackets = readPacketsNum;
    OSStatus status = AudioFileReadPackets(m_playFile,
                                  false,
                                  &bytesRead,
                                  packetDesc,
                                  m_playCurrentPacket,
                                  &numPackets,
                                  audioDataRef);
    
    if(status ! = noErr) { NSLog(@"read packet failed: %d", (int)status);
    }
    
    if (bytesRead > 0) {
        m_playCurrentPacket += numPackets;
    }else {
        status = AudioFileClose(m_playFile);
        if(status ! = noErr) { NSLog(@"close file failed: %d", (int)status);
        }
        self.isPlayFileWorking = NO;
        m_playCurrentPacket = 0;
    }
Copy the code