iOS 录音、音频的拼接剪切以及边录边压缩转码

简介: iOS 录音、音频的拼接剪切以及边录边压缩转码

总体内容


1、录音实现

2、录音的编辑 (拼接音频:可以设置多段,音频的剪切:按照时间段剪切)

3、lame静态库进行压缩转码


一、录音实现



  • 1.1、导入 AVFoundation 框架,多媒体的处理, 基本上都使用这个框架


#import <AVFoundation/AVFoundation.h>
  • 1.2、使用 AVAudioRecorder 进行录音,定义一个JKAudioTool 管理录音的类
  • (1)、定义一个录音对象,懒加载


@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
-(AVAudioRecorder *)audioRecorder
{
    if (!_audioRecorder) {
       // 0. 设置录音会话
       /**
         AVAudioSessionCategoryPlayAndRecord: 可以边播放边录音(也就是平时看到的背景音乐)
        */
       [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
       // 启动会话
       [[AVAudioSession sharedInstance] setActive:YES error:nil];
       // 1. 确定录音存放的位置
       NSURL *url = [NSURL URLWithString:self.recordPath];
       // 2. 设置录音参数
       NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init];
       // 设置编码格式
       /**
         kAudioFormatLinearPCM: 无损压缩,内容非常大
         kAudioFormatMPEG4AAC
       */
       [recordSettings setValue :[NSNumber numberWithInt: kAudioFormatLinearPCM] forKey: AVFormatIDKey];
       // 采样率(通过测试的数据,根据公司的要求可以再去调整),必须保证和转码设置的相同
       [recordSettings setValue :[NSNumber numberWithFloat:11025.0] forKey: AVSampleRateKey];
       // 通道数(必须设置为双声道, 不然转码生成的 MP3 会声音尖锐变声.)
       [recordSettings setValue :[NSNumber numberWithInt:2] forKey: AVNumberOfChannelsKey];
       //音频质量,采样质量(音频质量越高,文件的大小也就越大)
       [recordSettings setValue:[NSNumber numberWithInt:AVAudioQualityMin] forKey:AVEncoderAudioQualityKey];
       // 3. 创建录音对象
       _audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSettings error:nil];
       _audioRecorder.meteringEnabled = YES;
     }
   return _audioRecorder;
}


