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

栏目: IOS · 发布时间: 7年前

内容简介:总体内容1、录音实现2、录音的编辑 (拼接音频:可以设置多段,音频的剪切:按照时间段剪切)

总体内容

1、录音实现

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

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

一、录音实现

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

#import 
<avfoundation avfoundation="" h=""></avfoundation>

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,那么就不需要改脚本的内容

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

修改脚本里面的 `SOURCE="lame"` 名字与 下载的lame名字一致,也可以把 下载的lame名字 改为 `lame`,那么就不需要改脚本的内容

改脚本为可执行脚本

chmod +x build-lame.sh

执行脚本

./build-lame.sh

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

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

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

提示:我们要的是支持多种架构的 fat-lame 文件,把 fat-lame 里面的 lame.h 与 libmp3lame.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 : 录音的同时转码,这个是学习 iOS 使用 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

推荐博客如下:

作者:IIronMan

链接:https://www.jianshu.com/p/1a752b92070b


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Ruby

Programming Ruby

Dave Thomas、Chad Fowler、Andy Hunt / Pragmatic Bookshelf / 2004-10-8 / USD 44.95

Ruby is an increasingly popular, fully object-oriented dynamic programming language, hailed by many practitioners as the finest and most useful language available today. When Ruby first burst onto the......一起来看看 《Programming Ruby》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具