歡迎光臨
每天分享高質量文章

iOS 錄音、音訊的拼接剪下以及邊錄邊壓縮轉碼

作者:IIronMan

連結:https://www.jianshu.com/p/1a752b92070b

總體內容

1、錄音實現

2、錄音的編輯 (拼接音訊:可以設定多段,音訊的剪下:按照時間段剪下)

3、lame靜態庫進行壓縮轉碼

一、錄音實現

1.1、匯入 AVFoundation 框架,多媒體的處理, 基本上都使用這個框架

#import 

1.2、使用 AVAudioRecorder 進行錄音,定義一個JKAudioTool 管理錄音的類

(1)、定義一個錄音物件,懶載入

@property (nonatomicstrongAVAudioRecorder *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,那麼就不需要改指令碼的內容

修改指令碼裡面的 `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(00), ^{

        // 輸入路徑
        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 essay-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:沙盒路徑的一些操作

最後:測試的https://github.com/JoanKing/JKRecorderKit

贊(0)

分享創造快樂