提示:设置 AVAudioSessionCategoryPlayAndRecord: 可以边播放边录音(也就是平时看到的背景音乐)

  • [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
  • AVSampleRateKey 必须保证和转码设置的相同.
  • AVNumberOfChannelsKey 必须设置为双声道, 不然转码生成的 MP3 会声音尖锐变声.


  • (2)、开始录音


- (void)beginRecordWithRecordPath: (NSString *)recordPath {
    // 记录录音地址
    _recordPath = recordPath;
    // 准备录音
    [self.audioRecorder prepareToRecord];
    // 开始录音
    [self.audioRecorder record];
}
  • (3)、结束录音


- (void)endRecord {
     [self.audioRecorder stop];
}
  • (4)、暂停录音


- (void)pauseRecord {
    [self.audioRecorder pause];
}
  • (5)、删除录音


- (void)deleteRecord {
     [self.audioRecorder stop];
     [self.audioRecorder deleteRecording];
}
  • (6)、重新录音


- (void)reRecord {
    self.audioRecorder = nil;
    [self beginRecordWithRecordPath:self.recordPath];
}
  • (7)、更新音频测量值


-(void)updateMeters
{
    [self.audioRecorder updateMeters];
}


提示:更新音频测量值,注意如果要更新音频测量值必须设置meteringEnabled为YES,通过音频测量值可以即时获得音频分贝等信息

@property(getter=isMeteringEnabled) BOOL meteringEnabled:是否启用音频测量,默认为NO,一旦启用音频测量可以通过updateMeters方法更新测量值


  • (8)、获得指定声道的分贝峰值


- (float)peakPowerForChannel0{
    [self.audioRecorder updateMeters];
    return [self.audioRecorder peakPowerForChannel:0];
}

提示:获得指定声道的分贝峰值,注意如果要获得分贝峰值必须在此之前调用updateMeters方法


二、录音的编辑



  • 2.1、理论基础
  • AVAsset:音频源
  • AVAssetTrack:素材的轨道
  • AVMutableComposition :一个用来合成视频的"合成器"
  • AVMutableCompositionTrack :"合成器"中的轨道,里面可以插入各种对应的素材


  • 2.2、拼接录音


#pragma mark 音频的拼接:追加某个音频在某个音频的后面
/**
   音频的拼接
   @param fromPath 前段音频路径
   @param toPath 后段音频路径
   @param outputPath 拼接后的音频路径
 */
+(void)addAudio:(NSString *)fromPath toAudio:(NSString *)toPath outputPath:(NSString *)outputPath{
    // 1. 获取两个音频源
    AVURLAsset *audioAsset1 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:fromPath]];
    AVURLAsset *audioAsset2 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:toPath]];
    // 2. 获取两个音频素材中的素材轨道
    AVAssetTrack *audioAssetTrack1 = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
    AVAssetTrack *audioAssetTrack2 = [[audioAsset2 tracksWithMediaType:AVMediaTypeAudio] firstObject];
    // 3. 向音频合成器, 添加一个空的素材容器
    AVMutableComposition *composition = [AVMutableComposition composition];
    AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
    // 4. 向素材容器中, 插入音轨素材
    [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset2.duration) ofTrack:audioAssetTrack2 atTime:kCMTimeZero error:nil];
    [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset1.duration) ofTrack:audioAssetTrack1 atTime:audioAsset2.duration error:nil];
    // 5. 根据合成器, 创建一个导出对象, 并设置导出参数
    AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
    session.outputURL = [NSURL fileURLWithPath:outputPath];
    // 导出类型
    session.outputFileType = AVFileTypeAppleM4A;
    // 6. 开始导出数据
    [session exportAsynchronouslyWithCompletionHandler:^{
          AVAssetExportSessionStatus status = session.status;
          /**
             AVAssetExportSessionStatusUnknown,
             AVAssetExportSessionStatusWaiting,
             AVAssetExportSessionStatusExporting,
             AVAssetExportSessionStatusCompleted,
             AVAssetExportSessionStatusFailed,
             AVAssetExportSessionStatusCancelled
           */
          switch (status) {
               case AVAssetExportSessionStatusUnknown:
                  NSLog(@"未知状态");
               break;
               case AVAssetExportSessionStatusWaiting:
                  NSLog(@"等待导出");
               break;
               case AVAssetExportSessionStatusExporting:
                  NSLog(@"导出中");
               break;
               case AVAssetExportSessionStatusCompleted:{
                  NSLog(@"导出成功,路径是:%@", outputPath);
               }
               break;
               case AVAssetExportSessionStatusFailed:
                  NSLog(@"导出失败");
               break;
               case AVAssetExportSessionStatusCancelled:
                  NSLog(@"取消导出");
               break;
               default:
               break;
           }   
     }];  
}
  • 2.3、音频的剪切


/**
   音频的剪切
   @param audioPath 要剪切的音频路径
   @param fromTime 开始剪切的时间点
   @param toTime 结束剪切的时间点
   @param outputPath 剪切成功后的音频路径
  */
+(void)cutAudio:(NSString *)audioPath fromTime:(NSTimeInterval)fromTime toTime:(NSTimeInterval)toTime outputPath:(NSString *)outputPath{
     // 1. 获取音频源
     AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audioPath]];
     // 2. 创建一个音频会话, 并且,设置相应的配置
     AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetAppleM4A];
     session.outputFileType = AVFileTypeAppleM4A;
     session.outputURL = [NSURL fileURLWithPath:outputPath];
    CMTime startTime = CMTimeMake(fromTime, 1);
    CMTime endTime = CMTimeMake(toTime, 1);
    session.timeRange = CMTimeRangeFromTimeToTime(startTime, endTime);
     // 3. 导出
     [session exportAsynchronouslyWithCompletionHandler:^{
          AVAssetExportSessionStatus status = session.status;
          if (status == AVAssetExportSessionStatusCompleted)        
          {
                NSLog(@"导出成功");
          }
     }];
}


