總體內容
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
推薦博客如下:
iOS 使用 Lame 轉碼 MP3 的最正確姿勢
ios多媒體
iOS音頻格式m4a、caf、amr的相互轉換
iOS音軌合成(音頻與音頻,音頻與視頻)
iOS音頻錄製
iOS 實時錄音和播放
iOS之錄音功能