美文网首页iOS 开发~项目常用,经典内容收集iOS技术专题Temp
iOS中使用lame将PCM文件转换成MP3(边录边转)

iOS中使用lame将PCM文件转换成MP3(边录边转)

作者: fanly1987444 | 来源:发表于2015-12-03 17:12 被阅读6920次

    在iOS中使用AVAudioRecorder无法录制MP3格式的音频文件,虽然你可能会看到过这样一个枚举:

        CF_ENUM(AudioFormatID)
      {
          kAudioFormatLinearPCM               = 'lpcm',
          kAudioFormatAC3                     = 'ac-3',
          kAudioFormat60958AC3                = 'cac3',
          kAudioFormatAppleIMA4               = 'ima4',
          kAudioFormatMPEG4AAC                = 'aac ',
          kAudioFormatMPEG4CELP               = 'celp',
          kAudioFormatMPEG4HVXC               = 'hvxc',
          kAudioFormatMPEG4TwinVQ             = 'twvq',
          kAudioFormatMACE3                   = 'MAC3',
          kAudioFormatMACE6                   = 'MAC6',
          kAudioFormatULaw                    = 'ulaw',
          kAudioFormatALaw                    = 'alaw',
          kAudioFormatQDesign                 = 'QDMC',
          kAudioFormatQDesign2                = 'QDM2',
          kAudioFormatQUALCOMM                = 'Qclp',
          kAudioFormatMPEGLayer1              = '.mp1',
          kAudioFormatMPEGLayer2              = '.mp2',
          kAudioFormatMPEGLayer3              = '.mp3',
          kAudioFormatTimeCode                = 'time',
          kAudioFormatMIDIStream              = 'midi',
          kAudioFormatParameterValueStream    = 'apvs',
          kAudioFormatAppleLossless           = 'alac',
          kAudioFormatMPEG4AAC_HE             = 'aach',
          kAudioFormatMPEG4AAC_LD             = 'aacl',
          kAudioFormatMPEG4AAC_ELD            = 'aace',
          kAudioFormatMPEG4AAC_ELD_SBR        = 'aacf',
          kAudioFormatMPEG4AAC_ELD_V2         = 'aacg',    
          kAudioFormatMPEG4AAC_HE_V2          = 'aacp',
          kAudioFormatMPEG4AAC_Spatial        = 'aacs',
          kAudioFormatAMR                     = 'samr',
          kAudioFormatAMR_WB                  = 'sawb',
          kAudioFormatAudible                 = 'AUDB',
          kAudioFormatiLBC                    = 'ilbc',
          kAudioFormatDVIIntelIMA             = 0x6D730011,
          kAudioFormatMicrosoftGSM            = 0x6D730031,
          kAudioFormatAES3                    = 'aes3',
          kAudioFormatEnhancedAC3             = 'ec-3'
      };
    

    也许你也使用了以下代码设置了AVAudioRecorder:

      NSDictionary *settings =
          @{AVSampleRateKey:@44100,
            AVFormatIDKey:[NSNumber numberWithInt:kAudioFormatMPEGLayer3],
            AVNumberOfChannelsKey:@2,
            AVEncoderAudioQualityKey:[NSNumber numberWithInt:self.quality]};
        
          _recorder = [[AVAudioRecorder alloc] initWithURL:tempFile settings:settings error:&recorderError];
    

    然而上面的代码并没有什么卵用,当你开始录音后也没有什么错误,然后待录音完毕后你会发现录音文件的长度为0。根本没有mp3文件生成。还是乖乖地录制PCM格式的音频,然后再转码成mp3格式。本文的重点不在于讲如何录制PCM音频文件,也不讲解lame的下载和build过程,这些网络上都能搜索到,而是在于如何在录音开始的过程将PCM文件转码成mp3文件。

    先展示一下关键代码部分:

      - (void)conventToMp3 {
        
          DWDLog(@"convert begin!!");
        
          NSString *cafFilePath = [NSTemporaryDirectory() stringByAppendingString:@"VoiceInputFile"];
          _mp3FileName = [[NSUUID UUID] UUIDString];
          self.mp3FileName = [self.mp3FileName stringByAppendingString:@".mp3"];
          NSString *mp3FilePath = [[NSHomeDirectory() stringByAppendingFormat:@"/Documents/"] stringByAppendingPathComponent:self.mp3FileName];
        
          @try {
            
              int read, write;
            
              FILE *pcm = fopen([cafFilePath cStringUsingEncoding:NSASCIIStringEncoding], "rb");
              FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:NSASCIIStringEncoding], "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, self.sampleRate);
              lame_set_VBR(lame, vbr_default);
              lame_init_params(lame);
            
              long curpos;
              BOOL isSkipPCMHeader = NO;
            
              do {
                
                  curpos = ftell(pcm);
                
                  long startPos = ftell(pcm);
                
                  fseek(pcm, 0, SEEK_END);
                  long endPos = ftell(pcm);
                
                  long length = endPos - startPos;
                
                  fseek(pcm, curpos, SEEK_SET);
                
                
                  if (length > PCM_SIZE * 2 * sizeof(short int)) {
                    
                      if (!isSkipPCMHeader) {
                          //Uump audio file header, If you do not skip file header
                          //you will heard some noise at the beginning!!!
                          fseek(pcm, 4 * 1024, SEEK_SET);
                          isSkipPCMHeader = YES;
                          DWDLog(@"skip pcm file header !!!!!!!!!!");
                      }
                    
                      read = (int)fread(pcm_buffer, 2 * sizeof(short int), PCM_SIZE, pcm);
                      write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
                      fwrite(mp3_buffer, write, 1, mp3);
                      DWDLog(@"read %d bytes", write);
                  }
                
                  else {
                    
                      [NSThread sleepForTimeInterval:0.05];
                      DWDLog(@"sleep");
                    
                  }
    
              } while (!self.isStopRecorde);
            
              read = (int)fread(pcm_buffer, 2 * sizeof(short int), PCM_SIZE, pcm);
              write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
              DWDLog(@"read %d bytes and flush to mp3 file", write);
    
              lame_close(lame);
              fclose(mp3);
              fclose(pcm);
            
              self.isFinishConvert = YES;
          }
          @catch (NSException *exception) {
              DWDLog(@"%@", [exception description]);
          }
          @finally {
              DWDLog(@"convert mp3 finish!!!");
          }
      }
    

    对于熟悉C语言的大神来说,这根本没有什么难度。

    这个方法的大体思路就是在录音没有完成前,我们循环读取PCM文件,当读取到的字节大于我们规定的一个单位后,我们将这些字节交给lame,lame会把转码后的二进制数据输出到目标MP3文件里。

    代理有很多开关变量,那都是一个BOOL类型的自定义变量,并没有什么特别。

    如果看不懂上面的代码,我建议你先搞清楚这段代码用的几个函数的意思,这段代码是实现边录边播的关键。

      curpos = ftell(pcm);
      long startPos = ftell(pcm);          
      fseek(pcm, 0, SEEK_END); 
      long endPos = ftell(pcm);
      long length = endPos - startPos;
      fseek(pcm, curpos, SEEK_SET);
    

    代码唯一有注释的部分是为了跳过PCM的header部分,正如注释所说,如果不跳过这一部分,转换成的mp3在播放的最初一秒内会听到一个明显的噪音。

    那么这个方法应该怎么用才能实现边录边转呢?只需要像下面这样:

      ...
      self.recorder.meteringEnabled = YES;
      [self.recorder prepareToRecord];
      [self.recorder record];
      
    self.status = DWDAudioClientStatusRecording;
       _timer = [NSTimer scheduledTimerWithTimeInterval:.2
                          target:self selector:@selector(recordTimerUpdate)
                          userInfo:nil repeats:YES];
        
              dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              [self conventToMp3];
          });
      ...
    

    我们只需要在录音开始后,另起一个线程去完成转码的工作。

    这里也讲一个AVAudioRecorder的一些大坑,我们在录音和播放的过程中,经常要获取音量,还有录音时间的长短。

    而大坑之一就是有的时候你很可能获取不到正确的音量,而原因可能就是你设置AVAudioSession的方法和设置AVAudioRecorder的代码没有放到同一个方法内。我之前就是因为这个原因而获取不到正确音量(即metering)。

    另外一个坑就是,AVAudioRecorder在录音完成之后并不能获取到录音的时长,这个处理起来比较简单,我们可以在录音开始的时候和结束的时候获取时间,然后相减即可得到时长。像下面这样:

      ...  
      self.audioStartRecodeTime = CACurrentMediaTime();
      ...
      self.audioEndRecodeTime = CACurrentMediaTime();
    

    这里需要提醒一点,iOS中有很多获取时间的方法,而计算时间最好用上面的方法,至于原因请自己Google。

    想要完整源代码的同学可以直接问我索要,QQ2541251578

    相关文章

      网友评论

      • 连命都给你了:read = fread(pcm_buffer, 2 * sizeof(leng), PCM_SIZE, pcm);崩溃到这行 提示Thread 1: EXC_BAD_ACCESS (code=1, address=0x68);
        这行提示警告Implicit conversion loses integer precision: 'size_t' (aka 'unsigned long') to 'int'
        咋解决啊?
      • 夜神月No1:总是崩溃在curpos = ftell(pcm);这一行,是怎么回事
        84f8d661e4a8:找到问题了 你把 FILE *pcm = fopen([cafFilePath cStringUsingEncoding:NSUTF8StringEncoding], "rb"); 编码换为utf8试试
        84f8d661e4a8:我也是
      • 小vv:lame.h里 第100行的报错 楼主碰到过吗?
        lame.h:101:5: Redefinition of enumerator 'V2'
        lame.h:103:5: Redefinition of enumerator 'V1'
      • 爩龘:如果边录边转 我暂停了之后 怎么处理当前的录音
      • NieFeng1024:求demo,谢谢楼主
      • 凯文Kevin21:楼主,我想要你得这个Demo代码,,我加你QQ了, 同意一下。谢谢。。
        凯文Kevin21:@无谓的执恋 没有,demo也没要到。。
        6b9801f1b8f7:@七秒小鱼人 你要到demo了吗,你用他上面的代码实现了吗
        NieFeng1024:@七秒小鱼人 找到demo没?哥们。有的话方便发一份吗?550872569,谢谢。
      • Gxpzy: :+1:
        c1e0f39af20a:@Gxpzy能发一下demo吗
        Gxpzy:@无谓的执恋 我做的是录完转,修改了一点,有什么问题你可以问博主,他很热心的。
        6b9801f1b8f7:@Gxpzy你用他上面的代码实现了吗

      本文标题:iOS中使用lame将PCM文件转换成MP3(边录边转)

      本文链接:https://www.haomeiwen.com/subject/isrshttx.html