一、音頻會話 AVAudioSession
音頻會話在應用程序和操作系統之間扮演著中間人的角色,提供一種簡單實用的方法是OS得知應用程序應該如何與iOS音頻環境進行交互。
AVAudioSession
有AVFOundation
框架引入。每個iOS應用程序都有自己的一個音頻會話,這個會話可以被AVAudioSession
的類方法sharedInstance
訪問。
音頻會話是一個單例對象,可以使用它來設置應用程序的音頻上下文環境,并向系統表達您的應用程序音頻行為的意圖。
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
使用它可以實現:
- 啟用或停用應用程序中的音頻工作
- 設置音頻會話類別和模式,
- 配置音頻設置,如采樣率,I/O緩沖區持續時間和通道數
- 處理音頻輸出更改
- 相應重要的音頻時間,如更改底層Media Services守護程序的可用性。
1、音頻會話分類/類別
Ambient 游戲、效率應用程序
AVAudioSessionCategoryAmbient
/kAudioSessionCategory_AmbientSound
使用這個分類應用會隨著靜音鍵和屏幕關閉而靜音,且不會終止其他應用播放的聲音,可以和其他自帶應用如iPod、Safari同時播放聲音。該類別無法在后臺播放聲音Solo Ambient(默認) 游戲、效率應用程序
AVAudioSessionCategorySoloAmbient
/kAudioSessionCategory_SoloAmbientSound
類似Ambient
不同之處在于它會終止其它應用播放聲音。該類別無法在后臺播放聲音Playback 音頻和視頻播放
AVAudioSessionCategoryPlayback
/kAudioSessionCategory_MediaPlayback
用于以音頻為主的應用,不會隨著靜音鍵和平不關閉而靜音。可在后臺播放聲音。Record 錄音機、視頻捕捉
AVAudioSessionCategoryRecord
/kAudioSessionCategory_RecordAudio
錄音應用,除了來電鈴聲、鬧鐘、日歷提醒之外的其他系統聲音不會被播放。只提供單純錄音功能。Play and Record VoIP、語音聊天
AVAudioSessionCategoryPlayAndRecord
/kAudioSessionCategory_PlayAndRecord
提供錄音和播放功能,如果應用需要用到iPhone上的聽筒,這個類別是你唯一的選擇,在這個類別下,聲音的默認出口為聽筒或者耳機。Audio Processing 離線會話和處理
AVAudioSessionCategoryAudioProcessing
/kAudioSessionCategory_AudioProcessing
在不播放或錄制音頻時使用音頻硬件編解碼器或信號處理器的類別。例如在執行離線音頻格式轉換時,此類別禁用播放和禁用錄音。應用處于后臺時,音頻處理通常不會繼續,但是可以在應用移至后臺時,請求更多時間來完成處理。Multi-Route 使用外部硬件的高級A/V應用程序
AVAudioSessionCategoryMultiRoute
通過可以用的音頻輔助設備和內置音頻硬件設備,我們可以自定義使用類型
并不是一個應用只能使用一個category,可以根據實際需求來切換設置不同的category。
通過音頻會話單例對象的setCategory: error:
設置iOS應用音頻會話類別和模式。
NSError *error;
if (![_audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) { //設置類別
NSLog(@"Category error :%@",[error localizedDescription]);
}
2、配置音頻會話
音頻會話在應用程序的生命周期中是可以修改的,一般在應用程序啟動時,對其進行配置。配置音頻會話的最佳位置就是應用程序委托的application: didFinishLaunchingWithOptions:
方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *error;
if (![audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category error :%@",[error localizedDescription]);
}
if (![audioSession setActive:YES error:&error]) {
NSLog(@"Activation Error :%@",[error localizedDescription]);
}
return YES;
}
AVAudioSession 提供了與應用程序音頻會話交互的接口,通過設置合適的的分類,可以音頻的播放指定需要的音頻會話,定制一些行為。最后告知該音頻會話激活該配置setActive:YES error:
。
- 配置可以在后臺運行:
info.plist
文件天劍一個新的Required background modes
類型的數組,在其中添加名為App plays audio or streams audio/video using AirPlay
選項。或者右擊info.plist
文件,在相應的XML部分編輯plist,以及選擇Open as Source Code
,添加下面標簽到文件底部的</dict>
前:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
兩種方式效果一樣,只是添加的方式不同。
三、使用AVAudioPlayer播放音頻
AVAudioPlayer
提供了簡單地從文本或內存中播放音頻的方法。
AVAudioPlayer
構建于Core Audio
中的C-based Audio Queue Services
的最頂層。它可以提供在Audio Queue Service
中所能找到的核心功能。除非需要從網絡流中播放音頻、需要訪問原始音頻樣本或者需要非常低的時延,否則它都能勝任。
1、創建AVAudioPlayer
兩種方法創建AVAudioPlayer
:initWithData: error:nil
和initWithContentsOfURL: error:nil
使用包含要播放音頻的內存的NSData,或者本地音頻文件的NSURL。
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"崔健-假行僧" withExtension:@"mp3"];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
if (self.audioPlayer) {
[self.audioPlayer prepareToPlay];
}
如果返回一個有效的播放實例,建議調用prepareToPlay
方法。這樣會取得需要音頻硬件并預加載Audio Queue
的緩沖區。調用這個方法是可選的,在調用play
方法時會隱性激活,不過在創建時準備播放器可以降低調用paly方法和聽到聲音輸出之間的延時。
2、對播放進行控制
常規方法:
-
play
-- 立即播放音頻 -
pause
-- 暫停播放 -
stop
-- 停止播放
pause
和stop
都能停止播放,并且再次播放的時候繼續播放。區別是stop
方法會撤銷調用prepareToPlay
是所做的設置,而調用pause
不會。
其他方法:
- 修改播放器音量:播放器的音量獨立于系統的音量,可以通過對播放器音量的處理實現一些效果,比如聲音漸隱效果。音量或播放增益定義為0.0(靜音)到1.0之間的浮點值。
- 修改播放器pan值:允許使用立體聲播放聲音,pan值范圍
-1.0(極左)-1.0(極右)
,默認是為1.0(居中) - 調整播放率:允許用戶在不改變音調的情況下調整播放率,范圍從
0.5(半速)-2.0(2倍速)
。 - 通過設置
numberOfLoops
實現音頻無縫循環:給這個屬性設置一個大于0的數,可以實現播放器n次循環播放。相反如果為-1導致播放器無限循環。音頻循環可以是未壓縮的線性PCM音頻,也可以是AAC之類的壓縮格式音頻。MP3格式片段可以實現無縫循環,但是MP3格式用作循環格式不被推崇。MP3格式的音頻要實現循環的目的通常需要使用特殊工具進行處理。如果希望使用壓縮格式的資源,建議使用AAC或者AppleLossless格式的內容。 - 進行音頻計量:播放發生時從播放器讀取播放力度的平均值和峰值。將這些數據提供給VU計量器或其他可視化元件。向用戶提供可視化的反饋效果。
三、創建Audio Looper
四、處理中斷事件
音頻會話通知
添加通知監聽,監聽是否發生中斷事件。通知名稱為AVAudioSessionInterruptionNotification
。
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
推送的消息會包含許多重要信息的userInfo字典,通過關鍵字AVAudioSessionInterruptionTypeKey
獲取中斷類型AVAudioSessionInterruptionType
,根據中斷狀態執行不同操作
- (void)handleInterruption:(NSNotification *)notification {
NSDictionary *infoDict = notification.userInfo;
AVAudioSessionInterruptionType type = [infoDict[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
[self stop];//開始中斷,停止播放
} else {
AVAudioSessionInterruptionOptions options = [infoDict[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) {
[self play];
}
}
}
如果中斷類型為AVAudioSessionInterruptionTypeEnded
,userInfo字典里會包含一個通過keyAVAudioSessionInterruptionOptionKey
取的AVAudioSessionInterruptionOptions
類型值,表示音頻會話是否已經重新激活以及是否可以再次播放。上例中,如果options為AVAudioSessionInterruptionOptionShouldResume
,可以調用播放的方法繼續播放音頻。
五、對線路改變的響應
在iOS設備上添加或移除音頻輸入、輸出線路時,會發生線路改變,有多重原因會導致線路的變化,比如插入耳機或斷開USB麥克風。當這些時間發生時,音頻會根據情況改變輸入或輸出線路,同時AVAudioSession
會廣播一個描述該變化的通知給所有相關的監聽者。
添加監聽的通知名稱:AVAudioSessionRouteChangeNotification
。該通知同樣包含一個userInfo字典,帶有相應通知發送的原因一前一個線路的描述,以此可以確定線路變化的情況。
判斷線路變更發生的原因,取keyAVAudioSessionRouteChangeReasonKey
對應的AVAudioSessionRouteChangeReason
類型值。根據變更原因,作相應處理。
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
AVAudioSessionRouteChangeReasonUnknown = 0,
原因不明;
AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
有新設備可用,如耳機插入
AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
一個舊設備不可用,如耳機拔出
AVAudioSessionRouteChangeReasonCategoryChange = 3,
音頻類別被改變,如Audio從Play back 變成Play And Record
AVAudioSessionRouteChangeReasonOverride = 4,
音頻線路(route)改變,如類別是Play and Record,輸出社誒已經從默認的接收器改變成為揚聲器
AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
設備從休眠中醒來
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
沒有路徑返回當前的類別,如Record雷彪當前沒有輸入設備
AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
當前輸入/輸出口沒變,但設置修改,如一個端口的數據選擇已經改變。
}
知道有設備斷開連接后,需要向userInfo字典提出請求,一會的其中用于描述前一個線路的AVAudioSessionRouteDescription
,其對應的key為AVAudioSessionRouteChangePreviousRouteKey
。線路的描述信息整合在一個輸入NSArray和一個輸出NSArray中。數組中的元素都是AVAudioSessionPortDescription
的實例。用于描述不同的I/O
接口屬性。可以從線路描述中找到第一個輸出接口,即前一次的接口。
輸入口不同類型,input port type
AVAudioSessionPortLineIn
AVAudioSessionPortBuiltInMic :內置麥克風
AVAudioSessionPortHeadsetMic :耳機線中的麥克風
輸出口不同類型,output port type
AVAudioSessionPortLineOut
AVAudioSessionPortHeadphones :耳機或者耳機式輸出設備
AVAudioSessionPortBuiltInReceiver :帖耳朵時候內置揚聲器(打電話的時候的聽筒)
AVAudioSessionPortBuiltInSpeaker :iOS設備的揚聲器
AVAudioSessionPortBluetoothA2DP :A2DP協議式的藍牙設備
AVAudioSessionPortHDMI :高保真多媒體接口設備
AVAudioSessionPortAirPlay :遠程AirPlay設備
AVAudioSessionPortBluetoothLE :藍牙低電量輸出設備
一個簡單實例,拔出耳機之后,默認停止播放:
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *infoDict = notification.userInfo;
AVAudioSessionRouteChangeReason reason = [infoDict[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
// AVAudioSessionRouteDescription
// AVAudioSessionPortDescription
AVAudioSessionRouteDescription *previousRoute = infoDict[AVAudioSessionRouteChangePreviousRouteKey];
//取出所有線路描述
NSLog(@"count :%zd",previousRoute.outputs.count);
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
//取出前一次線路描述
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
[self stop];
}
}
}
六、使用AVAudioRecorder錄制音頻
AVAudioRecorder
構建于Audio Queue Services
之上,可以再iOS設備上使用這個類從內置的麥克風錄制音頻,也可以從外部音頻設備進行錄制,比如數字音頻接口或USB麥克風等。
1、創建AVAudioRecorder
- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
第一個參數:音頻流寫入文件的本地文件URL,第二個參數:包含用于配置錄音會話信息,第三個參數:捕捉初始化階段錯誤。
成功創建AVAudioRecorder
實例,建議調用prepareToRecord
。與AVAudioPlayer
的prepareToPlay
方法類似,執行底層Audio Queue
初始化的必要過程。在URL參數指定位置創建一個文件,將錄制啟動時的延時降到最小。
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"1.m4a"];
NSURL *fileUrl = [NSURL fileURLWithPath:path];
NSDictionary *settings = @{
AVFormatIDKey:@(kAudioFormatMPEG4AAC),
AVSampleRateKey:@22050.0f,
AVNumberOfChannelsKey:@1,
};
NSError *error;
_audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileUrl settings:settings error:&error];
if (_audioRecorder) {
[_audioRecorder prepareToRecord];
} else {
NSLog(@"init error :%@",[error localizedDescription]);
}
配置錄音會話參數:
- AVFormatIDKey --寫入內容的音頻格式,常用的音頻格式支持的值:
kAudioFormatLinearPCM
kAudioFormatMPEG4AAC
kAudioFormatAppleLossless
kAudioFormatAppleIMA4
kAudioFormatiLBC
kAudioFormatULaw
kAudioFormatLinearPCM -會將未壓縮的音頻流寫入文件中,
這種格式的保真度最高,相應的文件也最大。
AAC或Apple IMA4的壓縮格式會顯著縮小文件,還能保證高質量的音頻內容。
- AVSampleRateKey --定義錄音器采樣率。采樣率定義了對輸入的模擬音頻信號每一秒內的采樣數。采樣率決定音頻的質量及最終文件大小。一般標準的采樣率:8k、16k、22.5k、44.1k。
- AVNumbeOfChannelsKey -- 定義記錄音頻通道數。默認值1,單聲道錄制。設置2-立體聲錄制。除非使用外部硬件進行錄制,一般應該創建單聲道錄音。
2、控制錄音過程
record
--開始或繼續錄音
stop
--停止錄音,并關閉文件
pause
--暫停錄音
七、使用Audio Metering
AVAudioPlayer
和AVAudioRecorder
中最強大和最實用的功能是對音頻進行測量,Audio Metering
可讓開發者讀取音頻的平均分貝和峰值分貝數據,并使這些數據以可視化方式將聲音大小呈獻給用戶。
通過averagePowerForChannel:
和peakPowerForChannel:
獲取平均分貝和峰值分貝,返回一個用于表示聲音分貝(dB)等級的浮點值,這個值的范圍是從表示最大分貝的0dB(full scale)到最小分貝或靜音的-160dB。獲取這兩個值之前,要先設置屬性meteringEnabled
為YES,才能對音頻進行測量。另,每當需要讀取值時,需要先調用updateMeters
方法才能獲取最新的值。