录音并将caf转换格式为mp3之iOS笔记摘录

目录
    
  

长风破浪会有时,直挂云帆济沧海。

项目中遇到过需要录制一段声音,然后上传到7牛(格式需要是mp3格式)。

格外注意一下:AVAudioRecorder的配置参数和lame_set配置参数。
配置不正确会导致转换的mp3文件长度不正确。

前言

音频-文件格式(后缀)

1. WAV:
    音质最好(对应PCM编码)
    适用:多媒体开发,保存音乐和音效素材。

2. MP3:
    音质好,压缩比比较高,被广泛支持(iOS、Android都支持)。
    适用:比较高要求的音乐欣赏。

3. caf:
    适用: iOS中所有的编码格式。
音频-编码格式

1. PCM
    脉冲编码调制,是一种非压缩音频数字化技术(未压缩的原音重现)。
    数字模式下,音频的初始化信号是PCM。

2. MP3

3. AAC
    advanced audio coding高级音频编码,被设计来取代MPC格式。

4. HE-AAC
    HE-AAC是AAC的一个超集,专门为低比特率所优化的一种音频编码格式。

5. AMR
    Adaptive Multi-Rate,另一个专门为低比特率所优化的一种音频编码格式。

2.6 ALAC
    Apple Lossless,无损压缩的音频编码方式。

2.7 IMA4
    在16-bit音频文件下按照4:1的压缩比来进行压缩的格式。
  1. 录音工具类

FQVideoRecordTool.h

@interface FQVideoRecordTool : NSObject
// 必须设置(用于没权限时弹窗)
@property (nonatomic,weak) UIViewController *currentVC;
@property (nonatomic,copy) NSString *videoRecordPath;
// 最大录制/播放时间
@property (nonatomic,assign) int maxVideoRecordTime;
// 最小录制/播放时间
@property (nonatomic,assign) int minVideoRecordTime;
// 录制时间
@property (nonatomic,assign) int videoRecordTime;

+(instancetype)fq_shared;

// 录音
-(void)fq_recordVideo;
// 暂停录音
-(void)fq_pauseRecord;
// 停止录音
-(void)fq_stopRecord;
// 取消/清除 录音
-(void)fq_cancelOrCleanRecord;

// 播放录音
-(void)fq_playVideo;
// 暂停播放录音
-(void)fq_pauseVideo;
// 继续播放录音
-(void)fq_replayVideo;
@end

FQVideoRecordTool.m

#import "FQVideoRecordTool.h"

@interface FQVideoRecordTool()
@property (nonatomic,strong) AVAudioRecorder *videoRecorder;
@property (nonatomic,strong) AVAudioPlayer *videoplayer;
@end


@implementation FQVideoRecordTool

static FQVideoRecordTool *_shared;
+(instancetype)fq_shared{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if(!_shared){
            _shared=[FQVideoRecordTool new];
        }
    });
    return _shared;
}


