Почему этот аудио сеанс не распознает прерывание?

Мое приложение синтезирует аудио из справочной таблицы. Он воспроизводит аудио успешно, но вылетает в тот момент, когда я пытаюсь остановить воспроизведение. Воспроизведение аудио необходимо завершить без перезапуска, поэтому требования к обработке прерывания являются основными. Я перечитал руководство по программированию аудиосеансов Apple, включая раздел " Реагирование на прерывания". Однако метод handleAudioSessionInterruption кажется, не регистрирует прерывание, поэтому я явно что-то упускаю.


РЕДАКТИРОВАТЬ Смотри мой ответ. Когда я начал работать над этим, я почти ничего не знал о NSNotificationCenter поэтому я приветствую любые предложения по улучшению.


Два метода настраивают аудио сеанс для воспроизведения на переднем плане.

- (void)setUpAudio
{
    if (_playQueue == NULL)
    {
        if ([self setUpAudioSession] == TRUE)
        {
            [self setUpPlayQueue];
            [self setUpPlayQueueBuffers];
        }
    }
}


- (BOOL)setUpAudioSession
{
    BOOL success                        = NO;
    NSError *audioSessionError          = nil;

    AVAudioSession *session             = [AVAudioSession sharedInstance];

    // Set up notifications

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleAudioSessionInterruption:)
                                                 name:AVAudioSessionInterruptionNotification
                                               object:session];

    // Set category

    success                             = [session setCategory:AVAudioSessionCategoryPlayback
                                                         error:&audioSessionError];
    if (!success)
    {
        NSLog(@"%@ Error setting category: %@",
              NSStringFromSelector(_cmd), [audioSessionError localizedDescription]);

        // Exit early
        return success;
    }

    // Set mode

    success                             = [session setMode:AVAudioSessionModeDefault
                                                     error:&audioSessionError];
    if (!success)
    {
        NSLog(@"%@ Error setting mode: %@",
              NSStringFromSelector(_cmd), [audioSessionError localizedDescription]);

        // Exit early
        return success;
    }

    // Set some preferred values

    NSTimeInterval bufferDuration       = .005; // I would prefer a 5ms buffer duration

    success                             = [session setPreferredIOBufferDuration:bufferDuration
                                                                          error:&audioSessionError];
    if (audioSessionError)
    {
        NSLog(@"Error %ld, %@ %i", (long)audioSessionError.code, audioSessionError.localizedDescription, success);
    }

    double sampleRate                   = _audioFormat.mSampleRate; // I would prefer a sample rate of 44.1kHz

    success                             = [session setPreferredSampleRate:sampleRate
                                                                    error:&audioSessionError];
    if (audioSessionError)
    {
        NSLog(@"Error %ld, %@ %i", (long)audioSessionError.code, audioSessionError.localizedDescription, success);
    }

    success                             = [session setActive:YES
                                                       error:&audioSessionError];
    if (!success)
    {
        NSLog(@"%@ Error activating %@",
              NSStringFromSelector(_cmd), [audioSessionError localizedDescription]);
    }

    // Get current values

    sampleRate                          = session.sampleRate;
    bufferDuration                      = session.IOBufferDuration;
    NSLog(@"Sample Rate:%0.0fHz I/O Buffer Duration:%f", sampleRate, bufferDuration);

    return success;
}

И вот метод, который обрабатывает прерывание, когда я нажимаю кнопку остановки. Однако это не отвечает.


РЕДАКТИРОВАТЬ Правильный метод нуждается block, не selector. Смотри мой ответ.


- (void)handleAudioSessionInterruption:(NSNotification*)notification
{
    if (_playQueue)
    {
        NSNumber *interruptionType      = [[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey];
        NSNumber *interruptionOption    = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];

        NSLog(@"in-app Audio playback will be stopped by %@ %lu", notification.name, (unsigned long)interruptionType.unsignedIntegerValue);

        switch (interruptionType.unsignedIntegerValue)
        {
            case AVAudioSessionInterruptionTypeBegan:
            {
                if (interruptionOption.unsignedIntegerValue == AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation)
                {
                    NSLog(@"notify other apps that audio is now available");
                }
            }
                break;

            default:
                break;
        }
    }
}

1 ответ

Ответ Мой метод для обработки AudioSessionInterruption не подписывался observer правильно с NSNotificationCentre, Это было исправлено добавлением observer с помощью block, не selector.

Решение заменяет устаревшее AVAudioSession delegate методы в AudioBufferPlayer, Аудиоплеер, идеально подходящий для этой цели, изначально разработанный Маттиасом Холлеймансом для прямого синтеза звука Несколько устаревших функций, включая InterruptionListenerCallback были позже модернизированы Марио Дианой. Решение (ниже) использует NSNotification позволяя пользователям выйти AVAudioSession изящно нажатием кнопки.

Вот соответствующий код.

PlayViewController.m

