iOS多媒体音频(下)-录音及其播放的实例
上一篇中总结了iOS中音效和音频播放的最基本使用方法,其中音频的播放控制是使用AVFoundation.framework框架中的AVAudioPlayer播放器对象来实现的,而这里音频的录制则是使用了同样框架下的一个叫AVAudioRecorder的录音机对象来实现,这两个类的用法流程非常类似,类的属性和方法也类似,例如:播放器中需要获取音频文件的url,而录音机要在沙盒中Docuemnt目录下创建一个音频文件路径url;
播放器有isPlaying变量判断是否正在播放,录音机中有isRecording变量表示是否正在录制;currentTime在播放器中表示播放时间,在录音机中则表示录音时间;播放器通过prepareToPlay方法加载文件到缓冲区,录音机通过prepareToRecord创建缓冲区;播放音频有play方法,音频录制有record方法,另外都有pause暂停方法和stop停止方法等等,具体可直接打开两个类的头文件详细了解。
这里实现最基本的录音流程以及录音过程的控制,并通过之前使用的AVAudioPlayer来播放录制好的音频。注意iOS录制的音频为caf格式,如果需要通用化可以通过lame等插件将caf格式音频转成mp3格式。
录音
这里实现开始录音,暂停,继续以及停止录音。
创建文件目录
iOS沙盒内胡要有三个目录:Documents目录,tmp目录以及Library目录,其中Documents目录用来存放用户的应用程序数据,需要定期备份的数据要放在这里,和plist文件存储一样,我们要找到存放文件的路径,然后在该路径下放一个我们的文件,因此要自定义一个带后缀的文件名,将获得的路径和文件名拼在一起记得到我们的文件的绝对路径:
// 文件名 #define fileName_caf @"demoRecord.caf" // 录音文件绝对路径 @property (nonatomic, copy) NSString *filepathCaf; // 获取沙盒Document文件路径 NSString *sandBoxPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // 拼接录音文件绝对路径 _filepathCaf = [sandBoxPath stringByAppendingPathComponent:fileName_caf];
创建音频会话
录音前要创建一个音频会话,同时要设置录音类型,提供的类型有以下几种:
- AVF_EXPORT NSString *const AVAudioSessionCategoryAmbient; // 用于录制背景声音,像雨声、汽车引擎发动噪音等,可和其他音乐混合
- AVF_EXPORT NSString *const AVAudioSessionCategorySoloAmbient; // 也是背景声音,但其他音乐会被强制停止
- AVF_EXPORT NSString *const AVAudioSessionCategoryPlayback; // 音轨
- AVF_EXPORT NSString *const AVAudioSessionCategoryRecord; // 录音
- AVF_EXPORT NSString *const AVAudioSessionCategoryPlayAndRecord; // 录音和回放
- AVF_EXPORT NSString *const AVAudioSessionCategoryAudioProcessing; // 用于底层硬件编码信号处理等
- AVF_EXPORT NSString *const AVAudioSessionCategoryMultiRoute; // 内置硬件相关,iOS 6.0以上可用
常用的是AVAudioSessionCategoryPlayAndRecord类型,便于录音后播放。
// 创建音频会话 AVAudioSession *audioSession=[AVAudioSession sharedInstance]; // 设置录音类别(这里选用录音后可回放录音类型) [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; [audioSession setActive:YES error:nil];
录音设置
录音前要根据需要对录音进行一些相应的基本设置,例如录音格式(LinearPCM)、采样率、通道数等等,设置保存在一个字典内并作为初始化录音机的一个参数。
// 录音设置 -(NSDictionary *)getAudioSetting{ // LinearPCM 是iOS的一种无损编码格式,但是体积较为庞大 // 录音设置信息字典 NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init]; // 录音格式 [recordSettings setValue :@(kAudioFormatLinearPCM) forKey: AVFormatIDKey]; // 采样率 [recordSettings setValue :@11025.0 forKey: AVSampleRateKey]; // 通道数(双通道) [recordSettings setValue :@2 forKey: AVNumberOfChannelsKey]; // 每个采样点位数(有8、16、24、32) [recordSettings setValue :@16 forKey: AVLinearPCMBitDepthKey]; // 采用浮点采样 [recordSettings setValue:@YES forKey:AVLinearPCMIsFloatKey]; // 音频质量 [recordSettings setValue:@(AVAudioQualityMedium) forKey:AVEncoderAudioQualityKey]; // 其他可选的设置 // ... ... return recordSettings; }
创建录音机对象
录音机对象的创建主要是利用上面的保存路径和录音设置进行初始化得到:
// 懒加载录音机对象get方法 - (AVAudioRecorder *)audioRecorder { if (!_audioRecorder) { // 保存录音文件的路径url NSURL *url = [NSURL URLWithString:_filepathCaf]; // 创建录音格式设置setting NSDictionary *setting = [self getAudioSetting]; // error NSError *error=nil; _audioRecorder = [[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error]; _audioRecorder.delegate = self; _audioRecorder.meteringEnabled = YES;// 监控声波 if (error) { NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription); return nil; } } return _audioRecorder; }
录音控制方法
录音过程控制主要是开始录音、暂停、继续和停止录音,其中开始录音和继续录音都是record方法。
// 开始录音或者继续录音 - (IBAction)startOrResumeRecord { // 注意调用audiorecorder的get方法 if (![self.audioRecorder isRecording]) { // 如果该路径下的音频文件录制过则删除 [self deleteRecord]; // 开始录音,会取得用户使用麦克风的同意 [_audioRecorder record]; } } // 录音暂停 - (IBAction)pauseRecord { if (_audioRecorder) { [_audioRecorder pause]; } } // 结束录音 - (IBAction)stopRecord { [_audioRecorder stop]; }
录音播放
录音的播放很简单,就是之前AVAudioPlayer音频播放的简单应用,播放的路径即我们录音时创建好的音频路径。但这里注意为了保证每次都播放最新录制的音频,播放器的get方法要每次重新创建初始化。
// audioPlayer懒加载getter方法 - (AVAudioPlayer *)audioPlayer { _audioRecorder = NULL; // 每次都创建新的播放器,删除旧的 // 资源路径 NSURL *url = [NSURL fileURLWithPath:_filepathCaf]; // 初始化播放器,注意这里的Url参数只能为本地文件路径,不支持HTTP Url NSError *error = nil; _audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //设置播放器属性 _audioPlayer.numberOfLoops = 0;// 不循环 _audioPlayer.delegate = self; _audioPlayer.volume = 0.5; // 音量 [_audioPlayer prepareToPlay];// 加载音频文件到缓存【这个函数在调用play函数时会自动调用】 if(error){ NSLog(@"初始化播放器过程发生错误,错误信息:%@",error.localizedDescription); return nil; } return _audioPlayer; } // 播放录制好的音频 - (IBAction)playRecordedAudio { // 没有文件不播放 if (![[NSFileManager defaultManager] fileExistsAtPath:self.filepathCaf]) return; // 播放最新的录音 [self.audioPlayer play]; }
完整源码和Demo下载
// // ViewController.m // IOSRecorderDemo // // Created by Xinhou Jiang on 29/12/16. // Copyright © 2016年 Xinhou Jiang. All rights reserved. // #import "ViewController.h" #import <AVFoundation/AVFoundation.h> // 文件名 #define fileName_caf @"demoRecord.caf" @interface ViewController () // 录音文件绝对路径 @property (nonatomic, copy) NSString *filepathCaf; // 录音机对象 @property (nonatomic, strong) AVAudioRecorder *audioRecorder; // 播放器对象,和上一章音频播放的方法相同,只不过这里简单播放即可 @property (nonatomic, strong) AVAudioPlayer *audioPlayer; // 用一个processview显示声波波动情况 @property (nonatomic, weak) IBOutlet UIProgressView *processView; // 用一个label显示录制时间 @property (nonatomic, weak) IBOutlet UILabel *recordTime; // UI刷新监听器 @property (nonatomic, strong) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 初始化工作 [self initData]; } // 初始化 - (void)initData { // 获取沙盒Document文件路径 NSString *sandBoxPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // 拼接录音文件绝对路径 _filepathCaf = [sandBoxPath stringByAppendingPathComponent:fileName_caf]; // 1.创建音频会话 AVAudioSession *audioSession=[AVAudioSession sharedInstance]; // 设置录音类别(这里选用录音后可回放录音类型) [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; [audioSession setActive:YES error:nil]; // 2.开启定时器 [self timer]; } #pragma mark -录音设置工具函数 // 懒加载录音机对象get方法 - (AVAudioRecorder *)audioRecorder { if (!_audioRecorder) { // 保存录音文件的路径url NSURL *url = [NSURL URLWithString:_filepathCaf]; // 创建录音格式设置setting NSDictionary *setting = [self getAudioSetting]; // error NSError *error=nil; _audioRecorder = [[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error]; _audioRecorder.delegate = self; _audioRecorder.meteringEnabled = YES;// 监控声波 if (error) { NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription); return nil; } } return _audioRecorder; } // audioPlayer懒加载getter方法 - (AVAudioPlayer *)audioPlayer { _audioRecorder = NULL; // 每次都创建新的播放器,删除旧的 // 资源路径 NSURL *url = [NSURL fileURLWithPath:_filepathCaf]; // 初始化播放器,注意这里的Url参数只能为本地文件路径,不支持HTTP Url NSError *error = nil; _audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //设置播放器属性 _audioPlayer.numberOfLoops = 0;// 不循环 _audioPlayer.delegate = self; _audioPlayer.volume = 0.5; // 音量 [_audioPlayer prepareToPlay];// 加载音频文件到缓存【这个函数在调用play函数时会自动调用】 if(error){ NSLog(@"初始化播放器过程发生错误,错误信息:%@",error.localizedDescription); return nil; } return _audioPlayer; } // 计时器get方法 - (NSTimer *)timer { if (!_timer) { _timer = [NSTimer scheduledTimerWithTimeInterval:0.1f repeats:YES block:^(NSTimer * _Nonnull timer) { if(_audioRecorder) { // 1.更新录音时间,单位秒 int curInterval = [_audioRecorder currentTime]; _recordTime.text = [NSString stringWithFormat:@"%02d:%02d",curInterval/60,curInterval%60]; // 2.声波显示 //更新声波值 [self.audioRecorder updateMeters]; //第一个通道的音频,音频强度范围:[-160~0],这里调整到0~160 float power = [self.audioRecorder averagePowerForChannel:0] + 160; [_processView setProgress:power/160.0]; } }]; } return _timer; } // 录音设置 -(NSDictionary *)getAudioSetting{ // LinearPCM 是iOS的一种无损编码格式,但是体积较为庞大 // 录音设置信息字典 NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init]; // 录音格式 [recordSettings setValue :@(kAudioFormatLinearPCM) forKey: AVFormatIDKey]; // 采样率 [recordSettings setValue :@11025.0 forKey: AVSampleRateKey]; // 通道数(双通道) [recordSettings setValue :@2 forKey: AVNumberOfChannelsKey]; // 每个采样点位数(有8、16、24、32) [recordSettings setValue :@16 forKey: AVLinearPCMBitDepthKey]; // 采用浮点采样 [recordSettings setValue:@YES forKey:AVLinearPCMIsFloatKey]; // 音频质量 [recordSettings setValue:@(AVAudioQualityMedium) forKey:AVEncoderAudioQualityKey]; // 其他可选的设置 // ... ... return recordSettings; } // 删除filepathCaf路径下的音频文件 -(void)deleteRecord{ NSFileManager* fileManager=[NSFileManager defaultManager]; if ([[NSFileManager defaultManager] fileExistsAtPath:self.filepathCaf]) { // 文件已经存在 if ([fileManager removeItemAtPath:self.filepathCaf error:nil]) { NSLog(@"删除成功"); }else { NSLog(@"删除失败"); } }else { return; // 文件不存在无需删除 } } #pragma mark -录音流程控制函数 // 开始录音或者继续录音 - (IBAction)startOrResumeRecord { // 注意调用audiorecorder的get方法 if (![self.audioRecorder isRecording]) { // 如果该路径下的音频文件录制过则删除 [self deleteRecord]; // 开始录音,会取得用户使用麦克风的同意 [_audioRecorder record]; } } // 录音暂停 - (IBAction)pauseRecord { if (_audioRecorder) { [_audioRecorder pause]; } } // 结束录音 - (IBAction)stopRecord { [_audioRecorder stop]; } #pragma mark -录音播放 // 播放录制好的音频 - (IBAction)playRecordedAudio { // 没有文件不播放 if (![[NSFileManager defaultManager] fileExistsAtPath:self.filepathCaf]) return; // 播放最新的录音 [self.audioPlayer play]; } @end
Demo下载:demo