夜深时动笔
前面一篇文章写了视频播放的几种基本的方式,算是给这个系列开了一个头,这里面最想说和探究的就是AVFoundation框架,很想把这个框架不敢说是完全理解,但至少想把它弄明白它里面到底有什么,这个过程需要一些时间,既然是不明白的东西就得花时间来总结学习。白天工作的时候都要忙着项目的事,只能等晚上或者哪天上班没其他事打扰或者周末去花时间来做这些了,毕业这么些年,有时候还是会想起以前在学校时候,那时候只顾着长身体追求我的女神和电竞梦,其实就是什么都没做成。也真是浪费了太多的时间,要是再有学校那时的时光环境,那时的我们又不会有工作、生活上的压力,要是把时间放在自己现在才发觉这是自己喜欢做的事上结果不知道会是什么样子,不知道有没有还在学校的朋友会看到这些文章,不管有没有还是想说一句,放弃掉那些不会有结果的事,好好的去做一些你想做的!工作了很多事就不是你想不想了,与其说是身不由己,不如说是有心无力!回正题,总结AVFoundation。
我准备在这个系列当中总结一下AVFoundation这个框架,从最基本的入手,一点点的学习这个框架里面的每一个类,争取把这个框架里面的基本的类都有一个涉及到。我也是看着《AVFoundation 开发秘籍》开始学习这个框架。
下面我们一个一个的一遍看书中的内容,按照框架里面的类分别一个一个总结。
这个系列的几篇文章的Demo都在 (点击下载Demo)后面文章的Demo也在这里,需要的可以一起下载看看。
AVFoundation
凡是对这个框架有想过了解的同学肯定也见过下面这张图:
这张图还是挺好理解的,我们大概的总结一下:
在《AVFoundation开发秘籍》书中有这样一段描述,AVFoundation是苹果在iOS和OS X系统中用于处理基于时间的媒体数据的框架。这句话也就说明了它的一个基本的作用,在项目中你嵌入H5也照样能播放视频,但涉及到视频的采集(比如说微信的短视频拍摄)时候你就只能乖乖的去利用AVFoundation了。
AVFoundation是封装在 Core Avdio 、Core Media 、Core Animition 等这些个层次之上的,它里面还包括一个音频类,在上层就是我们常用的UIKit了,再往上层图上面写的是media Play其实就是我们熟悉的AVKit层,AVKit及方便的简化了媒体应用创建的过程 。AVKit 这个视频播放的部分相信大家都比较熟悉了,我们就不在这里多说了,在前面我们说过一部分关于它,我们在后面重点说说它其他的方面。
我们再说说它下面的三层都做了些什么事:
1、 Core Avdio 处理所有音频事件,为所有音频以及MIDI(Musical Instrument Digital Interface 乐器数字接口)内容的录制、播放等提供了接口。设置可以针对音频信号进行完全控制,并通过Audio Units来构建一些复杂的音频处理,它是由多个框架整合在一起的。看着这么多内容感觉这个框架我们都能学习一大堆东西,我们接着往下总结先。
2、Core Media 是提供音频样本和视频帧处理等的API
3、Core Animition 动画相关框架, 封装了支持OpenGL和OpenGL ES功能的ObjC各种类.。AVFoundation可以利用CoreAnimation让开发者能够在视频的编辑和播放过程中添加动画和图片效果。
AVSpeechSynthesizer
在书中最开始的时候简单的介绍了一下AVSpeechSynthesizer,它可以很方便的在iOS应用中添加“文本到语音”的功能,我们在Demo中在你开始录制视频的时候有一个语音的提示,就是用它处理的,我们简单的看看它的代码,整理的一些基本的用法以及一些属性的意义都在代码的注释中:
// 简单的语音测试 -(void)speakHintMessage{ // 这样子可以简单的播放一段语音 AVSpeechSynthesizer * synthesizer = [[AVSpeechSynthesizer alloc]init]; // Utterance 表达方式 AVSpeechSynthesisVoice * voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"]; AVSpeechUtterance * utterance = [[AVSpeechUtterance alloc]initWithString:@"准备了猪,开始录制视频了"]; utterance.rate = 1.5; // 这个是播放速率 默认1.0 utterance.voice = voice; utterance.pitchMultiplier = 0.8; // 可在播放待定语句时候改变声调 utterance.postUtteranceDelay = 0.1; // 语音合成器在播放下一条语句的时候有短暂的停顿 这个属性指定停顿的时间 [synthesizer speakUtterance:utterance]; /* "[AVSpeechSynthesisVoice 0x978a0b0] Language: th-TH", "[AVSpeechSynthesisVoice 0x977a450] Language: pt-BR", "[AVSpeechSynthesisVoice 0x977a480] Language: sk-SK", "[AVSpeechSynthesisVoice 0x978ad50] Language: fr-CA", "[AVSpeechSynthesisVoice 0x978ada0] Language: ro-RO", "[AVSpeechSynthesisVoice 0x97823f0] Language: no-NO", "[AVSpeechSynthesisVoice 0x978e7b0] Language: fi-FI", "[AVSpeechSynthesisVoice 0x978af50] Language: pl-PL", "[AVSpeechSynthesisVoice 0x978afa0] Language: de-DE", "[AVSpeechSynthesisVoice 0x978e390] Language: nl-NL", "[AVSpeechSynthesisVoice 0x978b030] Language: id-ID", "[AVSpeechSynthesisVoice 0x978b080] Language: tr-TR", "[AVSpeechSynthesisVoice 0x978b0d0] Language: it-IT", "[AVSpeechSynthesisVoice 0x978b120] Language: pt-PT", "[AVSpeechSynthesisVoice 0x978b170] Language: fr-FR", "[AVSpeechSynthesisVoice 0x978b1c0] Language: ru-RU", "[AVSpeechSynthesisVoice 0x978b210] Language: es-MX", "[AVSpeechSynthesisVoice 0x978b2d0] Language: zh-HK", "[AVSpeechSynthesisVoice 0x978b320] Language: sv-SE", "[AVSpeechSynthesisVoice 0x978b010] Language: hu-HU", "[AVSpeechSynthesisVoice 0x978b440] Language: zh-TW", "[AVSpeechSynthesisVoice 0x978b490] Language: es-ES", "[AVSpeechSynthesisVoice 0x978b4e0] Language: zh-CN", "[AVSpeechSynthesisVoice 0x978b530] Language: nl-BE", "[AVSpeechSynthesisVoice 0x978b580] Language: en-GB", "[AVSpeechSynthesisVoice 0x978b5d0] Language: ar-SA", "[AVSpeechSynthesisVoice 0x978b620] Language: ko-KR", "[AVSpeechSynthesisVoice 0x978b670] Language: cs-CZ", "[AVSpeechSynthesisVoice 0x978b6c0] Language: en-ZA", "[AVSpeechSynthesisVoice 0x978aed0] Language: en-AU", "[AVSpeechSynthesisVoice 0x978af20] Language: da-DK", "[AVSpeechSynthesisVoice 0x978b810] Language: en-US", "[AVSpeechSynthesisVoice 0x978b860] Language: en-IE", "[AVSpeechSynthesisVoice 0x978b8b0] Language: hi-IN", "[AVSpeechSynthesisVoice 0x978b900] Language: el-GR", "[AVSpeechSynthesisVoice 0x978b950] Language: ja-JP" ) */ // 通过这个方法可以看到整个支持的会话的列表,信息如上输出 NSLog(@"目前支持的语音列表:%@",[AVSpeechSynthesisVoice speechVoices]); }
AVAudioPlayer
AVAudioPlayer也是在我们要说的 AV Foundation 框架里面,这个类的实例提供了简单的从文本或者是内存中播放一音频的功能,虽然API很简单,但是它提供的功能却是很强大的,并且在MAC合作和是iOS系统中经常被作为实现音频播放的最佳的选择。
AVAudioPlayer构建与CoreServices中的C-based Audio Queue Services 的最顶层,所以他可以提供你在 Audio Queue Services 中所能找到的核心功能,比如播放。循环甚至是音频的计量,使用的时候它提供了非常友好的OC的接口,除非你需要从网络流中播放音频,需要访问原始音频样本或者需要非常低的延时,否则AVAudioPlayer都能胜任。
下面看看AVAudioPlayer的一些具体的属性以及方法,我们解释一下这些属性或者方法:
/* AVAudioPlayer 基本方法以及属性 基本的初始化方法 - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError; - (nullable instancetype)initWithData:(NSData *)data error:(NSError **)outError; - (nullable instancetype)initWithContentsOfURL:(NSURL *)url fileTypeHint:(NSString * __nullable)utiString error:(NSError **)outError NS_AVAILABLE(10_9, 7_0); - (nullable instancetype)initWithData:(NSData *)data fileTypeHint:(NSString * __nullable)utiString error:(NSError **)outError NS_AVAILABLE(10_9, 7_0); // 准备播放,这个方法可以不执行,但执行的话可以降低播放器play方法和你听到声音之间的延时 - (BOOL)prepareToPlay; // 播放 - (BOOL)play; // play a sound some time in the future. time is an absolute time based on and greater than deviceCurrentTime. // 跳转到某一个时间点播放 - (BOOL)playAtTime:(NSTimeInterval)time NS_AVAILABLE(10_7, 4_0); // 暂停 pauses playback, but remains ready to play - (void)pause; // 停止 // 它和上面的暂停的方法是在底层stop会撤销掉prepareToPlay时所作的设置,但是调用暂停不会 - (void)stop; properties // 是否在播放 @property(readonly, getter=isPlaying) BOOL playing // 音频声道数,只读 @property(readonly) NSUInteger numberOfChannels // 音长 @property(readonly) NSTimeInterval duration //the delegate will be sent messages from the AVAudioPlayerDelegate protocol @property(assign, nullable) id delegate; // 下面两个是获取到的你初始化传入的相应的值 @property(readonly, nullable) NSURL *url @property(readonly, nullable) NSData *data // set panning. -1.0 is left, 0.0 is center, 1.0 is right. NS_AVAILABLE(10_7, 4_0) // 允许使用立体声播放声音 如果为-1.0则完全左声道,如果0.0则左右声道平衡,如果为1.0则完全为右声道 @property float pan // 音量 The volume for the sound. The nominal range is from 0.0 to 1.0. @property float volume // set音量逐渐减弱在时间间隔内 - (void)setVolume:(float)volume fadeDuration:(NSTimeInterval)duration NS_AVAILABLE(10_12, 10_0); // 是否能设置rate属性,只有这个属性设置成YES了才能设置rate属性,并且这些属性都设置在prepareToPlay方法调用之前 @property BOOL enableRate NS_AVAILABLE(10_8, 5_0); @property float rate NS_AVAILABLE(10_8, 5_0); // 当前播放的时间,利用定时器去观察这个属性可以读取到音频播放的时间点 需要注意的是这个时间在你暂停播放之后是不会再改变的 @property NSTimeInterval currentTime; // 输出设备播放音频的时间,注意如果播放中被暂停此时间也会继续累加 @property(readonly) NSTimeInterval deviceCurrentTime NS_AVAILABLE(10_7, 4_0); // 这个属性实现循环播放, 设置一个大于0的数值,可以实现循环播放N次,要是设置-1,就会无限的循环播放 @property NSInteger numberOfLoops; // 音频播放设置信息,只读 @property(readonly) NSDictionary *settings NS_AVAILABLE(10_7, 4_0); // 10.0之后的属性 @property(readonly) AVAudioFormat * format NS_AVAILABLE(10_12, 10_0); @property(getter=isMeteringEnabled) BOOL meteringEnabled; // 更新音频测量值,注意如果要更新音频测量值必须设置meteringEnabled为YES,通过音频测量值可以即时获得音频分贝等信息 - (void)updateMeters // 获得指定声道的分贝峰值,注意如果要获得分贝峰值必须在此之前调用updateMeters方法 - (float)peakPowerForChannel:(NSUInteger)channelNumber // 获得指定声道的分贝平均值,注意如果要获得分贝平均值必须在此之前调用updateMeters方法 - (float)averagePowerForChannel:(NSUInteger)channelNumber @property(nonatomic, copy, nullable) NSArray *channelAssignments AVAudioPlayerDelegate 播放代理 @optional // 成功播放到结束 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag; // if an error occurs while decoding it will be reported to the delegate. // 看上面的解释在音频解码出错的时候就会走这个方法 - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error; // 注意下面的一行解释,下面的代理方法在8.0之后被弃用了,转用AVAudioSession来代替了 AVAudioPlayer INTERRUPTION NOTIFICATIONS ARE DEPRECATED - Use AVAudioSession instead. // audioPlayerBeginInterruption: is called when the audio session has been interrupted while the player was playing. The player will have been paused. // Interruption 中断 声音播放被中断的时候就会进这个代理方法 - (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 8_0); // audioPlayerEndInterruption:withOptions: is called when the audio session interruption has ended and this player had been interrupted while playing. // 中断结束进这里代理 - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0); - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withFlags:(NSUInteger)flags NS_DEPRECATED_IOS(4_0, 6_0); // audioPlayerEndInterruption: is called when the preferred method, audioPlayerEndInterruption:withFlags:, is not implemented. - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 6_0); */
在Demo中,也是简单的把AVAudioPlayer的使用总结了一下,用它来播放我们本地的音频,当然你也可以用它播放网络音频,检测它的播放进度以及检测它的分贝值,下面是Demo的效果图,这份部分的代码你可以在Demo中的AVAudioPlayerController中找到。
AVAudioRecorder
前面说了我们的AVAudioPlayer,它是用来播放音频的话,那下面我们要总结的AVAudioRecorder就是负责来录音的类,和前面介绍AVAudioPlayer类似,我们先看看这个类的源码中都有那些方法,我们还是先介绍一个它的属性和方法,都写在代码注释中,大家仔细的看下面的代码就能了解它,等了解完之后我们在模仿一个我们录制十秒语音的简单的例子。
/* @interface AVAudioRecorder : NSObject { // 私有的 @private void *_impl; } // 下面两个是初始化的方法,和我们前面说的AVAudioPlayer大致类似,我们不再解释 The file type to create can be set through the corresponding settings key. If not set, it will be inferred from the file extension. Will overwrite a file at the specified url if a file exists. - (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError; The file type to create can be set through the corresponding settings key. If not set, it will be inferred from the file extension. Will overwrite a file at the specified url if a file exists. - (nullable instancetype)initWithURL:(NSURL *)url format:(AVAudioFormat *)format error:(NSError **)outError API_AVAILABLE(macos(10.12), ios(10.0), watchos(4.0)) API_UNAVAILABLE(tvos); // prepareToRecord 准备去记录,它的作用和前面AVAudioPlayer的也是类似的,可以看看前面的注释 methods that return BOOL return YES on success and NO on failure. - (BOOL)prepareToRecord; creates the file and gets ready to record. happens automatically on record. // 开始记录 类似与AVAudioPlayer的play方法 - (BOOL)record; start or resume recording to file. // 在将来的某个特殊的你设置的时间点开始记录 - (BOOL)recordAtTime:(NSTimeInterval)time NS_AVAILABLE_IOS(6_0); start recording at specified time in the future. time is an absolute time based on and greater than deviceCurrentTime. // 在某一段时间之后开始记录 - (BOOL)recordForDuration:(NSTimeInterval) duration; record a file of a specified duration. the recorder will stop when it has recorded this length of audio // 在某一个时间点的某一段时间之后开始记录 - (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration NS_AVAILABLE_IOS(6_0); record a file of a specified duration starting at specified time. time is an absolute time based on and greater than deviceCurrentTime. // 下面是暂停和停止的方法,这两个比较好理解 - (void)pause; pause recording - (void)stop; stops recording. closes the file. // 删除记录,调用这个方法之前必须保证 recorder 是 stop 状态 - (BOOL)deleteRecording; delete the recorded file. recorder must be stopped. returns NO on failure. // 下面是一些属性 properties // 是否在记录 @property(readonly, getter=isRecording) BOOL recording; is it recording or not? // 保存记录音频文件的URL @property(readonly) NSURL *url; URL of the recorded file // these settings are fully valid only when prepareToRecord has been called @property(readonly) NSDictionary *settings; //10.0之后的属性, AVAudioFormat 音频格式注意是只读 this object is fully valid only when prepareToRecord has been called @property(readonly) AVAudioFormat *format API_AVAILABLE(macos(10.12), ios(10.0), watchos(4.0)) API_UNAVAILABLE(tvos); // 代理 the delegate will be sent messages from the AVAudioRecorderDelegate protocol @property(assign, nullable) id delegate; // 下面的currentTime和deviceCurrentTime在前面也是解释过,按照理解AVAudioPlayer的理解就没问题 get the current time of the recording - only valid while recording @property(readonly) NSTimeInterval currentTime; get the device current time - always valid @property(readonly) NSTimeInterval deviceCurrentTime NS_AVAILABLE_IOS(6_0); // meteringEnabled 也是和AVAudioPlayer相同 // 需要注意的前面也有提过,注意这个属性以及下面两个方法之间的必要关系。 至于方法是干什么的我们不在解释,前面AVAudioPlayer也有 @property(getter=isMeteringEnabled) BOOL meteringEnabled; turns level metering on or off. default is off. - (void)updateMeters; call to refresh meter values - (float)peakPowerForChannel:(NSUInteger)channelNumber; returns peak power in decibels for a given channel - (float)averagePowerForChannel:(NSUInteger)channelNumber; returns average power in decibels for a given channel The channels property lets you assign the output to record specific channels as described by AVAudioSession's channels property This property is nil valued until set. The array must have the same number of channels as returned by the numberOfChannels property. @property(nonatomic, copy, nullable) NSArray *channelAssignments NS_AVAILABLE(10_9, 7_0); Array of AVAudioSessionChannelDescription objects // 代理 代理需要注意的地方我们不再说了。这个代理和前面AVAudioPlayer的完全类似 注意点也是类似,有不理解的可以往前面翻 @protocol AVAudioRecorderDelegate @optional audioRecorderDidFinishRecording:successfully: is called when a recording has been finished or stopped. This method is NOT called if the recorder is stopped due to an interruption. - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag; if an error occurs while encoding it will be reported to the delegate. - (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError * __nullable)error; #if TARGET_OS_IPHONE // 下面的方法也是被AVAudioSession替换掉,这个我们在下面的介绍中会说AVAudioSession这个类 AVAudioRecorder INTERRUPTION NOTIFICATIONS ARE DEPRECATED - Use AVAudioSession instead. audioRecorderBeginInterruption: is called when the audio session has been interrupted while the recorder was recording. The recorded file will be closed. - (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder NS_DEPRECATED_IOS(2_2, 8_0); audioRecorderEndInterruption:withOptions: is called when the audio session interruption has ended and this recorder had been interrupted while recording. Currently the only flag is AVAudioSessionInterruptionFlags_ShouldResume. - (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0); - (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder withFlags:(NSUInteger)flags NS_DEPRECATED_IOS(4_0, 6_0); audioRecorderEndInterruption: is called when the preferred method, audioRecorderEndInterruption:withFlags:, is not implemented. - (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder NS_DEPRECATED_IOS(2_2, 6_0); */
我们和前面一样,也在写一个Demo出来,整理一下AVAudioRecorder的使用,具体的使用大家可以看代码,在我写Demo的时候感觉有两点是需要大家注意一下的,把这两点也说一下:
1、有看到有些人说的声音小的问题,这个主要是在上面AVAudioPlayer
2、录音功能的前提想正常使用也是需要AVAudioSession
上面的这两个问题就成功的引出了我们下面还要说的类AVAudioSession。具体的代码的写法我们在这里就不在说,Demo里面是有的,当然下面总结它的时候我们也会把问题说清楚。我们接着往下在看:
AVAudioSession
AVAudioSession 我们也是需要了解的,通过它可以实现对App当前上下文音频资源的控制,比如插拔耳机、接电话、是否和其他音频数据混音等,经常我们遇到的一些问题,比如下面的这些:
1、是进行录音还是播放?
2、当系统静音键按下时该如何表现?
3、是从扬声器还是从听筒里面播放声音?
4、插拔耳机后如何表现?
5、来电话/闹钟响了后如何表现?
6、其他音频App启动后如何表现?
带着这些问题,我们来看看AVAudioSession。
/* returns singleton instance */ + (AVAudioSession*)sharedInstance;
- (BOOL)setActive:(BOOL)active error:(NSError * _Nullable *)outError;
通过上面这个方法我们就可以激活AVAudioSession,当然是设置YES激活,错误的话可以通过error的localizedDescription属性去查看。因为AVAudioSession会影响其他App的表现,当自己App的Session被激活,其他App的就会解除激活,那就有这样一个问题,如何要让自己的Session解除激活后恢复其他App Session的激活状态呢?下面这个方法能解决我们的问题:
- (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError
这里的options传入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation 即可。当然,你也可以通过otherAudioPlaying这个只读属性来提前判断当前是否有其他App在播放音频。
在AVAudioSession源码中你可以看到这个属性:
/* get session category. Examples: AVAudioSessionCategoryRecord, AVAudioSessionCategoryPlayAndRecord, etc. */ @property(readonly) NSString *category;
这个只读属性可以帮助我们获取到AVAudioSession的category,你首先不要给我们的AVAudioSession去设置category的时候,你获取一下category你就可以看到默认的category是:AVAudioSessionCategorySoloAmbien
AVAudioSession主要能控制App的哪些表现以及如何控制的呢?首先AVAudioSession将使用音频的场景分成七大类,通过设置Session为不同的类别,可以控制,下面是同行整理的这个七个category针对下面这几点做的总结,先看看是针对那些个方面总结的:
1、是否支持播放
2、是否支持录音
3、当设置“静音”或者“锁屏”的时候是否“静音”
4、当App激活Session的时候,是否会打断其他不支持混音的App声音
了解了上面说的category,我们就可以给我们的session设置category了,当然在设置之前我们还是有必要看一看我们的设备到底支持哪些category类型,通过下面这个只读属性就可以知道我们的设置支持哪些类型了:
@property(readonly) NSArray *availableCategories;
知道了我们的设备支持哪些类型的category之后,我们需要做的就是去设置了:
/* set session category */ - (BOOL)setCategory:(NSString *)category error:(NSError **)outError; /* set session category with options */ - (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0); /* set session category and mode with options */ - (BOOL)setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(10_0);
为什么这个我们单独拿出来说说呢,因为这个CategoryOptions的内容有点和category异曲同工的感觉,点击进入看一下这个:AVAudioSessionCategoryOptions 源码如下:
typedef NS_OPTIONS(NSUInteger, AVAudioSessionCategoryOptions) { /* MixWithOthers is only valid with AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute */ AVAudioSessionCategoryOptionMixWithOthers = 0x1, /* DuckOthers is only valid with AVAudioSessionCategoryAmbient, AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute */ AVAudioSessionCategoryOptionDuckOthers = 0x2 , /* AllowBluetooth is only valid with AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord */ AVAudioSessionCategoryOptionAllowBluetooth __TVOS_PROHIBITED __WATCHOS_PROHIBITED = 0x4, /* DefaultToSpeaker is only valid with AVAudioSessionCategoryPlayAndRecord */ AVAudioSessionCategoryOptionDefaultToSpeaker __TVOS_PROHIBITED __WATCHOS_PROHIBITED = 0x8, /* InterruptSpokenAudioAndMixWithOthers is only valid with AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute */ AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers NS_AVAILABLE_IOS(9_0) = 0x11, /* AllowBluetoothA2DP is only valid with AVAudioSessionCategoryPlayAndRecord */ AVAudioSessionCategoryOptionAllowBluetoothA2DP NS_AVAILABLE_IOS(10_0) = 0x20, /* AllowAirPlay is only valid with AVAudioSessionCategoryPlayAndRecord */ AVAudioSessionCategoryOptionAllowAirPlay NS_AVAILABLE_IOS(10_0) __WATCHOS_PROHIBITED = 0x40, } NS_AVAILABLE_IOS(6_0);
会不会看的有点眼花缭乱的感觉,我们这里简单的把它们之间做一个简单的总结归纳:
1、AVAudioSessionCategoryOptionMixWithOthers : 如果确实用的AVAudioSessionCategoryPlayback实现的一个背景音,但是呢,又想和QQ音乐并存,那么可以在AVAudioSessionCategoryPlayback类别下在设置这个选项,就可以实现共存了。
2、AVAudioSessionCategoryOptionDuckOthers:在实时通话的场景,比如QQ音乐,当进行视频通话的时候,会发现QQ音乐自动声音降低了,此时就是通过设置这个选项来对其他音乐App进行了压制。
3、AVAudioSessionCategoryOptionAllowBluetooth:如果要支持蓝牙耳机电话,则需要设置这个选项。
4、AVAudioSessionCategoryOptionDefaultToSpeaker: 如果在VoIP模式下,希望默认打开免提功能,需要设置这个选项。
5、AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers也是在9.0之后添加的。
6、AVAudioSessionCategoryOptionAllowBluetoothA2DP、AVAudioSessionCategoryOptionAllowAirPlay是10之后新加的,用来支持蓝牙A2DP耳机和AirPlay。
五:模式
通过上面的描述,基本上的设置是能满足我们的需求了,你再回过头去看一下我们上面说的三个设置category的方法,你会发现第三个方法里面有一个NSString类型的mode参数,有没有想过这个mode是什么?其实它又是另外的一个大的设置内容,哇,是不是觉得太多东西了,我也觉得,哈哈,这个mode的具体内容也有同行帮我们总结过了,表示感谢,我们看看下面内容:
当然设置这个模式的时候,你也可以做预判:
@property(readonly) NSArray *availableModes;
看你的设备具体支持那些mode,对mode我们也是做一个说明吧,说说有那些:
1、AVAudioSessionModeDefault 每种类别默认的就是这个模式,所有要想还原的话,就设置成这个模式。
2、AVAudioSessionModeVoiceChat 主要用于VoIP场景,此时系统会选择最佳的输入设备,比如插上耳机就使用耳机上的麦克风进行采集。此时有个副作用,他会设置类别的选项为"AVAudioSessionCategoryOptionAllowBluetooth"从而支持蓝牙耳机。
3、AVAudioSessionModeVideoChat 主要用于视频通话,比如QQ视频、FaceTime。时系统也会选择最佳的输入设备,比如插上耳机就使用耳机上的麦克风进行采集并且会设置类别的选项为"AVAudioSessionCategoryOptionAllowBluetooth" 和 "AVAudioSessionCategoryOptionDefaultToSpeaker"。
4、AVAudioSessionModeGameChat 适用于游戏App的采集和播放,比如“GKVoiceChat”对象,一般不需要手动设置
另外几种和音频APP关系不大,一般我们只需要关注VoIP或者视频通话即可。设置mode可以在我们前面说的设置category的时候一起设置,也可以利用下面的方法单独的设置:
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError
六:处理中断事件
我们要是做音视频相关的App,这个中断事件的处理就必须是我们要考虑的事情了。
AVAudioSession提供了多种Notifications来进行此类状况的通知。其中将来电话、闹铃响等都归结为一般性的中断,用AVAudioSessionInterruptionNotification来通知,其回调回来的userInfo主要包含两个键:
1、AVAudioSessionInterruptionTypeKey: 取值为AVAudioSessionInterruptionTypeBegan表示中断开始,我们应该暂停播放和采集,取值为AVAudioSessionInterruptionTypeEnded表示中断结束,我们可以继续播放和采集。
2、AVAudioSessionInterruptionOptionKey: 当前只有一种值AVAudioSessionInterruptionOptionShouldResume表示此时也应该恢复继续播放和采集。
而将其他App占据AudioSession的时候用AVAudioSessionSilenceSecondaryAudioHintNotification来进行通知。其回调回来的userInfo键为:AVAudioSessionSilenceSecondaryAudioHintTypeKey 可能包含的值如下:
1、AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App开始占据Session
2、AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App开始释放Session
在iOS设备上天啊及或者是移除音频输出后者输入线路时候,就会引起线路改变,有多重原因会导致线路的改变,比如用户插入或者拔出耳机时候就有线路的改变发生,同样的AVAudioSession会广播一个描述该变化的通知。
AVAudioSessionRouteChangeNotification 就是我们前面说的线路改变时候发出的通知。我们最后就把这个通知里面info参数AVAudioSessionRouteChangeReasonKey对应的值列举出来,也就是把改变的原因列举出来:
通过上面的这些内容,我们就对AVFoundation有了一个基本的了解,基础的东西也是《AV Foundation 开发秘籍》第一二章的大致内容就总结完了,后面的内容我们会再接着总结。
文章Demo点这里下载
第二篇也差不多总结完了,争取这两天发出来,有问题欢迎讨论!
我的博客即将同步至腾讯云+社区,邀请大家一同入驻。