UIButton действие выполняет упорядоченное отключение synth, делает недействительным timer и отправляет уведомление о выходе AVAudioSession

- (void)fromEscButton:(UIButton*)button
{
    [self stopConcertClock];

    ...                            // code for Exit PlayViewController not shown
}

- (void)stopConcertClock
{
    [_synthLock lock];
    [_synth stopAllNotes];
    [_synthLock unlock];
    [timer invalidate];
    timer = nil;
    [self postAVAudioSessionInterruptionNotification];
    NSLog(@"Esc button pressed or sequence ended. Exit PlayViewController ");
}


- (void) postAVAudioSessionInterruptionNotification
{
    [[NSNotificationCenter defaultCenter]
     postNotificationName:@"AVAudioSessionInterruptionNotification"
     object:self];
}

Инициализация AVAudioSession включает в себя подписку на одно уведомление о прерывании перед началом startAudioPlayer в AudioBufferPlayer

- (id)init
{
    if (self = [super init])
    {
        NSLog(@"PlayViewController starts MotionListener and AudioSession");

        [self startAudioSession];
    }
    return self;
}


- (void)startAudioSession
    {
    // Synth and the AudioBufferPlayer must use the same sample rate.

    _synthLock = [[NSLock alloc] init];

    float sampleRate = 44100.0f;

    // Initialise synth to fill the audio buffer with audio samples.

    _synth = [[Synth alloc] initWithSampleRate:sampleRate];

    // Initialise the audio buffer.

    _player = [[AudioBufferPlayer alloc] initWithSampleRate:sampleRate 
                                                   channels:1
                                             bitsPerChannel:16
                                           packetsPerBuffer:1024];
    _player.gain = 0.9f;

    __block __weak PlayViewController *weakSelf = self;


    _player.block = ^(AudioQueueBufferRef buffer, AudioStreamBasicDescription audioFormat)
{
    PlayViewController *blockSelf   = weakSelf;

    if (blockSelf != nil)
    {
        // Lock access to the synth. This callback runs on an internal Audio Queue thread and we don't
        // want another thread to change the Synth's state while we're still filling up the audio buffer.

    [blockSelf -> _synthLock lock];

        // Calculate how many packets fit into this buffer. Remember that a packet equals one frame
        // because we are dealing with uncompressed audio; a frame is a set of left+right samples
        // for stereo sound, or a single sample for mono sound. Each sample consists of one or more
        // bytes. So for 16-bit mono sound, each packet is 2 bytes. For stereo it would be 4 bytes.

    int packetsPerBuffer = buffer -> mAudioDataBytesCapacity / audioFormat.mBytesPerPacket;

        // Let the Synth write into the buffer. The Synth just knows how to fill up buffers
        // in a particular format and does not care where they come from.

    int packetsWritten              = [blockSelf -> _synth fillBuffer:buffer->mAudioData frames:packetsPerBuffer];

        // We have to tell the buffer how many bytes we wrote into it.

    buffer -> mAudioDataByteSize    = packetsWritten * audioFormat.mBytesPerPacket;

    [blockSelf -> _synthLock unlock];
    }
};

// Set up notifications

    [self subscribeForBlockNotification];

    [_player startAudioPlayer];
}


- (void)subscribeForBlockNotification
{
    NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter];

    id __block token = [center addObserverForName:@"AVAudioSessionInterruptionNotification"

                                       object:nil
                                        queue:[NSOperationQueue mainQueue]
                                   usingBlock:^(NSNotification *note) {
                                       NSLog(@"Received the notification!");
                                       [_player stopAudioPlayer];
                                       [center removeObserver:token];
                                   }];
}

PlayViewController.h

Это соответствующие настройки интерфейса

@interface PlayViewController : UIViewController <EscButtonDelegate>

{    
    ... 

    // Initialisation of audio player and synth

    AudioBufferPlayer* player;
    Synth*             synth;
    NSLock*            synthLock;
}

    ...

    - (AudioBufferPlayer*)player;
    - (Synth*)synth;

@end

AudioBufferPlayer.m

- (void)stopAudioPlayer
{
    [self stopPlayQueue];
    [self tearDownPlayQueue];
    [self tearDownAudioSession];
}


- (void)stopPlayQueue
{
    if (_audioPlaybackQueue != NULL)
    {
        AudioQueuePause(_audioPlaybackQueue);
        AudioQueueReset(_audioPlaybackQueue);
        _playing = NO;
    }
}


- (void)tearDownPlayQueue
{
    AudioQueueDispose(_audioPlaybackQueue, NO);
    _audioPlaybackQueue = NULL;
}


- (BOOL)tearDownAudioSession
{
    NSError *deactivationError = nil;
    BOOL success = [[AVAudioSession sharedInstance] setActive:NO
                                                  withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                                                        error:nil];
    if (!success)
    {
        NSLog(@"%s AVAudioSession Error: %@", __FUNCTION__, deactivationError);
    }
    return success;
}
Другие вопросы по тегам