AVFoundation開發秘籍筆記-02播放和錄制音頻

一、音頻會話 AVAudioSession

音頻會話在應用程序和操作系統之間扮演著中間人的角色,提供一種簡單實用的方法是OS得知應用程序應該如何與iOS音頻環境進行交互。

AVAudioSessionAVFOundation框架引入。每個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

兩種方法創建AVAudioPlayerinitWithData: error:nilinitWithContentsOfURL: 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 -- 停止播放

pausestop都能停止播放,并且再次播放的時候繼續播放。區別是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。與AVAudioPlayerprepareToPlay方法類似,執行底層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

AVAudioPlayerAVAudioRecorder中最強大和最實用的功能是對音頻進行測量,Audio Metering可讓開發者讀取音頻的平均分貝和峰值分貝數據,并使這些數據以可視化方式將聲音大小呈獻給用戶。

通過averagePowerForChannel:peakPowerForChannel:獲取平均分貝和峰值分貝,返回一個用于表示聲音分貝(dB)等級的浮點值,這個值的范圍是從表示最大分貝的0dB(full scale)到最小分貝或靜音的-160dB。獲取這兩個值之前,要先設置屬性meteringEnabled為YES,才能對音頻進行測量。另,每當需要讀取值時,需要先調用updateMeters方法才能獲取最新的值。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容