iOS的AVFoundation框架中的AVAudioRecorder和AVAudioPlayer可以實現(xiàn)語音的錄制和播放功能demo下載
AVAudioRecorder
AVAudioRecorder 的初始化方法是
- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
- url: 錄制的語音文件保存的路徑,文件的類型是由這個參數(shù)值的file extension推測的。
- settings: 對audio recored的設(shè)置。在iOS7中,默認(rèn)的設(shè)置是
{ AVFormatIDKey = 1819304813;
AVLinearPCMBitDepthKey = 16;
AVLinearPCMIsBigEndianKey = 0;
AVLinearPCMIsFloatKey = 0;
AVLinearPCMIsNonInterleaved = 0;
AVNumberOfChannelsKey = 2;
AVSampleRateKey = 44100;}
下面的三個key適用于所有的語音格式:
AVFormatIDKey:格式的標(biāo)識,表示音頻文件的格式,它對應(yīng)的是個枚舉值。
Use these identifiers to test for the presence of audio codecs on a system. If a given codec is present, you can use its identifier to specify that codec for data encoding or decoding, according to the capabilities of the codec. For more information, see Core Audio Overview.
CF_ENUM(AudioFormatID)
{
kAudioFormatLinearPCM = 'lpcm',
kAudioFormatAC3 = 'ac-3',
kAudioFormat60958AC3 = 'cac3',
kAudioFormatAppleIMA4 = 'ima4',
kAudioFormatMPEG4AAC = 'aac ',
kAudioFormatMPEG4CELP = 'celp',
kAudioFormatMPEG4HVXC = 'hvxc',
kAudioFormatMPEG4TwinVQ = 'twvq',
kAudioFormatMACE3 = 'MAC3',
kAudioFormatMACE6 = 'MAC6',
kAudioFormatULaw = 'ulaw',
kAudioFormatALaw = 'alaw',
kAudioFormatQDesign = 'QDMC',
kAudioFormatQDesign2 = 'QDM2',
kAudioFormatQUALCOMM = 'Qclp',
kAudioFormatMPEGLayer1 = '.mp1',
kAudioFormatMPEGLayer2 = '.mp2',
kAudioFormatMPEGLayer3 = '.mp3',
kAudioFormatTimeCode = 'time',
kAudioFormatMIDIStream = 'midi',
kAudioFormatParameterValueStream = 'apvs',
kAudioFormatAppleLossless = 'alac',
kAudioFormatMPEG4AAC_HE = 'aach',
kAudioFormatMPEG4AAC_LD = 'aacl',
kAudioFormatMPEG4AAC_ELD = 'aace',
kAudioFormatMPEG4AAC_ELD_SBR = 'aacf',
kAudioFormatMPEG4AAC_ELD_V2 = 'aacg',
kAudioFormatMPEG4AAC_HE_V2 = 'aacp',
kAudioFormatMPEG4AAC_Spatial = 'aacs',
kAudioFormatAMR = 'samr',
kAudioFormatAMR_WB = 'sawb',
kAudioFormatAudible = 'AUDB',
kAudioFormatiLBC = 'ilbc',
kAudioFormatDVIIntelIMA = 0x6D730011,
kAudioFormatMicrosoftGSM = 0x6D730031,
kAudioFormatAES3 = 'aes3',
kAudioFormatEnhancedAC3 = 'ec-3'
};
AVSampleRateKey: 抽樣率,單位時間內(nèi)的抽樣數(shù)。44.1kHZ和標(biāo)準(zhǔn)的CD Audio是相同的,除非你需要一個高保真的錄音,你不需要這樣高的采樣率,大部分的音頻軟件只能特定的速率像32KHZ,24KHZ,16KHZ,12KHZ.8KHZ是電話采樣率,對一般的錄音已經(jīng)足夠了。
AVNumberOfChannelsKey:通道數(shù)。設(shè)成2的話是雙聲道。iPhone只有一個麥克風(fēng),一個單聲道的通道足夠了,它把你的數(shù)據(jù)需求削減了一半。
AVLinearPCMBitDepthKey:位寬。抽樣后的數(shù)值用二進(jìn)制表示,這個值表示二進(jìn)制的位數(shù)。值可以是 8, 16, 24, or 32。
參考
開始錄音的代碼:
NSString * url = NSTemporaryDirectory();
url = [url stringByAppendingString:[NSString stringWithFormat:@"%f.wav", [[NSDate date] timeIntervalSince1970]]];
NSMutableDictionary * settings = @{}.mutableCopy;
[settings setObject:[NSNumber numberWithFloat:8000.0] forKey:AVSampleRateKey];
[settings setObject:[NSNumber numberWithInt: kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[settings setObject:@1 forKey:AVNumberOfChannelsKey];//設(shè)置成一個通道,iPnone只有一個麥克風(fēng),一個通道已經(jīng)足夠了
[settings setObject:@16 forKey:AVLinearPCMBitDepthKey];//采樣的位數(shù)
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:url] settings:settings error:&error];
self.audioRecorder.delegate = self;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
self.audioRecorder.meteringEnabled = YES;
BOOL success = [self.audioRecorder record];
if (success) {
NSLog(@"錄音開始成功");
}else{
NSLog(@"錄音開始失敗");
}
遇到的問題
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
這個是設(shè)置AVAudioSession的category的,如果不設(shè)置的話,在模擬器上success是YES,但是在真機(jī)上是NO。同樣,在用AVAudioPlayer播放語音時要設(shè)置category為AVAudioSessionCategoryPlayback。
如果把setting里的AVFormatIDKey值改為kAudioFormatMPEGLayer3,則url的后綴也要改成.mp3,否則初始化AVAudioRecorder實例會失敗。
指定的音頻格式一定要和文件寫入的URL文件類型保持一致。如果錄制.wav文件格式,AVFormatIDKey指定的值不是kAudioFormatLinearPCM則會發(fā)生錯誤。NSError 會返回如下錯誤
The operation couldn’t be completed. (OSState error 1718449215.)
iOS 4.3以后不支持amr格式的錄制和播放了,所以使用kAudioFormatAMR因為無法錄制amr格式的語音。
停止錄音
[self.audioRecorder stop];
調(diào)用這個方法后,會走下面的代理方法
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
NSURL * url = recorder.url;
}
獲取錄音過程中的分貝
要在錄音過程中獲取分貝數(shù),要在錄音前先把AVAudioRecorder的屬性meteringEnabled設(shè)置成YES.
在錄音開始后,可以設(shè)置一個定時器獲取分貝數(shù)
_metesTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(setVoiceImage) userInfo:nil repeats:YES];
-(void)setVoiceImage{
if (self.audioRecorder.isRecording) {
[self.audioRecorder updateMeters];
float peakPower = [self.audioRecorder peakPowerForChannel:0];
NSLog(@"%f", peakPower);
}
}
AVAudioRecorder的averagePowerForChannel和peakPowerForChannel方法返回的是分貝數(shù)據(jù),數(shù)值在-160 – 0之間
播放語音
可以將錄音后獲取的url傳入下面的方法中:
- (void)playAudioWithURL:(NSURL *)url{
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
NSError * error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
self.audioPlayer.delegate = self;
BOOL success = [self.audioPlayer play];
if (success) {
NSLog(@"播放成功");
}else{
NSLog(@"播放失敗");
}
}
我在demo里封裝了一個ZMAudioManager的類,方便進(jìn)行語音的播放和錄制:
開始錄音
[[ZMAudioManager shareInstance] startRecordingWithFileName:[NSString stringWithFormat:@"%f.wav", [[NSDate date] timeIntervalSince1970]] completion:^(NSError *error) {
if (error) {
else{
}
}];
停止錄音,recordPath是錄音文件存放的路徑,aDuration是錄音時長
[[ZMAudioManager shareInstance] stopRecordingWithType:ZMAudioRecordeAMRType completion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
if (error) {
UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"error" message:error.domain delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];
[a show];
}else{
}];
播放錄音,audioPath是錄音文件存放的路徑,錄音播放完后會走completion回調(diào)
[[ZMAudioManager shareInstance] playAudioWithPath:audioPath completion:^(NSError *error) {
}];
在實際開發(fā)中,考慮到要和android之間通信,android支持amr,不支持wav;iOS支持wav,不支持amr。通常iOS客戶端會進(jìn)行amr和wav的互相轉(zhuǎn)換。
在demo里EMVoiceConverter文件包含了wav和amr互相轉(zhuǎn)換的方法:
+ (int)amrToWav:(NSString*)_amrPath wavSavePath:(NSString*)_savePath;
+ (int)wavToAmr:(NSString*)_wavPath amrSavePath:(NSString*)_savePath;
基礎(chǔ)知識
聲音是物體震動發(fā)出的聲波。聲音的頻率是值單位時間內(nèi)(每秒鐘)物體震動的次數(shù),或者說頻率是每秒經(jīng)過一給定點的聲波數(shù)量。
人耳所能聽到的聲音,最低的頻率是從20Hz起一直到最高頻率20KHZ,因此音頻文件格式的最大帶寬是20KHZ。根據(jù)奈奎斯特的理論,只有采樣頻率高于聲音信號最高頻率的兩倍時,才能把數(shù)字信號表示的聲音還原成為原來的聲音,所以音頻文件的采樣率一般在40~50KHZ,比如最常見的CD音質(zhì)采樣率44.1KHZ。
音頻的采集過程主要通過設(shè)備將環(huán)境中的模擬信號采集成 PCM (Pulse Code Modulation)(脈沖編碼調(diào)制)編碼的原始數(shù)據(jù),然后編碼壓縮成 MP3 等格式的數(shù)據(jù)分發(fā)出去。常見的音頻壓縮格式有:MP3,AAC,OGG,WMA,Opus,F(xiàn)LAC,APE,m4a 和 AMR 等
PCM編碼
PCM編碼主要過程是將話音、圖像等模擬信號每隔一定時間進(jìn)行取樣,使其離散化,同時將抽樣值按分層單位四舍五入取整量化,同時將抽樣值按一組二進(jìn)制碼來表示抽樣脈沖的幅值。
也就是說,PCM對模擬信號進(jìn)行了抽樣、量化和編碼三個過程。
PCM數(shù)據(jù)是最原始的音頻數(shù)據(jù)完全無損,所以PCM數(shù)據(jù)雖然音質(zhì)優(yōu)秀但體積龐大,為了解決這個問題先后誕生了一系列的音頻格式,這些音頻格式運用不同的方法對音頻數(shù)據(jù)進(jìn)行壓縮,其中有無損壓縮(ALAC、APE、FLAC)和有損壓縮(MP3、AAC、OGG、WMA)兩種。
目前最為常用的音頻格式是MP3,MP3是一種有損壓縮的音頻格式,設(shè)計這種格式的目的就是為了大幅度的減小音頻的數(shù)據(jù)量,它舍棄PCM音頻數(shù)據(jù)中人類聽覺不敏感的部分,
抽樣頻率(Sampling Rate):單位時間內(nèi)采集的樣本數(shù)。采樣頻率必須至少是信號中最大頻率分量頻率的兩倍,否則就不能從信號采樣中恢復(fù)原始信號,這其實就是著名的香農(nóng)采樣定理。CD音質(zhì)采樣率為 44.1 kHz,其他常用采樣率:22.05KHz,11.025KHz,一般網(wǎng)絡(luò)和移動通信的音頻采樣率:8KHz。聲音的頻率是每秒經(jīng)過一給定點的聲波數(shù)量。
位寬(bit depth):
每一個采樣點都需要用一個數(shù)值來表示大小,這個數(shù)值的數(shù)據(jù)類型大小可以是:4bit、8bit、16bit、32bit 等等,位數(shù)越多,表示得就越精細(xì),聲音質(zhì)量自然就越好,而數(shù)據(jù)量也會成倍增大。我們在音頻采樣過程中常用的位寬是 8bit 或者 16bit;聲道數(shù)(channels):
由于音頻的采集和播放是可以疊加的,因此,可以同時從多個音頻源采集聲音,并分別輸出到不同的揚聲器,故聲道數(shù)一般表示聲音錄制時的音源數(shù)量或回放時相應(yīng)的揚聲器數(shù)量。聲道數(shù)為 1 和 2 分別稱為單聲道和雙聲道,是比較常見的聲道參數(shù);音頻幀(frame):
音頻跟視頻很不一樣,視頻每一幀就是一張圖像,而從上面的正玄波可以看出,音頻數(shù)據(jù)是流式的,本身沒有明確的一幀幀的概念,在實際的應(yīng)用中,為了音頻算法處理/傳輸?shù)姆奖?,一般約定俗成取 2.5ms~60ms 為單位的數(shù)據(jù)量為一幀音頻。這個時間被稱之為“采樣時間”,其長度沒有特別的標(biāo)準(zhǔn),它是根據(jù)編解碼器和具體應(yīng)用的需求來決定的
根據(jù)以上定義,我們可以計算一下一幀音頻幀的大小。
假設(shè)某音頻信號是采樣率為 8kHz、雙通道、位寬為 16bit,20ms 一幀,則一幀音頻數(shù)據(jù)的大小為:
1size = 8000 x 2 x 16bit x 0.02s = 5120 bit = 640 byte比特率(bit rate):表示經(jīng)過編碼(壓縮)后的音頻數(shù)據(jù)每秒鐘需要用多少個比特來表示,單位常為kbps。
AVAudioSession