三、lame静态库



  • 3.1、lame 静态库简介
  • LAME 是一个开源的MP3音频压缩软件。LAME是一个递归缩写,来自LAME Ain't an MP3 Encoder(LAME不是MP3编码器)。它自1998年以来由一个开源社区开发,目前是公认有损品质MP3中压缩效果最好的编码器
  • Lame 的转码压缩, 是把录制的 PCM 转码成 MP3, 所以录制的 AVFormatIDKey 设置成 kAudioFormatLinearPCM(无损压缩,内容非常大) , 生成的文件可以是 caf 或者 wav.


  • 3.2、如何使用lame
  • 第一步: 下载 lame 的最新版本并解压
  • 第二步: 把下载的 lame 生成静态库,我们使用脚本
  • 下载 build 的脚本
  • 创建一个文件夹放 脚本下载的lame
  • 修改脚本里面的 SOURCE="lame" 名字与 下载的lame名字一致,也可以把 下载的lame名字 改为 lame,那么就不需要改脚本的内容


image.png

  • 改脚本为可执行脚本


chmod +x build-lame.sh
  • 执行脚本


./build-lame.sh


  • 执行脚本的结果如下:生成三个文件


image.png


提示:我们要的是支持多种架构的 fat-lame 文件,把 fat-lame 里面的 lame.hlibmp3lame.a 拖走即可


  • 第三步: 导入静态库到工程, 开始使用,我们把代码都写在 JKLameTool 类里面,具体的分析放在 3.3


  • 3.3、lame 的使用,代码都在 JKLameTool 里面
  • <1>、录完音频 统一 caf 转 mp3,核心代码如下


