为什么此音频会话无法识别中断?

时间:2017-09-12 11:01:55

标签: ios nsnotificationcenter audio-player

我的应用程序从查找表中合成音频。它成功播放音频,但在我尝试停止播放时崩溃。音频播放只需退出而不重新启动,因此处理中断的要求是基本的。我重读了Apple的音频会话编程指南,包括Responding to Interruptions部分。然而,方法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 个答案:

答案 0 :(得分:0)

回答我处理AudioSessionInterruption的方法未正确使用observer订阅NSNotificationCentre。通过使用observer而不是block,

添加selector.来解决此问题

该解决方案取代了AVAudioSession delegate中已弃用的AudioBufferPlayer,方法,这种方法非常适合用于Matthias Hollejmans最初为直接音频合成开发的音频播放器。一些已弃用的功能(包括InterruptionListenerCallback)后来被Mario Diana升级。解决方案(以下)使用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;
}