1. 程式人生 > >IM軟體中的語音錄製與播放【iOS】

IM軟體中的語音錄製與播放【iOS】

前言

自從微信推出語音聊天后,人們的通訊方式發生了巨大變化,硬是把智慧手機變成了對講機大笑。之後也成為了各種實時通訊軟體不可或缺的功能。前一陣子微信公眾號中展開了一場“傳送語音訊息利弊”的“討論”。本文將針對語音錄製和播放的實現進行分解。

1.語音錄製動作分解

1)按下按鈕,開始錄製,顯示錄音指示介面;

2)手指上滑,暫停錄製,顯示“鬆開手指取消傳送”,如果這個時候鬆開手指,取消錄製,並不會傳送;

3)手指滑回錄製按鈕位置,繼續錄音;

4)鬆開手指,錄音完成,傳送;

5)錄製時長小於1秒,顯示時間太短,不傳送;

6)錄製時長超過60秒,自動結束錄製,並自動傳送。

2.語音錄製實現

目前,大多數實時iOS通訊軟體採用.caf格式儲存和傳送語音檔案。因為這個格式在保證聲音質量的前提下體積更小。安卓大多數採用amr格式,所以要播放安卓傳送過來的語音還需要轉碼,這個後面講。

要錄製語音,當然要用到蘋果自帶的AVFoundation中的AVAudioRecorder和AVAudioSession。關於這個框架的詳細知識不在本文的討論範圍中。需要了解的可自行搜尋。

程式碼中如何操作才可以開始錄音呢?這裡貼一段程式碼,寫了註釋。

- (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];
}
結束錄音的核心程式碼就是呼叫AVAudioRecorder的stop方法:
[self.recorder stop];

錄音結束後,開啟沙盒,找到自己設定的路徑,就可以看到以.caf字尾的語音檔案。

3.語音播放

語音播放主要用到AVFoundation中的AVAudioPlayer。程式碼中要想播放一段語音檔案,那麼首先得知道這段語音的檔案路徑。這個路徑在錄音之後需要記錄下來,然後在播放的時候拿到路徑,呼叫相關方法就可以了。又要上程式碼了,播放的核心程式碼如下:

        _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];
其中的URLString就是語音檔案的路徑。

那麼停止播放呢?和停止錄製一樣,呼叫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鼓勵一下,謝謝!