最近在做的項目中,需要在iPhone上接上帶麥耳機(蘋果叫Headset,不帶麥耳機叫Headphone),然後實現同步錄音和播放。這個功能實現之後,需要改變錄音、播放的輸入源。
對於實現同步錄音和播放功能,肯定就要使用到底層的接口,用AVAudioRecorder/AVAudioPlayer是無法實現的。
我研究了iOS聲音處理的知識之後,發現自己實現太過麻煩,需要用到至少包括AudioQueue/AudioBuffer等等,然後還有各種復雜的C Struct、回調函數、回調處理等等,非常麻煩,一不小心就出錯,很難找到出錯的地方。於是我在網上找了一個比較著名的封閉好的庫,Novocaine。它對聲音的輸入、輸出都實現的比較好,也提供了很簡單明確的回調函數,我只要修改一下回調函數就可以了,這裡不表。
後面項目需要確定能否實現一種輸入/輸出方式:在插入帶麥克風的耳機時,能夠讓聲音從耳機麥克風輸入,然後同步的從手機內置揚聲器播放。
在手機插入帶麥耳機後,輸入源共有2個:內置麥克風(Build-In Microphone)、耳機麥克風(Wired Microphone);輸出源有3個:打電話時用的,在上面的那個輸出(Built-In Receiver)、耳機(Wired Headphones)和放音樂時用的內置揚聲器(Built-In Speaker)。
查了蘋果的文檔後發現,根據蘋果的產品策略,用戶在插入帶麥耳機後,會自動將輸入源切換到耳機麥克風,將輸出源切換到耳機。而且,在輸入源是耳機麥克風的時候,將輸出源切換到耳機是強制的。這句話比較繞,我舉個例子。假如現在輸入源是耳機麥克風,那麼輸入源只能是耳機。而如果將輸入源切換到手機內置麥克風(這是可以實現的)的時候,則可以將輸出源切換到手機。
蘋果認為,插入耳機代表用戶只想從耳機聽聲音。為了保護用戶的隱私,蘋果不允許開發者隨便換輸出源。也就是說,雖然蘋果提供了3種方法用於切換輸出源(都比較繁瑣,後面可能會再寫一篇博客),但在用戶插入耳機,並且我們需要輸入源是耳機麥克風的情況下,是無法將輸出源切換到內置揚聲器的。這裡有更詳細的說明:http://stackoverflow.com/questions/5931799/redirecting-audio-output-to-phone-speaker-and-mic-input-to-headphones
第二個問題是,需要在耳機插入的狀態下,將輸入源調整為手機內置麥克風,而輸出源仍然保持為耳機。
在千能(不是萬能)的AVAudioSession類裡,提供了幾個很好的方法:
打印當前正在工作的輸入/輸入源:
NSArray* input = [[AVAudioSession sharedInstance] currentRoute].inputs; NSArray* output = [[AVAudioSession sharedInstance] currentRoute].outputs; NSLog(@"current intput:%@",input); NSLog(@"current output:%@",output);
NSArray* availableInputs = [[AVAudioSession sharedInstance] availableInputs]; NSLog(@"available inputs:%@",availableInputs);
AVAudioSession沒有提供可用的輸出源的列表。
將耳機插入之後,打印可用的輸入源,結果如下:
2015-01-26 17:45:40.747 PregNotice[605:6d13] available inputs:( "", "" )可以看出,現在可用的輸入源包括內置麥克風和耳機麥克風(Wired Microphone)。我們通過以下方法改變輸入源:
NSArray* inputArray = [[AVAudioSession sharedInstance] availableInputs]; for (AVAudioSessionPortDescription* desc in inputArray) { if ([desc.portType isEqualToString:AVAudioSessionPortBuiltInMic]) { NSError* error; [[AVAudioSession sharedInstance] setPreferredInput:desc error:&error]; } }同樣,在需要切換回來時,檢查desc.portType是不是AVAudioSessionPortHeadsetMic,如果是,調整回來。
需要注意的是,使用這個方法會觸發AVAudioSessionRouteChangeNotification通知,而插入耳機後,也會調用這個通知。我在測試的時候,為了檢查耳機插入的動作,監聽了這個通知,然後在通知回調方法裡通過上面方法修改了輸入源,導致又觸發了通知,所以插入1次耳機導致了2次調用。
關於聲音,AVAudioSession,還有很多需要了解的。有興趣的同學,需要搞定iOS聲音的同學,一定要好好學學。