ios实现录制 mp3文件

最近公司做项目,突然让我去实现一个录制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
大家记得点赞!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容