美文网首页
微信语音格式aud转mp3

微信语音格式aud转mp3

作者: 主动打电话 | 来源:发表于2018-03-22 15:16 被阅读0次

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

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

    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关掉:

    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

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