#pragma mark 录音
// 录音
-(void)fq_recordVideo{
    AVAudioSessionRecordPermission permission = AVAudioSession.sharedInstance.recordPermission;
    //在此添加新的判定 undetermined,否则新安装后的第一次询问会出错。新安装后的第一次询问为 undetermined,而非 denied。
    if (permission == AVAudioSessionRecordPermissionDenied || permission == AVAudioSessionRecordPermissionUndetermined) {
        [AVAudioSession.sharedInstance requestRecordPermission:^(BOOL granted) {
            if (!granted) {
                UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"无法访问麦克风" message:@"开启麦克风权限才能发送语音消息" preferredStyle:UIAlertControllerStyleAlert];
                [ac addAction:[UIAlertAction actionWithTitle:@"以后再说" style:UIAlertActionStyleCancel handler:nil]];
                [ac addAction:[UIAlertAction actionWithTitle:@"去开启" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                    UIApplication *app = [UIApplication sharedApplication];
                    NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
                    if ([app canOpenURL:settingsURL]) {
                        [app openURL:settingsURL];
                    }
                }]];
                [self.currentVC presentViewController:ac animated:YES completion:nil];
            }
        }];
        return;
    }
    if(permission == AVAudioSessionRecordPermissionGranted){
        [self fq_startRecord];
    }
}
-(void)fq_startRecord{
    if([[NSFileManager defaultManager] fileExistsAtPath:self.videoRecordPath]){
//        [self.videoRecorder record];
        [self fq_cancelOrCleanRecord];
    }
    
    //
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *error = nil;
    [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
    [session setActive:YES error:&error];
    
    if(error){
        [SQGToast showWithText:@"录制出错"];
        NSLog(@"%@",error.userInfo);
    }else{
        [self.videoRecorder prepareToRecord];
        [self.videoRecorder record];
        [self.videoRecorder updateMeters];
    }
}
// 暂停录音
-(void)fq_pauseRecord{   //
    if([self.videoRecorder isRecording]){
        [self.videoRecorder pause];
    }
}
// 停止录音
-(void)fq_stopRecord{   //
    if([self.videoRecorder isRecording]){
        [self.videoRecorder stop];
    }
}
// 取消/清除 录音
-(void)fq_cancelOrCleanRecord{
    //
    if([self.videoRecorder isRecording]){
        [self.videoRecorder stop];
    }
    self.videoRecorder=nil;
    //
    if([[NSFileManager defaultManager] fileExistsAtPath:self.videoRecordPath]){
        [[NSFileManager defaultManager] removeItemAtPath:self.videoRecordPath error:nil];
    }
    
    //
    self.videoplayer=nil;
}

#pragma mark 播放录音
// 播放录音
-(void)fq_playVideo{
    if([self.videoRecorder isRecording]){
        [self fq_stopRecord];
    }
    if ([self.videoplayer isPlaying])return;
     
    //
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [self.videoplayer play];
}
// 暂停播放录音
-(void)fq_pauseVideo{
    if ([self.videoplayer isPlaying]){
        [self.videoplayer pause];
    }
}
// 继续播放录音
-(void)fq_replayVideo{
    if ([self.videoplayer isPlaying]){
        return;
    }
    
    [self.videoplayer play];
}
// 没权限
-(BOOL)fq_NoPermission{
    return AVAudioSession.sharedInstance.recordPermission == AVAudioSessionRecordPermissionDenied;
}



#pragma mark GET
-(AVAudioRecorder *)videoRecorder{
    if(!_videoRecorder){
        NSDictionary *recordSetting=[[NSDictionary alloc] initWithObjectsAndKeys:
                                       // 采样率  8000/11025/22050/44100/96000(影响音频的质量)
                                       [NSNumber numberWithFloat: 8000.0],AVSampleRateKey,
                                       // 音频格式()
                                       [NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
                                       // 采样位数  8、16、24、32 默认为16
                                       [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                                       // 音频通道数 1 或 2
                                       [NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
                                       //
                                       [NSNumber numberWithInt: 16],AVLinearPCMBitDepthKey,
                                       // 录音质量
                                       [NSNumber numberWithInt:AVAudioQualityHigh],AVEncoderAudioQualityKey,
                                       nil];
        NSError *error;
        _videoRecorder=[[AVAudioRecorder alloc]initWithURL:[NSURL URLWithString:self.videoRecordPath]settings:recordSetting error:&error];
        [_videoRecorder setMeteringEnabled:true];
    }
    return _videoRecorder;
}
-(NSString *)videoRecordPath{
    if(!_videoRecordPath){
        _videoRecordPath=[NSString stringWithFormat:@"%@/VideoRecord.caf",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]];
    }
    return _videoRecordPath;
    
}
-(AVAudioPlayer *)videoplayer{
    if(!_videoplayer){
        _videoplayer=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:self.videoRecordPath] error:nil];
    }
    return _videoplayer;
}
-(int)maxVideoRecordTime{
    if(!_maxVideoRecordTime){
        _maxVideoRecordTime=20;
    }
    return _maxVideoRecordTime;
}
-(int)minVideoRecordTime{
    if(!_minVideoRecordTime){
        _minVideoRecordTime=5;
    }
    return _minVideoRecordTime;
}



-(void)dealloc{
    //
    [self fq_cancelOrCleanRecord];
}
@end
  1. 打包lame静态库(用来将caf转换为mp3格式)

1. 下载lame
2. 下载build脚本

  1. 创建一个新文件夹(存放1和2下载的内容)
  2. 编译(在终端输入./build-lame.sh)
  3. 将fat-lame中编译好的.a和.h文件拖入项目(这里要注意:勾选Create folder refrences,和平时的Create groups不太一样)
文件目录1
文件目录2
  1. lame使用工具类

FQLibMp3Tool.h

@interface FQLibMp3Tool : NSObject

+(NSString *)FQTransferToMP3:(NSString *)sourcePath isDeleteSourchFile:(BOOL)isDelete;
@end

FQLibMp3Tool.m

#import "FQLibMp3Tool.h"
#import "lame.h"

@implementation FQLibMp3Tool

+(NSString *)FQTransferToMP3: (NSString *)sourcePath isDeleteSourchFile:(BOOL)isDelete {
    
    // 输入路径
    NSString *inPath = sourcePath;
    // 判断输入路径是否存在
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:sourcePath]) {
        NSLog(@"文件不存在");
    }
    // 输出路径
    NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
    @try {
        int read, write;
        
        FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb");  //source 被转换的音频文件位置
        fseek(pcm, 4*1024, SEEK_CUR);                                   //skip file header
        FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb+");  //output 输出生成的Mp3文件位置
        
        const int PCM_SIZE = 8192;
        const int MP3_SIZE = 8192;
        short int pcm_buffer[PCM_SIZE*2];
        unsigned char mp3_buffer[MP3_SIZE];
        
        // 配置不正确可能造成mp3转换时间长短出现错误。
        lame_t lame = lame_init();
        lame_set_num_channels(lame,2);  // 设置1为单通道,默认为2双通道
        lame_set_in_samplerate(lame, 8000.0);
//        lame_set_VBR(lame, vbr_default);
        lame_set_brate(lame, 16);
        lame_set_mode(lame, 3);
        lame_set_quality(lame, 2); /* 2=high 5 = medium 7=low 音质*/
        lame_init_params(lame);
        
        do {
            size_t size = (size_t)(2 * sizeof(short int));
            read = fread(pcm_buffer, size, PCM_SIZE, pcm);
            if (read == 0)
                write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            else
                write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
            
            fwrite(mp3_buffer, write, 1, mp3);
            
        } while (read != 0);
        
        lame_close(lame);
        fclose(mp3);
        fclose(pcm);
    }
    @catch (NSException *exception) {
        NSLog(@"%@",[exception description]);
    }
    @finally {
        NSLog(@"MP3生成成功:");
        if (isDelete) {
            
            NSError *error;
            [fm removeItemAtPath:sourcePath error:&error];
            if (error == nil) {
                NSLog(@"删除源文件成功");
            }
        }
        return outPath;
    }
}

@end
  1. 转换为mp3格式
// var/..../test.mp3
NSString *mp3Path=[FQLibMp3Tool FQTransferToMP3:@"var/..../test.caf" isDeleteSourchFile:false];

你可能感兴趣的:(录音并将caf转换格式为mp3之iOS笔记摘录)