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,那麼就不需要改腳本的內容

      • 改腳本爲可執行腳本

        chmod +x build-lame.sh
        
      • 執行腳本

        ./build-lame.sh
        
      • 執行腳本的結果如下:生成三個文件


        提示:我們要的是支持多種架構的 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 使用 Lame 轉碼 MP3 的最正確姿勢
ios多媒體
iOS音頻格式m4a、caf、amr的相互轉換
iOS音軌合成(音頻與音頻,音頻與視頻)
iOS音頻錄製
iOS 實時錄音和播放
iOS之錄音功能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章