/**
  caf 转 mp3
  如果录音时间比较长的话,会要等待几秒...
  @param sourcePath 转 mp3 的caf 路径
  @param isDelete 是否删除原来的 caf 文件,YES:删除、NO:不删除
  @param success 成功的回调
  @param fail 失败的回调
*/
+ (void)audioToMP3:(NSString *)sourcePath isDeleteSourchFile: (BOOL)isDelete withSuccessBack:(void(^)(NSString *resultPath))success withFailBack:(void(^)(NSString *error))fail{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 输入路径
        NSString *inPath = sourcePath;
        // 判断输入路径是否存在
        NSFileManager *fm = [NSFileManager defaultManager];
        if (![fm fileExistsAtPath:sourcePath])
        {
              if (fail) {
                 fail(@"文件不存在");
              }
              return;
        }
        // 输出路径
        NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
        @try {
              int read, write;
              //source 被转换的音频文件位置
              FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb");  
              //skip file header
              fseek(pcm, 4*1024, SEEK_CUR);                                   
              //output 输出生成的Mp3文件位置
              FILE *mp3 = fopen([outPath 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, 11025.0);
              lame_set_VBR(lame, vbr_default);
              lame_init_params(lame);
              do {
                 size_t size = (size_t)(2 * sizeof(short int));
                 read = (int)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 {
           if (isDelete) {
                NSError *error;
                [fm removeItemAtPath:sourcePath error:&error];
                if (error == nil)
                {
                   // NSLog(@"删除源文件成功");
                }
            }
            if (success) {
                 success(outPath);
            }
       }
   });
}
  • <2>、caf 转 mp3 : 录音的同时转码,这个是学习OS 使用 Lame 转码 MP3 的最正确姿势,代码结构上在此基础上进行了封装和改进,具体的请看 JKLameTool 类,在此不再重复,核心思想如下:


  • 边录边转码, 只是我们在可以录制后,重新开一个线程来进行文件的转码
  • 当录音进行中时, 会持续读取到指定大小文件,进行编码, 读取不到,则线程休眠
  • 在 while 的条件中, 我们收到 录音结束的条件,则会结束 do while 的循环.
  • 我们需要在录制结束后发送一个信号, 让 do while 跳出循环


四、上面那么的内容封装之后使用方式如下



  • 4.1、导入 #import "JKRecorderKit.h",录音都存在 /Library/Caches/JKRecorder 里面
  • 4.2、使用 JKAudioTool 类进行调用 录音的一系列操作,如下
  • 开始录音


// 目前使用 caf 格式, test2:录音的名字  caf:录音的格式
[[JKAudioTool shareJKAudioTool]beginRecordWithRecordName:@"test2" withRecordType:@"caf" withIsConventToMp3:YES];
  • 完成录音


[[JKAudioTool shareJKAudioTool]endRecord];
  • 暂停录音


[[JKAudioTool shareJKAudioTool]pauseRecord];
  • 删除录音


[[JKAudioTool shareJKAudioTool]deleteRecord];
  • caf 转 mp3,第一个参数是原音频的路径,第二个参数是转换为 MP3 后是否删除原来的路径


[JKLameTool audioToMP3:[cachesRecorderPath stringByAppendingPathComponent:@"test2.caf"] isDeleteSourchFile:YES withSuccessBack:^(NSString * _Nonnull resultPath) {
    NSLog(@"转为MP3后的路径=%@",resultPath);
 } withFailBack:^(NSString * _Nonnull error) {
  NSLog(@"转换失败:%@",error);
 }];

提示:更多的内容请看demo里面的封装


  • 补充:封装类的说明
  • JKLameTool:对 lame静态库的使用
  • JKSingle:单利的封装
  • JKAudioTool:录音的封装
  • JKAudioFileTool:录音文件的操作,音频拼接,剪切,m4a格式转caf格式,caf格式转m4a格式
  • JKAudioPlayerTool:音频的简单播放封装
  • JKAudioFilePathTool:沙盒路径的一些操作


最后:测试的 demo



目录
相关文章
|
iOS开发
iOS布局中的抗被拉伸、抗压缩优先级
iOS布局中的抗被拉伸、抗压缩优先级
731 0
|
iOS开发
iOS App廋身体积压缩之字体图标使用
iOS App廋身体积压缩之字体图标使用
iOS App廋身体积压缩之字体图标使用
|
iOS开发
iOS 多条音频拼接为一条音频进行播放
把多条mp3音频合并为一条保存并进行播放
345 0
|
Web App开发 编解码 Android开发
iOS 视频转码处理
iOS 视频转码处理
|
iOS开发
IOS下利用ziparchive解压缩zip文件
IOS下利用ziparchive解压缩zip文件
582 0
|
编解码 iOS开发
iOS拍摄视频,压缩并上传服务器
iOS拍摄视频,压缩并上传服务器
486 0
|
Web App开发 编解码 iOS开发
iOS解决压缩之后图片模糊的问题
iOS解决压缩之后图片模糊的问题
736 0
iOS解决压缩之后图片模糊的问题
|
XML JSON JavaScript
iOS网络请求指南: 请求参数的拼接(签名)、返回参数解析(JSON/XML解析)、案例(条码支付综合前置平台申请退款)
iOS网络请求指南: 请求参数的拼接(签名)、返回参数解析(JSON/XML解析)、案例(条码支付综合前置平台申请退款)
429 0
iOS网络请求指南: 请求参数的拼接(签名)、返回参数解析(JSON/XML解析)、案例(条码支付综合前置平台申请退款)
|
对象存储 iOS开发
iOS开发:日志记录文件及压缩上传OSS
发现APP上传本地日志文件相当的好用,根据之前研究学习的对这一模块做了一些更具体的优化处理。从标题可以看出,实现这一功能分以下几个步骤: 1、日志记录本地文件 2、日志文件压缩[xx.zip] 3、压缩之后的文件上传 4、压缩文件删除
584 0
|
数据可视化 API iOS开发
EZAudio:一个 iOS 和 OSX 上的音频可视化框架
EZAudio 是一个 iOS 和 OSX 上简单易用的音频框架。 它的官方的页面在 The Official EZAudio
969 0
EZAudio:一个 iOS 和 OSX 上的音频可视化框架