iOS pcm转mp3出现尖锐声

项目中需要将pcm音频文件转码为mp3格式后上传, 在网上查找转码mp3的资料, 应用时转码出来的mp3音频声音比较尖锐(像机器声音/女声), 最后发现是声道数不统一导致的.

项目中使用的单声道, 16000的采样率进行录音, 网上资料默认使用的 lame_encode_buffer_interleaved 这个方法进行转码, 导致出现上述问题

lame.h 中说明如下:

/*
 * input pcm data, output (maybe) mp3 frames.
 * This routine handles all buffering, resampling and filtering for you.
 *
 * return code     number of bytes output in mp3buf. Can be 0
 *                 -1:  mp3buf was too small
 *                 -2:  malloc() problem
 *                 -3:  lame_init_params() not called
 *                 -4:  psycho acoustic problems
 *
 * The required mp3buf_size can be computed from num_samples,
 * samplerate and encoding rate, but here is a worst case estimate:
 *
 * mp3buf_size in bytes = 1.25*num_samples + 7200
 *
 * I think a tighter bound could be:  (mt, March 2000)
 * MPEG1:
 *    num_samples*(bitrate/8)/samplerate + 4*1152*(bitrate/8)/samplerate + 512
 * MPEG2:
 *    num_samples*(bitrate/8)/samplerate + 4*576*(bitrate/8)/samplerate + 256
 *
 * but test first if you use that!
 *
 * set mp3buf_size = 0 and LAME will not check if mp3buf_size is
 * large enough.
 *
 * NOTE:
 * if gfp->num_channels=2, but gfp->mode = 3 (mono), the L & R channels
 * will be averaged into the L channel before encoding only the L channel
 * This will overwrite the data in buffer_l[] and buffer_r[].
 *
*/
int CDECL lame_encode_buffer (
        lame_global_flags*  gfp,           /* global context handle         */
        const short int     buffer_l [],   /* PCM data for left channel     */
        const short int     buffer_r [],   /* PCM data for right channel    */
        const int           nsamples,      /* number of samples per channel */
        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
        const int           mp3buf_size ); /* number of valid octets in this
                                              stream                        */

/*
 * as above, but input has L & R channel data interleaved.
 * NOTE:
 * num_samples = number of samples in the L (or R)
 * channel, not the total number of samples in pcm[]
 */
int CDECL lame_encode_buffer_interleaved(
        lame_global_flags*  gfp,           /* global context handlei        */
        short int           pcm[],         /* PCM data for left and right
                                              channel, interleaved          */
        int                 num_samples,   /* number of samples per channel,
                                              _not_ number of samples in
                                              pcm[]                         */
        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
        int                 mp3buf_size ); /* number of valid octets in this
                                              stream                        */

lame_encode_buffer_interleaved 左右声道数据交替传入进行转码

转码核心代码如下:

/***
 * pcm 文件转mp3文件
 */
- (BOOL)convertPcm:(NSString *)pcmPath toMp3:(NSString *)mp3Path {
    @try {
        FILE *fpcm = fopen([pcmPath cStringUsingEncoding:NSASCIIStringEncoding], "rb");
        if (fpcm == NULL) {
            return false;
        }
//        fseek(fpcm, 1024*4, SEEK_CUR); //跳过源文件的信息头,不然在开头会有爆破音
        FILE *fmp3 = fopen([mp3Path cStringUsingEncoding:NSASCIIStringEncoding], "wb");
        
        int channelCount = 1;   // 声道数, 跟录音时配置一样的
        
        lame = lame_init();
        lame_set_in_samplerate(lame, 16000); //设置采样率, 需要跟录音时的采样率相同
        lame_set_num_channels(lame, channelCount); //声道,不设置默认为双声道
        lame_set_VBR(lame, vbr_default);
//        lame_set_mode(lame, 0);
        lame_set_quality(lame, 2);
        lame_init_params(lame);
        
        const int PCM_SIZE = 8192;//
        const int MP3_SIZE = 8192; //
        short int pcm_buffer[PCM_SIZE*channelCount];
        unsigned char mp3_buffer[MP3_SIZE];
        
        int read, write;
        do {
            read = fread(pcm_buffer, channelCount*sizeof(short int), PCM_SIZE, fpcm);
            if (read == 0) {
                write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            } else {
                if (channelCount == 1) {
                    write = lame_encode_buffer(lame, pcm_buffer, NULL, read, mp3_buffer, MP3_SIZE); // 单声道音频转码
                } else {
                    write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE); // 多声道音频转码
                }
            }
            fwrite(mp3_buffer, write, 1, fmp3);
        } while (read != 0);
        lame_mp3_tags_fid(lame, fmp3);
        lame_close(lame);
        fclose(fmp3);
        fclose(fpcm);
    } @catch (NSException *exception) {
        NSLog(@"catch exception, %@", exception);
        return false;
    } @finally {
        return true;
    }
}

推荐阅读更多精彩内容