总体内容
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
,那么就不需要改脚本的内容
- 改脚本为可执行脚本
chmod +x build-lame.sh
- 执行脚本
./build-lame.sh
- 执行脚本的结果如下:生成三个文件
提示:我们要的是支持多种架构的 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 : 录音的同时转码,这个是学习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