IM軟體中的語音錄製與播放【iOS】
前言
自從微信推出語音聊天后,人們的通訊方式發生了巨大變化,硬是把智慧手機變成了對講機。之後也成為了各種實時通訊軟體不可或缺的功能。前一陣子微信公眾號中展開了一場“傳送語音訊息利弊”的“討論”。本文將針對語音錄製和播放的實現進行分解。
1.語音錄製動作分解
1)按下按鈕,開始錄製,顯示錄音指示介面;
2)手指上滑,暫停錄製,顯示“鬆開手指取消傳送”,如果這個時候鬆開手指,取消錄製,並不會傳送;
3)手指滑回錄製按鈕位置,繼續錄音;
4)鬆開手指,錄音完成,傳送;
5)錄製時長小於1秒,顯示時間太短,不傳送;
6)錄製時長超過60秒,自動結束錄製,並自動傳送。
2.語音錄製實現
目前,大多數實時iOS通訊軟體採用.caf格式儲存和傳送語音檔案。因為這個格式在保證聲音質量的前提下體積更小。安卓大多數採用amr格式,所以要播放安卓傳送過來的語音還需要轉碼,這個後面講。
要錄製語音,當然要用到蘋果自帶的AVFoundation中的AVAudioRecorder和AVAudioSession。關於這個框架的詳細知識不在本文的討論範圍中。需要了解的可自行搜尋。
程式碼中如何操作才可以開始錄音呢?這裡貼一段程式碼,寫了註釋。
結束錄音的核心程式碼就是呼叫AVAudioRecorder的stop方法:- (void)startRecord { AVAudioSession *audioSession = [AVAudioSession sharedInstance]; NSError *err = nil; //設定AVAudioSession [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]; if(err) { return; } //設定錄音輸入源 UInt32 doChangeDefaultRoute = 1; AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof (doChangeDefaultRoute), &doChangeDefaultRoute); err = nil; [audioSession setActive:YES error:&err]; if(err) { return; } //設定檔案儲存路徑和名稱 NSString *fileName = [NSString stringWithFormat:@"/voice-%5.2f.caf", [[NSDate date] timeIntervalSince1970] ]; self.recordPath = [self.recordPath stringByAppendingPathComponent:fileName]; NSURL *recordedFile = [NSURL fileURLWithPath:self.recordPath]; NSDictionary *dic = [self recordingSettings]; //初始化AVAudioRecorder err = nil; _recorder = [[AVAudioRecorder alloc] initWithURL:recordedFile settings:dic error:&err]; if(_recorder == nil) { return; } //準備和開始錄音 [_recorder prepareToRecord]; self.recorder.meteringEnabled = YES; [self.recorder record]; [_recorder recordForDuration:0]; if (self.levelTimer) { [self.levelTimer invalidate]; self.levelTimer = nil; } self.levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.0001 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES]; }
[self.recorder stop];
錄音結束後,開啟沙盒,找到自己設定的路徑,就可以看到以.caf字尾的語音檔案。
3.語音播放
語音播放主要用到AVFoundation中的AVAudioPlayer。程式碼中要想播放一段語音檔案,那麼首先得知道這段語音的檔案路徑。這個路徑在錄音之後需要記錄下來,然後在播放的時候拿到路徑,呼叫相關方法就可以了。又要上程式碼了,播放的核心程式碼如下:
其中的URLString就是語音檔案的路徑。_audioPlayer = [[AVAudioPlayer alloc] initWithData:audioData error:&audioPlayerError]; if (!_audioPlayer || !audioData) { [self setAudioPlayerState:LGAudioPlayerStateCancel]; return; } [[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(proximityStateChanged:) name:UIDeviceProximityStateDidChangeNotification object:nil]; _audioPlayer.volume = 1.0f; _audioPlayer.delegate = self; [_audioPlayer prepareToPlay]; [self setAudioPlayerState:LGAudioPlayerStatePlaying]; [_audioPlayer play];
那麼停止播放呢?和停止錄製一樣,呼叫stop方法
- (void)stopAudioPlayer {
if (_audioPlayer) {
_audioPlayer.playing ? [_audioPlayer stop] : nil;
_audioPlayer.delegate = nil;
_audioPlayer = nil;
[[LGAudioPlayer sharePlayer] setAudioPlayerState:LGAudioPlayerStateCancel];
}
}
4.amr檔案轉碼
前面說過,很多安卓手機發送語音採用amr格式,而amr檔案在iOS中不能被直接播放,這就需要轉碼。這裡推薦兩個amr轉wave的工具(注:轉成wave格式就可以在iOS中播放了),可以在github上搜索:
1.iOS-amr,好久沒更新了
2.amrFileCodec,也是個老程式碼
5.語音傳送
語音錄製完成之後,需要把語音訊息傳送出去。傳送語音分為兩個步驟:語音檔案上傳;語音訊息傳送。
5.1 語音檔案上傳
上傳方法當然很簡單,用AFN或者ASI就可以。這裡要說的是語音訊息的上傳機制。
語音檔案轉成二進位制資料,上傳至伺服器成功後,伺服器會返回一個檔案在伺服器的儲存“地址”,暫且把這個“地址”命名為partUrl,這個partUrl可以是一個完整的URL,也可以是URL的一部分。一般情況下,為了安全考慮,partUrl是一個URL除過協議部分和域名部分的其餘部分。例如完整的URL是“http://blog.csdn.net/gang544043963/article/details/52266903”,那麼這個partUrl就是“gang544043963/article/details/52266903”。我們拿到伺服器返回的這個partUrl之後呢,把它組裝成一條要傳送的訊息傳送出去。這樣,一個語音傳送的動作就完成了。
5.2 語音訊息下載與快取
當接收別人發來的語音訊息時,首先接收到的是不包含語音檔案的XML資料,這串資料中就包含5.1提到的partUrl。然後解析出partUrl,再用約定好的規則進行拼接,形成完整的URL,用這個URL就可以下載相應的語音檔案。
語音快取可以借鑑SDWebImage快取圖片的方法。URL中會包含‘檔名’部分,用‘檔名’作為下載要快取語音檔案的真實檔名。
6.揚聲器切換
播放語音的時候,手機貼近耳朵,自動切換聽筒播放;遠離耳朵,自動切換為揚聲器播放。這個功能實現其實很簡單,iOS系統自動檢測貼近(proximity)動作,併發送通知。我們只需要監聽這個通知,並在響應方法中切換AVAudioSession的Category。
新增監聽:
[[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(proximityStateChanged:) name:UIDeviceProximityStateDidChangeNotification object:nil];
響應方法中切換揚聲器:
- (void)proximityStateChanged:(NSNotification *)notification {
if ([[UIDevice currentDevice] proximityState] == YES) {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
}else {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
}
}
結束語
本文從程式碼角度講解了語音錄製和播放的實現,僅供入行不久的同行和想快速上手的同學參考。為方便使用和快速整合,我封裝了兩個框架,一個語音錄製,一個語音播放,放在一個Demo中,並上傳至github。歡迎使用並提出改進意見。
如果對您有幫助,請動動食指點個star鼓勵一下,謝謝!