最近公司做项目,突然让我去实现一个录制MP3的功能,我当时一想这还不简单,苹果有现成的框架,随机几句话就搞定,说着就揽下了这个活,可谁知,坑就在后面........
我就打算先顺手写一个 demo, 导入什么 AV 框架,创建音频管理器,设置一些参数啊,就搞定了,结果苹果这个巨坑,特么不支持 mp3 录制。
原来参数是这样写的:
NSDictionary *settings = @{
AVFormatIDKey:@(kAudioFormatMPEGLayer3),
AVLinearPCMBitDepthKey:@(8), // 位深度
AVSampleRateKey:@(8000),
AVNumberOfChannelsKey:@(1)
};
self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:nil];
录制完了去沙盒一看,尼玛,啥都没有,瞬间懵逼了,后来上网搜索了一下才知道,苹果不支持 mp3 录制,但是却能够播放 mp3,好奇葩的设定....
没办法只能去网络上去搜索,关于转换的教程也是有好几篇,可是一看都是拷贝别人的,运行都报错,而且有的 demo 都是2012年的,现在的 xcode 都没法跑了,更别说使用.
当时都准备放弃了,在想如何找措辞去推掉这个任务.想了好久还是先放弃这个念头,毕竟这是上头交给的任务,还是完成比较好,最后我还是去网上搜索,发现有个第三方框架,叫做lame,也是比较老的框架了,不过还是很强大的,最后我就利用这个框架,再参考网络上其他人的分享,终于把这个录制实现了.
分享一下实现的思路:
开始还是通过苹果官方提供的方法录制成了一个 caf 音频,这是苹果支持的格式,然后再把这个 caf 通过第三方框架 lame 转化为 mp3 格式.
整体实现的步骤:
1.老套路,导入 AV 框架和 lame 头文件
2.创建相关的属性
@interface ViewController ()
{
//音频存储的路径
NSString *_strCAFPath;
NSString *_strMp3Path;
//录制的音频器
AVAudioRecorder *_avRecorder;
NSMutableArray *_arrayCAFFiles;
NSMutableArray *_arrayMp3Files;
//这两个属性是按钮的状态判定
//正在记录
BOOL _isRecording;
//正在转换
BOOL _isConverting;
}
@property (weak, nonatomic) IBOutlet UIButton *recordBtn;
@end
3.初始化要做的事情,创建相应的文件目录
- (void)viewDidLoad {
[super viewDidLoad];
//1.获取沙盒路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//2.存储 caf 的路径和 mp3的路径
_strCAFPath = [[NSString alloc] initWithFormat:@"%@/%@",documentsDirectory,@"CAF"];
_strMp3Path = [[NSString alloc] initWithFormat:@"%@/%@",documentsDirectory,@"Mp3"];
NSFileManager *fileManager = [NSFileManager defaultManager];
//3.创建各自的文件夹
[fileManager createDirectoryAtPath:_strCAFPath withIntermediateDirectories:YES attributes:nil error:nil];
[fileManager createDirectoryAtPath:_strMp3Path withIntermediateDirectories:YES attributes:nil error:nil];
}
4.按钮点击的事件
//用于切换按钮的两种状态 开始和暂停
_isRecording=!_isRecording;
//如果是暂停 意为正在录制
if (_isRecording) {
[self.recordBtn setTitle:@"停止" forState:UIControlStateNormal];
//取出系统的时间
NSDateFormatter *fileNameFormatter = [[NSDateFormatter alloc] init];
[fileNameFormatter setDateFormat:@"yyyyMMddhhmmss"];
//把系统时间的字符格式应用为录制文件的名称
NSString *fileName = [fileNameFormatter stringFromDate:[NSDate date]];
fileName = [fileName stringByAppendingString:@".caf"];
NSString *cafFilePath = [_strCAFPath stringByAppendingPathComponent:fileName];
//创建音频设备需要的 URL
NSURL *cafURL = [NSURL fileURLWithPath:cafFilePath];
NSError *error;
NSLog(@"cafURL:%@" ,cafURL);
//设置录制音频需要的一些参数
NSDictionary *recordFileSettings = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:AVAudioQualityMin],
AVEncoderAudioQualityKey, //音频质量
[NSNumber numberWithInt:16],
AVEncoderBitRateKey,
[NSNumber numberWithInt: 2], //声音通道,这里必须为双通道 否则无法转换为 mp3
AVNumberOfChannelsKey,
[NSNumber numberWithFloat:44100.0], //采样率
AVSampleRateKey,
nil];
//判断音频设备是否存在
if (!_avRecorder) {
_avRecorder = [[AVAudioRecorder alloc] initWithURL:cafURL settings:recordFileSettings error:&error];
}else {
if ([_avRecorder isRecording]) {
[_avRecorder stop];
}
_avRecorder=Nil;
_avRecorder = [[AVAudioRecorder alloc] initWithURL:cafURL settings:recordFileSettings error:&error];
}
if (_avRecorder) {
[_avRecorder prepareToRecord];
_avRecorder.meteringEnabled = YES;
[_avRecorder record];
NSLog(@"正在录制");
self.lastRecordFileName=fileName;
}
//否则设置为录制
}else {
[self.recordBtn setTitle:@"录制" forState:UIControlStateNormal];
if (_avRecorder) {
[_avRecorder stop];
_avRecorder=Nil;
//在录制完苹果支持的 caf 文件之后 开始转换
# [self toMp3:self.lastRecordFileName]; //转换代码
}
}
5.转换的重要代码
//转换为 mp3 格式的重要代码
- (void)toMp3:(NSString*)cafFileName
{
NSString *cafFilePath =[_strCAFPath stringByAppendingPathComponent:cafFileName];
//根据当前的时间来命名录制的名称
NSDateFormatter *fileNameFormat=[[NSDateFormatter alloc] init];
[fileNameFormat setDateFormat:@"yyyyMMddhhmmss"];
NSString *mp3FileName = [fileNameFormat stringFromDate:[NSDate date]];
mp3FileName = [mp3FileName stringByAppendingString:@".mp3"];
NSString *mp3FilePath = [_strMp3Path stringByAppendingPathComponent:mp3FileName];
//开始转换
int read, write;
//转换的源文件位置
FILE *pcm = fopen([cafFilePath cStringUsingEncoding:1], "rb");
//转换后保存的位置
FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:1], "wb");
const int PCM_SIZE = 8192;
const int MP3_SIZE = 8192;
short int pcm_buffer[PCM_SIZE*2];
unsigned char mp3_buffer[MP3_SIZE];
//创建这个工具类
lame_t lame = lame_init();
lame_set_in_samplerate(lame, 44100);
lame_set_VBR(lame, vbr_default);
lame_init_params(lame);
//doWhile 循环
do {
read = fread(pcm_buffer, 2*sizeof(short int), PCM_SIZE, pcm);
if (read == 0){
//这里面的代码会在最后调用 只会调用一次
write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
//NSLog(@"read0%d",write);
}
else{
//这个 write 是写入文件的长度 在此区域内会一直调用此内中的代码 一直再压缩 mp3文件的大小,直到不满足条件才退出
write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
//写入文件 前面第一个参数是要写入的块 然后是写入数据的长度 声道 保存地址
fwrite(mp3_buffer, write, 1, mp3);
//NSLog(@"read%d",write);
}
} while (read != 0);
lame_close(lame);
fclose(mp3);
fclose(pcm);
}
实现就这样了,其实没什么难度,主要是这个 lame 框架的使用有点麻烦,都是用 c 语言写的,相对于 c 语言功底较好的人来说,看起来应该分简单。
有需要的可以试试看,唯一的缺点是转换之后的 mp3音质不是太好,希望有大神能够解决.
放上 demo 地址 :https://git.oschina.net/yangmaosheng/ConvertCafToMp3.git
大家记得点赞!