您的位置:首页 > 移动开发 > IOS开发

iOS音频播放(二):AudioSession

2014-07-17 09:39 423 查看


(本文转自码农人生



前言

在实施前一篇中所述的7个步骤步之前还必须面对一个麻烦的问题,AudioSession。

AudioSession简介
AudioSession这个玩意的主要功能包括以下几点(图片来自官方文档):

1. 确定你的app如何使用音频(是播放?还是录音?)
2. 为你的app选择合适的输入输出设备(比如输入用的麦克风,输出是耳机、手机功放或者airplay)
3. 协调你的app的音频播放和系统以及其他app行为(例如有电话时需要打断,电话结束时需要恢复,按下静音按钮时是否歌曲也要静音等)



AudioSession

AudioSession相关的类有两个:
1. AudioToolBox中的AudioSession
2. ***Foundation中的***AudioSession

其中AudioSession在SDK 7中已经被标注为depracated,而***AudioSession这个类虽然iOS 3开始就已经存在了,但其中很多方法和变量都是在iOS 6以后甚至是iOS 7才有的。所以各位可以依照以下标准选择:

* 如果最低版本支持iOS 5,可以使用AudioSession,也可以使用***AudioSession;
* 如果最低版本支持iOS 6及以上,请使用***AudioSession

下面以AudioSession类为例来讲述AudioSession相关功能的使用(很不幸我需要支持iOS 5。。T-T,使用***AudioSession的同学可以在其头文件中寻找对应的方法使用即可,需要注意的点我会加以说明)。

注意:在使用***AudioPlayer/***Player时可以不用关心AudioSession的相关问题,Apple已经把AudioSession的处理过程封装了,但音乐打断后的响应还是要做的(比如打断后音乐暂停了UI状态也要变化,这个应该通过KVO就可以搞定了吧。。我没试过瞎猜的>_ 0) { CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0); //Get the output type (will show airplay / hdmi etc CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type); airplayActived = (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo); } CFRelease(currentRouteDescriptionDictionary); } return airplayActived; }

设置类别
下一步要设置AudioSession的Category,使用AudioSession时调用下面的接口
extern OSStatus AudioSessionSetProperty(AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData);
如果我需要的功能是播放,执行如下代码
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback; AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
使用***AudioSession时调用下面的接口
/* set session category */ - (BOOL)setCategory:(NSString *)category error:(NSError **)outError; /* set session category with options */ - (BOOL)setCategory:(NSString *)category withOptions: (***AudioSessionCategoryOptions)options error:(NSError **)outError NS_***AILABLE_IOS(6_0);
至于Category的类型在官方文档中都有介绍,我这里也只罗列一下具体就不赘述了,各位在使用时可以依照自己需要的功能设置Category。
//AudioSession的AudioSessionCategory枚举 enum { kAudioSessionCategory_AmbientSound = 'ambi', kAudioSessionCategory_SoloAmbientSound = 'solo', kAudioSessionCategory_MediaPlayback = 'medi', kAudioSessionCategory_RecordAudio = 'reca', kAudioSessionCategory_PlayAndRecord = 'plar', kAudioSessionCategory_AudioProcessing = 'proc' };
//AudioSession的AudioSessionCategory字符串 /* Use this category for background sounds such as rain, car engine noise, etc. Mixes with other music. */ ***F_EXPORT NSString *const ***AudioSessionCategoryAmbient; /* Use this category for background sounds. Other music will stop playing. */ ***F_EXPORT NSString *const ***AudioSessionCategorySoloAmbient; /* Use this category for music tracks.*/ ***F_EXPORT NSString *const ***AudioSessionCategoryPlayback; /* Use this category when recording audio. */ ***F_EXPORT NSString *const ***AudioSessionCategoryRecord; /* Use this category when recording and playing back audio. */ ***F_EXPORT NSString *const ***AudioSessionCategoryPlayAndRecord; /* Use this category when using a hardware codec or signal processor while not playing or recording audio. */ ***F_EXPORT NSString *const ***AudioSessionCategoryAudioProcessing;

启用
有了Category就可以启动AudioSession了,启动方法:
//AudioSession的启动方法 extern OSStatus AudioSessionSetActive(Boolean active); extern OSStatus AudioSessionSetActiveWithFlags(Boolean active, UInt32 inFlags); //***AudioSession的启动方法 - (BOOL)setActive:(BOOL)active error:(NSError **)outError; - (BOOL)setActive:(BOOL)active withFlags:(NSInteger)flags error:(NSError **)outError NS_DEPRECATED_IOS(4_0, 6_0); - (BOOL)setActive:(BOOL)active withOptions:(***AudioSessionSetActiveOptions)options error:(NSError **)outError NS_***AILABLE_IOS(6_0);
启动方法调用后必须要判断是否启动成功,启动不成功的情况经常存在,例如一个前台的app正在播放,你的app正在后台想要启动AudioSession那就会返回失败。

一般情况下我们在启动和停止AudioSession调用第一个方法就可以了。但如果你正在做一个即时语音通讯app的话(类似于微信、易信)就需要注意在deactive AudioSession的时候需要使用第二个方法,inFlags参数传入kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation(***AudioSession给options参数传入***AudioSessionSetActiveOptionNotifyOthersOnDeactivation)。当你的app deactive自己的AudioSession时系统会通知上一个被打断播放app打断结束(就是上面说到的打断回调),如果你的app在deactive时传入了NotifyOthersOnDeactivation参数,那么其他app在接到打断结束回调时会多得到一个参数kAudioSessionInterruptionType_ShouldResume否则就是ShouldNotResume(***AudioSessionInterruptionOptionShouldResume),根据参数的值可以决定是否继续播放。

大概流程是这样的:

1. 一个音乐软件A正在播放;
2. 用户打开你的软件播放对话语音,AudioSession active;
3. 音乐软件A音乐被打断并收到InterruptBegin事件;
4. 对话语音播放结束,AudioSession deactive并且传入NotifyOthersOnDeactivation参数;
5. 音乐软件A收到InterruptEnd事件,查看Resume参数,如果是ShouldResume控制音频继续播放,如果是ShouldNotResume就维持打断状态;

官方文档中有一张很形象的图来阐述这个现象:



然而现在某些语音通讯软件和某些音乐软件却无视了NotifyOthersOnDeactivation和ShouldResume的正确用法,导致我们经常接到这样的用户反馈:“你们的app在使用xx语音软件听了一段话后就不会继续播放了,但xx音乐软件可以继续播放啊。”

好吧,上面只是吐槽一下。请无视我吧。

补充:

发现即使之前已经调用过AudioSessionInitialize方法,在某些情况下被打断之后可能出现AudioSession失效的情况,需要再次调用AudioSessionInitialize方法来重新生成AudioSession。否则调用AudioSessionSetActive会返回560557673(其他AudioSession方法也雷同,所有方法调用前必须首先初始化AudioSession),转换成string后为”!ini”即kAudioSessionNotInitialized,这个情况在iOS 5.1.x上尤其频繁,iOS 7.x也偶有发生具体的原因还不知晓。

所以每次在调用AudioSessionSetActive时应该判断一下错误码,如果是上述的错误码需要重新初始化一下AudioSession。

附上OSStatus转成string的方法:
#import NSString * OSStatusToString(OSStatus status) { size_t len = sizeof(UInt32); long addr = (unsigned long)&status; char cstring[5]; len = (status >> 24) == 0 ? len - 1 : len; len = (status >> 16) == 0 ? len - 1 : len; len = (status >> 8) == 0 ? len - 1 : len; len = (status >> 0) == 0 ? len - 1 : len; addr += (4 - len); status = EndianU32_NtoB(status); // strings are big endian strncpy(cstring, (char *)addr, len); cstring[len] = 0; return [NSString stringWithCString:(char *)cstring encoding:NSMacOSRomanStringEncoding]; }

打断处理
正常启动AudioSession之后就可以播放音频了,下面要讲的是对于打断的处理。之前我们说到打断的回调在iOS 5下需要统一管理,在收到打断开始和结束时需要发送自定义的通知。

使用AudioSession时打断回调应该首先获取kAudioSessionProperty_InterruptionType,然后发送一个自定义的通知并带上对应的参数。
static void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState) { AudioSessionInterruptionType interruptionType = kAudioSessionInterruptionType_ShouldNotResume; UInt32 interruptionTypeSize = sizeof(interruptionType); AudioSessionGetProperty(kAudioSessionProperty_InterruptionType, &interruptionTypeSize, &interruptionType); NSDictionary *userInfo = @{MyAudioInterruptionStateKey:@(inInterruptionState), MyAudioInterruptionTypeKey:@(interruptionType)}; [[NSNotificationCenter defaultCenter] postNotificationName:MyAudioInterruptionNotification object:nil userInfo:userInfo]; }
收到通知后的处理方法如下(注意ShouldResume参数):
- (void)interruptionNotificationReceived:(NSNotification *)notification { UInt32 interruptionState = [notification.userInfo[MyAudioInterruptionStateKey] unsignedIntValue]; AudioSessionInterruptionType interruptionType = [notification.userInfo[MyAudioInterruptionTypeKey] unsignedIntValue]; [self handleAudioSessionInterruptionWithState:interruptionState type:interruptionType]; } - (void)handleAudioSessionInterruptionWithState:(UInt32)interruptionState type:(AudioSessionInterruptionType)interruptionType { if (interruptionState == kAudioSessionBeginInterruption) { //控制UI,暂停播放 } else if (interruptionState == kAudioSessionEndInterruption) { if (interruptionType == kAudioSessionInterruptionType_ShouldResume) { OSStatus status = AudioSessionSetActive(true); if (status == noErr) { //控制UI,继续播放 } } } }

小结
关于AudioSession的话题到此结束(码字果然很累。。)。小结一下:

* 如果最低版本支持iOS 5,可以使用AudioSession也可以考虑使用***AudioSession,需要有一个类统一管理AudioSession的所有回调,在接到回调后发送对应的自定义通知;
* 如果最低版本支持iOS 6及以上,请使用***AudioSession,不用统一管理,接***AudioSession的通知即可;
* 根据app的应用场景合理选择Category;
* 在deactive时需要注意app的应用场景来合理的选择是否使用NotifyOthersOnDeactivation参数;
* 在处理InterruptEnd事件时需要注意ShouldResume的值。

示例代码
这里有我自己写的AudioSession的封装,如果各位需要支持iOS 5的话可以使用一下。

下一篇将讲述如何使用AudioFileStreamer提取音频文件格式信息和分离音频帧。

参考资料

AudioSession

相关阅读:

iOS音频播放(一):概述
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: