微信语音格式aud转mp3

本文参考博客,多谢两位大神:
微信语音文件的解析
从微信中提取语音文件,并转化成文字的全自动化解决方案

iOS端微信的语音格式是aud,这种格式是微信自定义的格式,不能用普通的播放器直接播放,所以需要把aud转化为mp3。
那么aud格式实质上是什么呢?使用hex friend等十六进制编辑器打开一个aud文件,可以看到:

微信语音格式aud转mp3_第1张图片
Snip20180322_4.png

开头是一个0x02的字节,然后就是#!SILK_V3,所以只要把开头的一个字节删掉,就是一个silk文件O(∩_∩)O哈哈~。silk需要先解码为标准音频格式pcm,然后pcm再编码成mp3。

silk->pcm:

这一步使用的是silk-arm-ios,github上有人已经把它编译成静态库,这个静态库只支持arm64架构,也就是说不能在iPhone5以及以下的设备以及模拟器上面运行,不过现在用iPhone5的估计没多少人了吧..。

pcm->mp3:

这一步使用的是lame,也有相应的静态库。

接下来就是调用静态库的方法,把这几部分逻辑串起来:

  1. 去掉aud文件的第一个字节:
NSData *audData = [NSData dataWithContentsOfFile:audPath];
NSMutableData *audDataM = [NSMutableData dataWithData:audData];
    [audDataM replaceBytesInRange:NSMakeRange(0, 1) withBytes:NULL length:0];
  1. silk转pcm:
- (int)covertSilkToPcmWithSilkPath:(NSString *)silkPath pcmPath:(NSString *)pcmPath {
    //得到输入文件和输出文件的路径
    bitInFileName = [silkPath cStringUsingEncoding:NSASCIIStringEncoding];
    speechOutFileName = [pcmPath cStringUsingEncoding:NSASCIIStringEncoding];
    //打开输入文件
    inFile = fopen(bitInFileName, "rb");
    if( inFile == NULL ) {
        printf( "Error: could not open input file %s\n", bitInFileName );
        return -999;
    }
    //验证文件头
    {
        char header_buf[50];
        fread(header_buf, sizeof(char), strlen("#!SILK_V3"), inFile);
        header_buf[strlen("#!SILK_V3")] = '\0';
        if (strcmp(header_buf, "#!SILK_V3") != 0) {
            printf( "Error: Wrong Header %s\n", header_buf );
            return -999;
        }
    }
    // 打开输出文件
    outFile = fopen(speechOutFileName, "wb");
    if (outFile == NULL) {
        printf( "Error: could not open output file %s\n", speechOutFileName );
        return -999;
    }
    // 设置采样率
    if (sampleRate == 0) {
        DecControl.API_sampleRate = 24000;
    } else {
        DecControl.API_sampleRate = sampleRate;
    }
    // 获取 Silk 解码器状态的字节大小
    ret = SKP_Silk_SDK_Get_Decoder_Size(&decSizeBytes);
    if (ret) {
        printf( "\nSKP_Silk_SDK_Get_Decoder_Size returned %d", ret );
    }
    psDec = malloc((size_t) decSizeBytes);
    // 初始化解码器
    ret = SKP_Silk_SDK_InitDecoder(psDec);
    if( ret ) {
        printf( "\nSKP_Silk_InitDecoder returned %d", ret );
    }
    
    totPackets = 0;
    
    while (1) {
        // 读取有效数据大小
        counter = fread(&nBytes, sizeof(SKP_int16), 1, inFile);
        if (nBytes < 0 || counter < 1) {
            break;
        }
        // 读取有效数据
        counter = fread(payload, sizeof(SKP_uint8), (size_t) nBytes, inFile);
        if ((SKP_int16) counter < nBytes) {
            break;
        }
        
        payloadToDec = payload;
        
        outPtr = out;
        tot_len = 0;
        
        frames = 0;
        do {
            // 解码
            ret = SKP_Silk_SDK_Decode(psDec, &DecControl, 0, payloadToDec, nBytes, outPtr, &len);
            if( ret ) {
                printf( "\nSKP_Silk_SDK_Decode returned %d", ret );
            }
            
            frames++;
            outPtr += len;
            tot_len += len;
            if (frames > MAX_INPUT_FRAMES) {
                outPtr = out;
                tot_len = 0;
                frames = 0;
            }
        } while (DecControl.moreInternalDecoderFrames);
        
        packetSize_ms = tot_len / (DecControl.API_sampleRate / 1000);
        totPackets++;
        // 将解码后的数据保存到文件
        fwrite(out, sizeof(SKP_int16), (size_t) tot_len, outFile);
    }
    
    free(psDec);
    
    fclose(outFile);
    fclose(inFile);
    
    fileLength = totPackets * 1e-3 * packetSize_ms;
    
    return 0;
}
  1. pcm->mp3:
