1. 程式人生 > >AVFoundation 框架初探究(一)

AVFoundation 框架初探究(一)

device 延時 dede 指定 來電 reat 基本 oops iop

技術分享圖片

夜深時動筆


前面一篇文章寫了視頻播放的幾種基本的方式,算是給這個系列開了一個頭,這裏面最想說和探究的就是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<AVAudioPlayerDelegate> 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<NSString *, id> *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<AVAudioSessionChannelDescription *> *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<NSString *, id> *)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<NSString *, id> *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<AVAudioRecorderDelegate> 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<AVAudioSessionChannelDescription *> *channelAssignments NS_AVAILABLE(10_9, 7_0); Array of AVAudioSessionChannelDescription objects



// 代理  代理需要註意的地方我們不再說了。這個代理和前面AVAudioPlayer的完全類似
   註意點也是類似,有不理解的可以往前面翻
@protocol AVAudioRecorderDelegate <NSObject>
@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

3、還有一點就是有人不理解兩個分貝有什麽用,這裏提一點,這個值可以用作的地方太多太多了,我們看到只要是隨著聲音大小改變的UI和這兩個值都緊密的關系,利用這兩個值加動畫就會有我們想要的效果

上面的這兩個問題就成功的引出了我們下面還要說的類AVAudioSession具體的代碼的寫法我們在這裏就不在說,Demo裏面是有的,當然下面總結它的時候我們也會把問題說清楚。我們接著往下在看

技術分享圖片

AVAudioSession


AVAudioSession 我們也是需要了解的,通過它可以實現對App當前上下文音頻資源的控制,比如插拔耳機、接電話、是否和其他音頻數據混音等,經常我們遇到的一些問題,比如下面的這些:

1、是進行錄音還是播放?

2、當系統靜音鍵按下時該如何表現?

3、是從揚聲器還是從聽筒裏面播放聲音?

4、插拔耳機後如何表現?

5、來電話/鬧鐘響了後如何表現?

6、其他音頻App啟動後如何表現?

帶著這些問題,我們來看看AVAudioSession。

一:首先AVAudioSession它是被寫成了一個單例的

 /* returns singleton instance */
+ (AVAudioSession*)sharedInstance;

二:激活這個AVAudioSession

- (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在播放音頻。

三: category

在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<NSString *> *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);

四: AVAudioSessionCategoryOptions

為什麽這個我們單獨拿出來說說呢,因為這個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<NSString *> *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點這裏下載

第二篇也差不多總結完了,爭取這兩天發出來,有問題歡迎討論!

AVFoundation 框架初探究(一)