为什么这个audio会话无法识别中断?

我的应用程序从查找表合成audio。 它能够成功播放audio,但是当我尝试停止播放的时候会崩溃。 audio播放只需要在不重新启动的情况下退出,因此处理中断的要求是基本的。 我重读了Apple的audio会话编程指南,包括“ 响应中断 ”一节。 然而,方法handleAudioSessionInterruption似乎并没有注册一个中断,所以我显然缺less一些东西。


编辑看到我的答案。 当我开始工作时,我几乎不知道NSNotificationCenter所以我欢迎任何改进build议。


两种方法设置audio会话在前台播放。

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

这里是按停止button时处理中断的方法。 但是它没有回应。


编辑正确的方法需要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; } } } 

回答我的方法来处理AudioSessionInterruption没有订阅observerNSNotificationCentre正确。 这已经通过添加observer使用block,而不是selector.

该解决scheme替代了AVAudioSession delegate不赞成使用的AVAudioSession delegate方法, AudioBufferPlayer,一种非常适合audio播放器的开发,用于Matthias Hollejmans的直接audio合成。 包括InterruptionListenerCallback在内的几个弃用函数后来由Mario Diana升级。 下面的解决scheme使用NSNotification允许用户通过按下一个button来正常退出AVAudioSession

这是相关的代码。

PlayViewController.m

UIButton操作执行synth的有序closures,使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; }