Search code examples
objective-cavaudioplayeraudioqueue

AudioQueue cannot play audio after switch the application


I have developed an audio play app in iOS with Objective-c and C language, it can play audio in most situations. But when I switch the app into background and then switch it back,it will not play the audio. Another problem is when I open it with earphone, if I take off the earphone, it will also stop play the audio.

The init function is:

    - (instancetype)init {
    self = [super init];
    if (self) {
        sysnLock = [[NSLock alloc] init];

        if (_audioDescription.mSampleRate <= 0) {
            _audioDescription.mSampleRate = _sampleRates;
            _audioDescription.mFormatID = kAudioFormatLinearPCM;

            _audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

            _audioDescription.mChannelsPerFrame = _channels;

            _audioDescription.mFramesPerPacket = 1;

            _audioDescription.mBitsPerChannel = 16;
            _audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;

            _audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;
        }

        AudioQueueNewOutput(&_audioDescription, AudioPlayerCallback, (__bridge void *_Nullable)(self), nil, 0, 0,
                            &audioQueue);

        AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 100.0);

        for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
            audioQueueBufferUsed[i] = false;

            osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
        }

        osState = AudioQueueStart(audioQueue, NULL);
    }
    return self;
}

In which AudioPlayerCallback is a callback function to reset the audioBufferUsed.

And the audio play function is:

- (void)startPlay:(AudioData *)audioData {
    uint8_t *c_data = get_data(audioData);
    size_t c_data_size = get_data_size(audioData);

    [sysnLock lock];
    int i = 0;
    while (true) {
        if (!audioQueueBufferUsed[i]) {
            audioQueueBufferUsed[i] = true;
            break;
        } else {
            i++;
            if (i >= QUEUE_BUFFER_SIZE) {
                i = 0;
            }
        }
    }

    audioQueueBuffers[i]->mAudioDataByteSize = (unsigned int)c_data_size;
    memcpy(audioQueueBuffers[i]->mAudioData, c_data, c_data_size);
    AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);
    OSStatus status = AudioQueueStart(audioQueue, NULL);

    [sysnLock unlock];
}

In which get_data and get_data_size can get the uint_8 data and its size to play.


Solution

  • You will setup NotificationCenter to observe the following keys AVAudioSessionInterruptionNotification and AVAudioSessionRouteChangeNotification

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruption:) name:AVAudioSessionInterruptionNotification
      object:nil];
    
    - (void)interruption:(NSNotification *)notiz {
        // handle stuff when audio interrupts
    }
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
    
    - (void)routeChanged:(NSNotification *)notiz {
        // handle stuff when route is changed, aka headphone jack in/out
    }
    

    In classic coding style you don't forget to remove the observer in your dealloc method.

    [[NSNotificationCenter defaultCenter] removeObserver:self name:... object:nil];
    

    This will allow you to watch when your disturbing events happen and act accordingly with re-creation of your AudioQueue, Start, Stop, Buffer if needed.

    Another way is to observe changes of propertys of the AudioQueueBuffer API with...

    AudioQueueAddPropertyListener(AudioQueueRef inAQ, AudioQueuePropertyID inID, AudioQueuePropertyListenerProc inProc, void * inUserData);