簡(jiǎn)介
最近公司研發(fā)了一個(gè)語(yǔ)音識(shí)別的框架,但這個(gè)框架是后端識(shí)別,所以需要手機(jī)端錄音,錄音后將音頻文件通轉(zhuǎn)成NSData類型,然后通過(guò)bsae64轉(zhuǎn)碼傳給后臺(tái)進(jìn)行語(yǔ)音識(shí)別,識(shí)別的文字再傳回給前端,雖然我認(rèn)為這樣做體驗(yàn)并不好,畢竟請(qǐng)求是耗時(shí)的,并且iOS10以后,自帶語(yǔ)音識(shí)別框架,但畢竟是公司研發(fā)的框架,所以就配合做了一下測(cè)試。
iOS錄音和音頻播放用到時(shí)AVFoundation.h框架中的,AVAudioRecorder類和AVAudioPlayer,其中AVAudioPlayer播放器只能播放本地音頻文件,如果要在線播放需要用AVPlayer,其配合AVPlayerLayer類還可實(shí)現(xiàn)視頻播放。另外支持播放進(jìn)度監(jiān)聽(tīng)。
AVAudioRecorder錄音
AVAudioRecorder錄音的主要步驟:
創(chuàng)建錄音文件保存路徑,并設(shè)置相關(guān)屬性(是一個(gè)字典),然后實(shí)例化AVAudioRecorder錄音對(duì)象,該對(duì)象必須是全局變量。
使用AVAudioSession的單例對(duì)象(音頻會(huì)話對(duì)象)來(lái)設(shè)置當(dāng)前音頻會(huì)話狀態(tài)和會(huì)話類別。
錄音對(duì)象設(shè)置開(kāi)始錄音,錄音結(jié)束后會(huì)調(diào)用錄音完成的代理方法。
//導(dǎo)入頭文件
#import <AVFoundation/AVFoundation.h>
#import "GTMBase64.h"
@interface ViewController ()<AVAudioRecorderDelegate,AVAudioPlayerDelegate>{
? ? NSURL * recordUrl;
? ? AVAudioRecorder * audioRecorder;//必須為全局變量
? ? AVAudioPlayer * player;//必須為全局變量
? ? }
//開(kāi)始錄音
- (void)startRecord
{
? ? //刪除上次生成的文件,保留最新文件
? ? NSFileManager *fileManager = [NSFileManager defaultManager];
? ? //默認(rèn)就是wav格式,是無(wú)損的
? ? if ([fileManager fileExistsAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]]) {
? ? ? ? [fileManager removeItemAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"] error:nil];
? ? }
? ? //錄音設(shè)置
? ? NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
? ? //設(shè)置錄音格式 AVFormatIDKey==kAudioFormatLinearPCM
? ? [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
? ? //設(shè)置錄音采樣率(Hz) 如:AVSampleRateKey==8000/44100/96000(影響音頻的質(zhì)量), 采樣率必須要設(shè)為11025才能使轉(zhuǎn)化成mp3格式后不會(huì)失真
? ? [recordSetting setValue:[NSNumber numberWithFloat:16000] forKey:AVSampleRateKey];
? ? //錄音通道數(shù) 1 或 2 ,要轉(zhuǎn)換成mp3格式必須為雙通道
? ? [recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
? ? //線性采樣位數(shù) 8、16、24、32
? ? [recordSetting setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
? ? //錄音的質(zhì)量
? ? [recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityHigh] forKey:AVEncoderAudioQualityKey];
? ? // 設(shè)置錄制音頻采用高位優(yōu)先的記錄格式
? ? [recordSetting setValue:[NSNumber numberWithBool:YES] forKey:AVLinearPCMIsBigEndianKey];
? ? // 設(shè)置采樣信號(hào)采用浮點(diǎn)數(shù)
? ? [recordSetting setValue:[NSNumber numberWithBool:YES] forKey:AVLinearPCMIsFloatKey];
? ? //存儲(chǔ)錄音文件
? ? NSURL * recordUrl = [NSURL URLWithString:[NSTemporaryDirectory()stringByAppendingPathComponent:@"recordAudio.wav"]];
? ? //初始化錄音對(duì)象
? ? NSError * error;
? ? audioRecorder = [[AVAudioRecorder alloc] initWithURL:recordUrl settings:recordSetting error:&error];
? ? if (error) {
? ? ? ? NSLog(@"%@",error.description);
? ? ? ? return;
? ? }
? ? audioRecorder.delegate = self;
? ? //開(kāi)啟音量檢測(cè)
? ? audioRecorder.meteringEnabled = YES;
? ? AVAudioSession * audioSession = [AVAudioSession sharedInstance];//得到音頻會(huì)話單例對(duì)象
? ? //如果不是正在錄音
? ? if (![audioRecorder isRecording]) {
? ? ? ? [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];//設(shè)置類別,表示該應(yīng)用同時(shí)支持播放和錄音
? ? ? ? [audioSession setActive:YES error:nil];//激活當(dāng)前應(yīng)用的音頻會(huì)話,此時(shí)會(huì)阻斷后臺(tái)音樂(lè)的播放.
? ? ? ? [audioRecorder prepareToRecord];//準(zhǔn)備錄音
? ? ? ? [audioRecorder record];//開(kāi)始錄音
? ? ? ? //暫停錄音
//? ? ? ? [audioRecorder pause];
? ? }
}
//結(jié)束錄音
- (void)endRecord
{
? ? [audioRecorder stop];? ? ? ? ? ? //錄音停止
}
//錄音結(jié)束后代理
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
? ? [[AVAudioSession sharedInstance] setActive:NO error:nil];//一定要在錄音停止以后再關(guān)閉音頻會(huì)話管理(否則會(huì)報(bào)錯(cuò)),此時(shí)會(huì)延續(xù)后臺(tái)音樂(lè)播放
? ? if (!flag) return;
? ? //請(qǐng)求接口傳給后臺(tái)
? ? [self postwavdata];
}
注意:
錄音時(shí)音頻文件類型后綴必須為.caf、.aif、.wav中的一種,不能是.mp3,但播放是可以播放.mp3文件,可以導(dǎo)入githu上的一個(gè)C語(yǔ)言框架lame轉(zhuǎn)成mp3音頻文件。
將音頻文件轉(zhuǎn)成NSData然后用bsae64加密傳給服務(wù)器,用系統(tǒng)自帶的base64EncodedStringWithOptions編碼方法后臺(tái)可能解析有問(wèn)題,主要是有空格和換行,可以多encode一次轉(zhuǎn)義去掉這些特殊符號(hào),也可以用第三方框架GTMBase64,請(qǐng)求數(shù)據(jù)時(shí)候request的Content-Type要設(shè)置為application/json,否則后臺(tái)可能結(jié)束不到音頻轉(zhuǎn)碼后端參數(shù)。
//設(shè)置request的Content-Type為application/json
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
//轉(zhuǎn)成base64編碼格式上傳服務(wù)器
-(NSString *)wavToBASE64{
NSData * wavData = [NSData dataWithContentsOfFile:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]];
NSString * encodedImageStr = [GTMBase64 encodeBase64Data:wavData];
}
AVAudioPlayer播放音頻文件。
AVAudioPlayer播放音頻文件主要步驟:
獲取音頻文件URL或者音頻文件數(shù)據(jù);
初始化AVAudioPlayer音頻播放器全局對(duì)象;
設(shè)置相關(guān)屬性,并播放文件。
相關(guān)代碼
————————————————
//導(dǎo)入頭文件
#import <AVFoundation/AVFoundation.h>
#import "GTMBase64.h"
@interface ViewController ()<AVAudioRecorderDelegate,AVAudioPlayerDelegate>{
? ? NSURL * recordUrl;
? ? AVAudioRecorder * audioRecorder;//必須為全局變量
? ? AVAudioPlayer * player;//必須為全局變量
? ? }
//開(kāi)始錄音
- (void)startRecord
{
? ? //刪除上次生成的文件,保留最新文件
? ? NSFileManager *fileManager = [NSFileManager defaultManager];
? ? //默認(rèn)就是wav格式,是無(wú)損的
? ? if ([fileManager fileExistsAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]]) {
? ? ? ? [fileManager removeItemAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"] error:nil];
? ? }
? ? //錄音設(shè)置
? ? NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
? ? //設(shè)置錄音格式 AVFormatIDKey==kAudioFormatLinearPCM
? ? [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
? ? //設(shè)置錄音采樣率(Hz) 如:AVSampleRateKey==8000/44100/96000(影響音頻的質(zhì)量), 采樣率必須要設(shè)為11025才能使轉(zhuǎn)化成mp3格式后不會(huì)失真
? ? [recordSetting setValue:[NSNumber numberWithFloat:16000] forKey:AVSampleRateKey];
? ? //錄音通道數(shù) 1 或 2 ,要轉(zhuǎn)換成mp3格式必須為雙通道
? ? [recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
? ? //線性采樣位數(shù) 8、16、24、32
? ? [recordSetting setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
? ? //錄音的質(zhì)量
? ? [recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityHigh] forKey:AVEncoderAudioQualityKey];
? ? // 設(shè)置錄制音頻采用高位優(yōu)先的記錄格式
? ? [recordSetting setValue:[NSNumber numberWithBool:YES] forKey:AVLinearPCMIsBigEndianKey];
? ? // 設(shè)置采樣信號(hào)采用浮點(diǎn)數(shù)
? ? [recordSetting setValue:[NSNumber numberWithBool:YES] forKey:AVLinearPCMIsFloatKey];
? ? //存儲(chǔ)錄音文件
? ? NSURL * recordUrl = [NSURL URLWithString:[NSTemporaryDirectory()stringByAppendingPathComponent:@"recordAudio.wav"]];
? ? //初始化錄音對(duì)象
? ? NSError * error;
? ? audioRecorder = [[AVAudioRecorder alloc] initWithURL:recordUrl settings:recordSetting error:&error];
? ? if (error) {
? ? ? ? NSLog(@"%@",error.description);
? ? ? ? return;
? ? }
? ? audioRecorder.delegate = self;
? ? //開(kāi)啟音量檢測(cè)
? ? audioRecorder.meteringEnabled = YES;
? ? AVAudioSession * audioSession = [AVAudioSession sharedInstance];//得到音頻會(huì)話單例對(duì)象
? ? //如果不是正在錄音
? ? if (![audioRecorder isRecording]) {
? ? ? ? [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];//設(shè)置類別,表示該應(yīng)用同時(shí)支持播放和錄音
? ? ? ? [audioSession setActive:YES error:nil];//激活當(dāng)前應(yīng)用的音頻會(huì)話,此時(shí)會(huì)阻斷后臺(tái)音樂(lè)的播放.
? ? ? ? [audioRecorder prepareToRecord];//準(zhǔn)備錄音
? ? ? ? [audioRecorder record];//開(kāi)始錄音
? ? ? ? //暫停錄音
//? ? ? ? [audioRecorder pause];
? ? }
}
//結(jié)束錄音
- (void)endRecord
{
? ? [audioRecorder stop];? ? ? ? ? ? //錄音停止
}
//錄音結(jié)束后代理
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
? ? [[AVAudioSession sharedInstance] setActive:NO error:nil];//一定要在錄音停止以后再關(guān)閉音頻會(huì)話管理(否則會(huì)報(bào)錯(cuò)),此時(shí)會(huì)延續(xù)后臺(tái)音樂(lè)播放
? ? if (!flag) return;
? ? //請(qǐng)求接口傳給后臺(tái)
? ? [self postwavdata];
}
注意:
錄音時(shí)音頻文件類型后綴必須為.caf、.aif、.wav中的一種,不能是.mp3,但播放是可以播放.mp3文件,可以導(dǎo)入githu上的一個(gè)C語(yǔ)言框架lame轉(zhuǎn)成mp3音頻文件。
將音頻文件轉(zhuǎn)成NSData然后用bsae64加密傳給服務(wù)器,用系統(tǒng)自帶的base64EncodedStringWithOptions編碼方法后臺(tái)可能解析有問(wèn)題,主要是有空格和換行,可以多encode一次轉(zhuǎn)義去掉這些特殊符號(hào),也可以用第三方框架GTMBase64,請(qǐng)求數(shù)據(jù)時(shí)候request的Content-Type要設(shè)置為application/json,否則后臺(tái)可能結(jié)束不到音頻轉(zhuǎn)碼后端參數(shù)。
————————————————
//設(shè)置request的Content-Type為application/json
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
//轉(zhuǎn)成base64編碼格式上傳服務(wù)器
-(NSString *)wavToBASE64{
NSData * wavData = [NSData dataWithContentsOfFile:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]];
NSString * encodedImageStr = [GTMBase64 encodeBase64Data:wavData];
}
AVAudioPlayer播放音頻文件。
AVAudioPlayer播放音頻文件主要步驟:
獲取音頻文件URL或者音頻文件數(shù)據(jù);
初始化AVAudioPlayer音頻播放器全局對(duì)象;
設(shè)置相關(guān)屬性,并播放文件。
相關(guān)代碼
//開(kāi)始播放音頻文件
- (void)playWav{
//獲取音頻文件url
//? NSURL * url = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]];
//獲取錄音數(shù)據(jù)
NSData * wavData = [NSData dataWithContentsOfFile:[NSTemporaryDirectory() stringByAppendingPathComponent:@"recordAudio.wav"]];
NSError * error;
//? ? AVAudioPlayer * player = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
? player = [[AVAudioPlayer alloc]initWithData:wavData error:&error];
? player.delegate = self;
? if (error) {
? ? ? NSLog(@"語(yǔ)音播放失敗,%@",error);
? ? ? return;
? }
? ? //播放器的聲音會(huì)自動(dòng)切到receiver,所以聽(tīng)起來(lái)特別小,如果需要從speaker出聲,需要自己設(shè)置。
? [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
? // 單獨(dú)設(shè)置音樂(lè)的音量(默認(rèn)1.0,可設(shè)置范圍為0.0至1.0,兩個(gè)極端為靜音、系統(tǒng)音量):
? player.volume = 1.0;
//? ? 修改左右聲道的平衡(默認(rèn)0.0,可設(shè)置范圍為-1.0至1.0,兩個(gè)極端分別為只有左聲道、只有右聲道):
? player.pan = -1;
//? ? 設(shè)置播放速度(默認(rèn)1.0,可設(shè)置范圍為0.5至2.0,兩個(gè)極端分別為一半速度、兩倍速度):
? player.rate = 2.0;
//? ? 設(shè)置循環(huán)播放(默認(rèn)1,若設(shè)置值大于0,則為相應(yīng)的循環(huán)次數(shù),設(shè)置為-1可以實(shí)現(xiàn)無(wú)限循環(huán)):
? player.numberOfLoops = 0;
//? ? player.currentTime = 0;
? //調(diào)用prepareToPlay方法,這樣可以提前獲取需要的硬件支持,并加載音頻到緩沖區(qū)。在調(diào)用play方法時(shí),減少開(kāi)始播放的延遲。
? [player prepareToPlay];
//? ? 開(kāi)始播放音樂(lè):
? [player play];
? }
//播放完成代理
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
? if (flag) {
? ? ? NSLog(@"停止播放");
? ? ? //調(diào)用pause或stop來(lái)暫停播放,這里的stop方法的效果也只是暫停播放,不同之處是stop會(huì)撤銷prepareToPlay方法所做的準(zhǔn)備。
? ? ? [player stop];
? ? ? player = nil;
? }
}
最后
給大家推薦一個(gè)優(yōu)秀的iOS交流平臺(tái),平臺(tái)里的伙伴們都是非常優(yōu)秀的iOS開(kāi)發(fā)人員,我們專注于技術(shù)的分享與技巧的交流,大家可以在平臺(tái)上討論技術(shù),交流學(xué)習(xí)。歡迎大家的加入(想要進(jìn)入的可加小編微信1367798518)