iOS.AVFoundation.AVAudioRecorders
Morris_ 2019.01.11
前言
做流媒体相关的开发工作,对AVFoundation和AVKit这两个框架的学习是很有必要的,可能还有美颜等这些功能。
AVFoundation和AVKit框架功能很强大,感觉有点不知道从何下手的感觉,那就一点一点来看吧。这里先看AVFoundation。
AVFoundation框架结构
打开查看AVFoundation文档,里面包含有一个AFVAudio的Framework。看类名,这个库应该是提供音频相关功能的类吧。Headers里面是所有这个框架里的类。
本篇学习AVAudioRecorder
AVAudioRecorder
see also : AVAudioRecorder
A class that provides audio recording capability in your application.
App中提供音频录制功能的类,有如下功能:
-
Record until the user stops the recording
录音功能,开始、停止录制
-
Record for a specified duration
指定时间开始录制
-
Pause and resume a recording
暂停录制、继续录制
-
Obtain input audio-level data that you can use to provide level metering
获取相关音频数据
音频录制的实现步骤
第一步:录制文件地址
我们将录制的音频统一放置在一个文件下,所以我们首先需要确保这个文件被创建。
1、创建/获取文件夹路劲
- (NSString *)audioDocmentPath
{
NSString *homePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *docPath = [homePath stringByAppendingPathComponent:@"音频录制文件夹001"];
if ([[NSFileManager defaultManager] fileExistsAtPath:docPath])
{
NSLog(@"文件夹已经存在,不需要创建");
}
else
{
NSError *error = nil;
if ([[NSFileManager defaultManager] createDirectoryAtPath:docPath
withIntermediateDirectories:YES
attributes:nil
error:&error])
{
NSLog(@"创建文件夹成功");
}
else {
NSLog(@"创建文件夹失败:%@",error);
}
}
return docPath;
}
2、创建文件地址
每一次录制的音频文件在手机沙河目录里都应该有其对应的存储路劲,并且不重复。
- (NSString *)creatFilePath {
//为了不覆盖上次的录制内容,每次生成一个新的时间戳,作为新文件名
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSString *timeStr = [NSString stringWithFormat:@"%f",timeStamp];
NSString *newFilePath = [[self audioDocmentPath] stringByAppendingPathComponent:timeStr];
if (![[NSFileManager defaultManager] fileExistsAtPath:newFilePath])
{
if ([[NSFileManager defaultManager] createFileAtPath:newFilePath contents:nil attributes:nil])
{
NSLog(@"文件地址创建成功!");
}
else {
NSLog(@"文件地址创建失败!");
}
}
return newFilePath;
}
创建文件路径的时候,不要每次随意创建一个路径,路径基础地址最好统一,上面我们已经创建好了存放文件的文件夹,所以文件路径直接在文件夹下再追加文件名称即可。
第二步:开始录制
拿到文件地址,直接开始录制即可。
/**
为了不覆盖上次的录制内容,我们需要生成新的url,并传入AVAudioRecorder的对象中,由于这个参数只能在初始化AVAudioRecorder的对象的时候设置,所以每次需要创建一个新的音频录制对象。
*/
//释放之前的对象
[self.audioRecorder stop];
self.audioRecorder = nil;
//创建新的录制对象
NSString *filePath = [self creatFilePath];
//这里为了验证录制成功,并播放上一次的录制,我们记录一下这次的文件地址
self.lastRecordFilePath = filePath;
NSURL *url = [NSURL fileURLWithPath:filePath];
NSError *outError = nil;
NSDictionary *seetings = [self getAudioSetting];
AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:url settings:seetings error:&outError];
//音频对象创建无误
if (!outError) {
//保存当前的音频录制对象
self.audioRecorder = recorder;
self.audioRecorder.delegate = self;
//开启设备录音模式
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
//开始录制
if ([self.audioRecorder prepareToRecord]) {
[self.audioRecorder record];
}
}
这里每一次录制我们都要讲新的录制url传入到AVAudioRecorder的对象中去进行新一轮的录制。
这里我保存了每次最新的录制对象,所以在录制前先停止上一次录制并且将上一次的录制对象释放。
@interface ViewController ()<AVAudioRecorderDelegate,AVAudioPlayerDelegate>
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@end
在创建recorder对象的时,需要设置录制参数,这里我直接摘抄的别人的设置:
//获取录音文件设置
- (NSMutableDictionary *)getAudioSetting
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//设置录音格式
[dict setObject:@(kAudioFormatMPEG4AAC) forKey:AVFormatIDKey];
//设置录音采样率,8000是电话采样率,对于一般录音已经够了
[dict setObject:@(8000) forKey:AVSampleRateKey];
//设置通道,这里采用单声道
[dict setObject:@(1) forKey:AVNumberOfChannelsKey];
//每个采样点位数,分为8、16、24、32
[dict setObject:@(8) forKey:AVLinearPCMBitDepthKey];
//是否使用浮点数采样
[dict setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
//....其他设置等
return dict;
}
这样就可以录制了,录制完后,可以使用AVAudioPlayer再去播放一下本次录制的音频,这里就不多写了,直接看下面的代码。
demo
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kViewController_Audio_data @"kViewController_Audio_data"
@interface ViewController ()<AVAudioRecorderDelegate,AVAudioPlayerDelegate>
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
@property (nonatomic, copy) NSString *lastRecordFilePath;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *recordBtn = [UIButton buttonWithType:UIButtonTypeCustom];
recordBtn.center = self.view.center;
recordBtn.bounds = CGRectMake(0, 0, 80, 80);
recordBtn.layer.cornerRadius = 40;
recordBtn.layer.borderColor = [UIColor lightGrayColor].CGColor;
recordBtn.layer.borderWidth = 0.5;
[recordBtn setTitle:@"start" forState:UIControlStateNormal];
[recordBtn setTitle:@"stop" forState:UIControlStateSelected];
[recordBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[recordBtn setTitleColor:[UIColor grayColor] forState:UIControlStateSelected];
[recordBtn addTarget:self action:@selector(recordButtonDidClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:recordBtn];
}
//创建文件路劲
- (NSString *)creatFilePath {
//为了不覆盖上次的录制内容,每次生成一个新的时间戳,作为新文件名
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSString *timeStr = [NSString stringWithFormat:@"%f",timeStamp];
NSString *newFilePath = [[self audioDocmentPath] stringByAppendingPathComponent:timeStr];
if (![[NSFileManager defaultManager] fileExistsAtPath:newFilePath])
{
if ([[NSFileManager defaultManager] createFileAtPath:newFilePath contents:nil attributes:nil])
{
NSLog(@"文件地址创建成功!");
}
else {
NSLog(@"文件地址创建失败!");
}
}
return newFilePath;
}
//获取存储音频数据的文件夹,如果是第一次,则直接创建
- (NSString *)audioDocmentPath
{
NSString *homePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *docPath = [homePath stringByAppendingPathComponent:kViewController_Audio_data];
if ([[NSFileManager defaultManager] fileExistsAtPath:docPath])
{
NSLog(@"文件夹已经存在,不需要创建");
}
else
{
NSError *error = nil;
if ([[NSFileManager defaultManager] createDirectoryAtPath:docPath
withIntermediateDirectories:YES
attributes:nil
error:&error])
{
NSLog(@"创建文件夹成功");
}
else {
NSLog(@"创建文件夹失败:%@",error);
}
}
return docPath;
}
//取得录音文件设置
- (NSMutableDictionary *)getAudioSetting
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//设置录音格式
[dict setObject:@(kAudioFormatMPEG4AAC) forKey:AVFormatIDKey];
//设置录音采样率,8000是电话采样率,对于一般录音已经够了
[dict setObject:@(8000) forKey:AVSampleRateKey];
//设置通道,这里采用单声道
[dict setObject:@(1) forKey:AVNumberOfChannelsKey];
//每个采样点位数,分为8、16、24、32
[dict setObject:@(8) forKey:AVLinearPCMBitDepthKey];
//是否使用浮点数采样
[dict setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
//....其他设置等
return dict;
}
#pragma mark - Action && Notification
- (void)recordButtonDidClick:(UIButton *)sender
{
sender.selected = !sender.selected;
if (sender.selected)
{
//停止当前的播放
[self.audioPlayer stop];
self.audioPlayer = nil;
/**
为了不覆盖上次的录制内容,我们需要生成新的url,并传入AVAudioRecorder的对象中,由于这个参数只能在初始化AVAudioRecorder的对象的时候设置,所以每次需要创建一个新的音频录制对象。
*/
//释放之前的对象
[self.audioRecorder stop];
self.audioRecorder = nil;
//创建新的录制对象
NSString *filePath = [self creatFilePath];
//这里为了验证录制成功,并播放上一次的录制,我们记录一下这次的文件地址
self.lastRecordFilePath = filePath;
NSURL *url = [NSURL fileURLWithPath:filePath];
NSError *outError = nil;
NSDictionary *seetings = [self getAudioSetting];
AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:url settings:seetings error:&outError];
//音频对象创建无误
if (!outError) {
//保存当前的音频录制对象
self.audioRecorder = recorder;
self.audioRecorder.delegate = self;
//开启设备录音模式
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
//开始录制
if ([self.audioRecorder prepareToRecord]) {
[self.audioRecorder record];
}
}
}
else
{
[self.audioRecorder stop];
}
}
#pragma mark - AVAudioRecorderDelegate
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
{
NSLog(@"结束录制,存储地址是%@", self.lastRecordFilePath);
//播放完成后,验证一下播放
[self playLastAudio];
}
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError * __nullable)error;
{
NSLog(@"出现了错误 = %@", error);
}
- (void)playLastAudio
{
[self.audioPlayer stop];
self.audioPlayer = nil;
NSError *outError = nil;
NSURL *pathURL = [NSURL fileURLWithPath:self.lastRecordFilePath];
NSData *data = [NSData dataWithContentsOfURL:pathURL];
AVAudioPlayer *curPlayer = [[AVAudioPlayer alloc] initWithData:data error:&outError];
curPlayer.delegate = self;
if (outError) {
NSLog(@"音频播放器创建失败:%@",outError);
} else {
self.audioPlayer = curPlayer;
//self.audioPlayer.delegate = self;//如果在这里设置代理,播放成功的代理不会回调,这是为什么?
if ([self.audioPlayer prepareToPlay]) {
[self.audioPlayer play];
}
}
}
#pragma mark - AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
NSLog(@"播放成功!");
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {
NSLog(@"播放失败:%@",error);
}
@end