Responding to Audio Session Interruptions
如果你在音樂應(yīng)用中播放音樂并收到電話或FaceTime請求時,應(yīng)用的音頻播放會暫停。如果拒絕來電或請求,則控制返回到應(yīng)用程序,音頻再次開始播放。
你可以直接觀察被AVAudioSession發(fā)出的終端通知:
- (void)setupNotifications{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
}
- (void)handleInterruption:(NSNotification *)notification{
if (notification.userInfo[AVAudioSessionInterruptionTypeKey]) {
AVAudioSessionInterruptionType type = [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
//中斷開始
}else if (type == AVAudioSessionInterruptionTypeEnded){
//中斷結(jié)束
if (notification.userInfo[AVAudioSessionInterruptionOptionKey]) {
AVAudioSessionInterruptionOptions option = [notification.userInfo[AVAudioSessionInterruptionOptionKey] intValue];
if (option == AVAudioSessionInterruptionOptionShouldResume) {
//中斷結(jié)束,播放將恢復(fù)
}else{
//中斷結(jié)束,播放不會結(jié)束
}
}
}
}
}
Observe for Route Change Notifications
AVAudioSession的一項重要職責(zé)是管理audio route changes。將音頻輸入或輸出添加到iOS設(shè)備或從iOS設(shè)備中移除時, route change 會發(fā)生。route change的行為包括插入一副耳機(jī),連接藍(lán)牙耳機(jī)或拔下USB音頻接口。當(dāng)放生這些route change時,AVAudioSession將相應(yīng)地重新傳送音頻信號,并向任何注冊的觀察者發(fā)送包含更改細(xì)節(jié)的通知。
- (void)setupNotifications{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionRouteChangeNotification object:nil];
}
- (void)handleInterruption:(NSNotification *)notification{
BOOL headphonesConnected;
if (notification.userInfo[AVAudioSessionRouteChangeReasonKey]) {
AVAudioSessionRouteChangeReason reason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] intValue];
switch (reason) {
//插入耳機(jī)
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:{
AVAudioSession *session = [AVAudioSession sharedInstance];
for (AVAudioSessionPortDescription * output in session.currentRoute.outputs) {
if (output.portType == AVAudioSessionPortHeadphones) {
headphonesConnected = YES;
break;
}
}
}
break;
//拔出耳機(jī)
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:{
AVAudioSessionRouteDescription * previousRoute = notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
for (AVAudioSessionPortDescription * output in previousRoute.outputs) {
if (output.portType == AVAudioSessionPortHeadphones) {
headphonesConnected = false;
break;
}
}
}
break;
default:
break;
}
}
}
active
setActive: withOptions:error:nil
當(dāng)你的app deactive自己的AudioSession時系統(tǒng)會通知上一個被中斷播放app中斷已經(jīng)結(jié)束。如果你的app在deactive時傳入了AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation參數(shù),那么其他app在接到打斷結(jié)束回調(diào)時會多得到一個參數(shù)kAudioSessionInterruptionType_ShouldResume否則就是AVAudioSessionInterruptionOptionShouldResume,根據(jù)參數(shù)的值可以決定是否繼續(xù)播放。
大概流程是這樣的:
- 一個音樂軟件A正在播放;
- 用戶打開你的軟件播放對話語音,AudioSession active;
- 音樂軟件A音樂被打斷并收到InterruptBegin事件;
對話語音播放結(jié)束,AudioSession deactive并且傳入NotifyOthersOnDeactivation參數(shù); - 音樂軟件A收到InterruptEnd事件,查看Resume參數(shù),如果是ShouldResume控制音頻繼續(xù)播放,如果是ShouldNotResume就維持打斷狀態(tài);