Silver
Silver

Reputation: 77

Audio queue works not as expected in iOS 10

Update

I resolved the problem in recording in iOS 10. After adding Audio Session configuration before starting recording, it works as normal. But playback hasn't been resolved.

Here's the solution:

NSError *error = nil;
// the param category depends what you need
BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if (!ret) {  
    NSLog(@"Audio session category setup failed");  
    return;  
}
// don't forget to setActive NO when finishing recording
ret = [[AVAudioSession sharedInstance] setActive:YES error:&error];  
if (!ret)  
{  
    NSLog(@"Audio session activation failed");  
    return;  
}  

Original

I work in audio recording with audio queue service in iOS. I followed apple's official tutorial to realize the recording part and playback part. It was successfully tested in iOS 9.3 in emulator but failed in iOS 10.3.1 in real device iPad.

For the recording part, the callback function invokes AudioFileWritePackets to save the audio into a file (see the code below). In iOS 9, ioNumPackets always has a non-zero value but in iOS 10, it is always 0 during the first recording, and from the second time it becomes normal. That is, only from the second time the recording works.

Here's some code about recording:

Callback function:

static void AudioInputCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp * inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription * inPacketDescs) {
    NSLog(@"Input callback called");
    RecordState * aqData = (RecordState*)inUserData;
    if (aqData->isRecording == 0) return;
    if (inNumPackets == 0 && aqData->dataFormat.mBytesPerPacket != 0)
        inNumPackets = inBuffer->mAudioDataByteSize / aqData->dataFormat.mBytesPerPacket;
    NSLog(@"inNumPackets = %d", inNumPackets);
    // handler the data
    if (outputToMobile){
        OSStatus res = AudioFileWritePackets(aqData->audioFile, false, inBuffer->mAudioDataByteSize, inPacketDescs, aqData->currentPacket, &inNumPackets, inBuffer->mAudioData);
        if(res == noErr)
            aqData->currentPacket += inNumPackets;
    }else{ 
    }
    // after handling, re-enqueue de buffer into the queue
    AudioQueueEnqueueBuffer(aqData->queue, inBuffer, 0, NULL);
}

Start record function:

-(void)startRecording{
    [self setupAudioFormat:&recordState.dataFormat];
    recordState.currentPacket = 0;
    OSStatus status;
    status = AudioQueueNewInput(&recordState.dataFormat, AudioInputCallback, &recordState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &recordState.queue);
    if (status == 0) {
        UInt32 dataFormatSize = sizeof (recordState.dataFormat);
        AudioQueueGetProperty (recordState.queue,kAudioQueueProperty_StreamDescription,&recordState.dataFormat,&dataFormatSize);
        if (outputToMobile) {
            [self createFile];
            SetMagicCookieForFile(recordState.queue, recordState.audioFile);
        }
        DeriveBufferSize(recordState.queue, &recordState.dataFormat, 0.5,  &recordState.bufferByteSize);
        for (int i = 0; i < NUM_BUFFERS; i++) {
            AudioQueueAllocateBuffer(recordState.queue, recordState.bufferByteSize, &recordState.buffers[i]);
            AudioQueueEnqueueBuffer(recordState.queue, recordState.buffers[i], 0, NULL);
        }
        recordState.isRecording = true;
        AudioQueueStart(recordState.queue, NULL);
    }
}

For the playback part, the callback function invokes AudioFileReadPacketData to read the audio file (see the code below). As well, in iOS 9, ioNumPackets is always non-zero but in iOS 10, ioNumPackets is always always 0 so that nothing is output from iOS 10.

Here's some code about playback:

Callback function:

static void AudioOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer){
    NSLog(@"Output callback called");
    PlayState *aqData = (PlayState *)inUserData;
    if (aqData->isPlaying == 0) return;
    UInt32 numBytesReadFromFile;
    UInt32 numPackets = aqData->numPacketsToRead;
    AudioFileReadPacketData(aqData->audioFile, false, &numBytesReadFromFile, aqData->packetDesc, aqData->currentPacket, &numPackets, inBuffer->mAudioData);
    NSLog(@"outNumPackets = %d", numPackets);
    if (numPackets > 0) {
        AudioQueueEnqueueBuffer(aqData->queue, inBuffer, aqData->packetDesc ? numPackets : 0, aqData->packetDesc);
        aqData->currentPacket += numPackets;
    } else {
        AudioQueueStop(aqData->queue, false);
        aqData->isPlaying = false;
    }
}

Start playback function:

- (void)startPlaying{
    playState.currentPacket = 0;
    [self openFile];
    UInt32 dataFormatSize = sizeof(playState.dataFormat);

    AudioFileGetProperty(playState.audioFile, kAudioFilePropertyDataFormat, &dataFormatSize, &playState.dataFormat);

    OSStatus status;
    status = AudioQueueNewOutput(&playState.dataFormat, AudioOutputCallback, &playState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &playState.queue);
    if (status == 0) {
        playState.isPlaying = true;
        UInt32 maxPacketSize;
        UInt32 propertySize = sizeof(maxPacketSize);
        AudioFileGetProperty(playState.audioFile,kAudioFilePropertyPacketSizeUpperBound,&propertySize,&maxPacketSize);

        DeriveBufferSize(playState.dataFormat, maxPacketSize, 0.5, &playState.bufferByteSize, &playState.numPacketsToRead);

        bool isFormatVBR = (playState.dataFormat.mBytesPerPacket == 0 ||playState.dataFormat.mFramesPerPacket == 0);
        if (isFormatVBR) {
            playState.packetDesc = (AudioStreamPacketDescription*) malloc (playState.numPacketsToRead * sizeof(AudioStreamPacketDescription));
        } else {
            playState.packetDesc = NULL;
        }
        //Set a Magic Cookie for a Playback Audio Queue
        MyCopyEncoderCookieToQueue(playState.audioFile, playState.queue);

        for (int i = 0; i < NUM_BUFFERS; i++) {
            AudioQueueAllocateBuffer(playState.queue, playState.bufferByteSize, &playState.buffers[i]);
            playState.buffers[i]->mAudioDataByteSize = playState.bufferByteSize;
            AudioOutputCallback(&playState, playState.queue, playState.buffers[i]);
        }
        Float32 gain = 10.0;
        AudioQueueSetParameter(playState.queue, kAudioQueueParam_Volume, gain);
        AudioQueueStart(playState.queue, NULL);
    }

}

This kind of incompatibility really upsets me for several days. Free to ask me if you need more details. I hope someone could help me out. Thanks a lot.

Upvotes: 1

Views: 703

Answers (0)

Related Questions