为什么此音频会话无法识别中断? [英] Why does this audio session fail to recognise an interruption?

查看:80
本文介绍了为什么此音频会话无法识别中断?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序从查找表中合成音频.它可以成功播放音频,但是在我尝试停止播放时会崩溃.音频回放只需要退出而无需重新启动,因此处理中断的要求是基本的.我重新阅读了《 Apple的音频会话编程指南》,其中包括

My app synthesises audio from a lookup table. It plays audio successfully but crashes the moment I try to stop playing. Audio playback only needs to exit without restarting so the requirements for handling the interruption are basic. I reread Apple’s Audio Session Programming Guide including the section Responding to Interruptions. However the method handleAudioSessionInterruption does not seem to register an interrupt so I’m obviously missing something.

编辑查看我的答案.当我开始对此进行研究时,我对NSNotificationCenter几乎一无所知,因此我欢迎任何改进的建议.

EDIT See my answer. When I began work on this I knew next to nothing about NSNotificationCenter so I welcome any suggestion for improvement.

两种方法将音频会话设置为在前台播放.

Two methods set up the audio session to play in the foreground.

- (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;
}

这是当我按下停止按钮时处理中断的方法.但是它没有响应.

And here is the method that handles the interruption when I press the stop button. However it does not respond.

编辑正确的方法需要block,而不是selector.参见我的答案.

EDIT The correct method needs block, not selector. See my answer.

- (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;
        }
    }
}

推荐答案

答案我处理AudioSessionInterruption的方法未正确地向NSNotificationCentre订阅observer.通过使用block,而不是selector.

Answer My method to handle AudioSessionInterruption did not subscribe the observer correctly with NSNotificationCentre. This has been fixed by adding observer using block, not selector.

该解决方案取代了AudioBufferPlayer,中不推荐使用的AVAudioSession delegate方法,该方法非常适合最初由

The solution replaces deprecated AVAudioSession delegate methods in AudioBufferPlayer, an extremely fit for purpose audio player initially developed for direct audio synthesis by Matthias Hollejmans. Several deprecated functions including InterruptionListenerCallback were later upgraded by Mario Diana. The solution (below) uses NSNotification allowing users to exit AVAudioSession gracefully by pressing a button.

这是相关的代码.

PlayViewController.m

UIButton操作将有序关闭synth,使timer无效并发布将退出AVAudioSession

UIButton action performs an orderly shutdown of synth, invalidates the timer and posts the notification that will exit 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的初始化包括在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;
}

这篇关于为什么此音频会话无法识别中断?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