微信语音格式aud转mp3

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容