- (int)covertPcmToMp3WithPcmPath:(NSString *)pcmPath mp3Path:(NSString *)mp3Path {
    int state = -999;
    @try {
        int read, write;
        
        FILE *pcm = fopen([pcmPath cStringUsingEncoding:NSASCIIStringEncoding], "rb");  //source
        fseek(pcm, 4*1024, SEEK_CUR);                                   //skip file header
        FILE *mp3 = fopen([mp3Path cStringUsingEncoding:NSASCIIStringEncoding], "wb");  //output
        
        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_num_channels(lame, 2); // 双声道
        lame_set_in_samplerate(lame, 12000); // 12k采样率
        lame_set_brate(lame, 50);  // 压缩的比特率为50
        lame_set_quality(lame, 1);  // mp3音质,很好
        lame_init_params(lame);
        
        do {
            read = (int)fread(pcm_buffer, 2*sizeof(short int), 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);
        state = 0;
    }
    @catch (NSException *exception) {
        state = -999;
    }
    @finally {
        return state;
    }
}

虽然吴航大神说把这几部分逻辑串起来一点都不难,但我还是花了一天时间...
所以我把代码封装了一下,后来者只需要调用一个方法就能成功转化了。github地址见文末。

首先把AudioTool文件夹拖入项目中,再添加libSKP_SILK_SDK.a和libmp3lame.a的链接,编译后会报错:

/AudioTool/libSKP_SILK_SDK.a(SKP_Silk_dec_API.o)' does not contain bitcode. 
You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), 
obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64

因为这个libSKP_SILK_SDK.a不支持bitcode,所以要把bitcode关掉:

微信语音格式aud转mp3_第2张图片
20150917113544983.jpg

你如果不是用Xcode开发,或许需要这个:

XXX_LDFLAGS += -lmp3lame -lSKP_SILK_SDK
XXX_LDFLAGS += -read_only_relocs suppress

导入头文件"WXAudioManager.h",然后调用如下方法:

/*
 audPath:aud文件路径
 mp3Path:转化后mp3文件的存放路径
 */
- (NSError *)covertAudToMp3WithAudPath:(NSString *)audPath mp3Path:(NSString *)mp3Path;

这是一个耗时操作,需要在子线程调用。
如果转化成功,会在指定mp3Path生成一个mp3文件,并且返回nil。如果转化失败会返回错误原因和错误码,具体的错误还需要看lame.h和SKP_Silk_errors.h

如果你想调整转化后的音质和大小,可以在PCMEncoder.m里改变下面的参数:

lame_t lame = lame_init(); // 初始化
        lame_set_num_channels(lame, 2); // 双声道
        lame_set_in_samplerate(lame, 12000); // 12k采样率
        lame_set_brate(lame, 50);  // 压缩的比特率为50
        lame_set_quality(lame, 1);  // mp3音质,很好
        lame_init_params(lame);

测试用的aud文件大小是7KB,转化后要达到和原来差不多的音质,mp3文件大小要达到24KB,这就是微信为什么要用aud的原因了吧。

献上github:
https://github.com/linzhesheng/AudConvertMp3.git

你可能感兴趣的:(微信语音格式aud转mp3)