微信的语音文件保存在voice2目录下,格式是amr,取出来却不能播放,推测是使用了其他编码。使用十六进制查看amr文件,发现文件头为“#!SILK_V3”。
amr文件头

查找资料发现该文件是使用了skype开源的silk encoder音频编码。如果要转码为mp3文件,需要先解码得到原始音频文件pcm,再以mp3格式进行编码。

解码

在github上找到silk项目。然后编译Decoder.c,得到可执行文件,用它对amr文件解码却失败,提示

1
Error: Wrong Header !SILK_V3

查看检查文件头代码

1
2
3
4
5
6
7
8
9
10
11
/* Check Silk header */
{
char header_buf[ 50 ];
counter = fread( header_buf, sizeof( char ), strlen( "#!SILK_V3" ), bitInFile );
header_buf[ strlen( "#!SILK_V3" ) ] = '\0'; /* Terminate with a null character */
if( strcmp( header_buf, "#!SILK_V3" ) != 0 ) {
/* Non-equal strings */
printf( "Error: Wrong Header %s\n", header_buf );
exit( 0 );
}
}

而微信amr文件头在“#!SILK_V3”前有一个值为0x02的字节,所以我们将检查文件头的代码改下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Check Silk header */
{
char header_buf[ 50 ];
int c = fgetc(bitInFile);
// 微信amr文件头“#!SILK_V3”前有一个值为0x02的字节
const char * HEADER = c != 0x23?"#!SILK_V3":"!SILK_V3";
counter = fread( header_buf, sizeof( char ), strlen(HEADER), bitInFile );
header_buf[ strlen(HEADER) ] = '\0'; /* Terminate with a null character */
if( strcmp( header_buf, HEADER ) != 0 ) {
/* Non-equal strings */
printf( "Error: Wrong Header %s\n", header_buf );
exit( 0 );
}
}

然后编译并执行

1
Silk2MP3 msg_261050062419bb5255bd637101.amr msg_261050062419bb5255bd637101.pcm

成功得到pcm文件。
使用命令

1
ffmpeg -y -f s16le -ar 24000 -ac 1 -i msg_261050062419bb5255bd637101.pcm msg_261050062419bb5255bd637101.mp3

成功得到mp3文件,播放正常。说明我们的解码没问题。

编码mp3

这里我们使用开源项目lame对pcm进行mp3编码。
使用源码编译发现老是编译不过,而lame提供了动态库,直接拿来用就行了。

1
brew install lame

linux下

1
yum install lame-devel.x86_64 lame-libs.x86_64

lame.h头文件在目录/usr/local/include/lame/下,添加到header search path就可以了。编译时加入链接指令-lmp3lame

创建encode_mp3.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "encode_mp3.h"
#include <sys/stat.h>
#include <lame.h>

int main(int argc, const char* argv[])
{
if(argc==3){
encode_pcm_file(argv[1], argv[2]);
}else{
printf("%s pcmIn mp3Out\n", argv[0]);
}
return 0;
}

/*
编码pcm字节
return: 编码后的mp3字节数
*/
int pcm2mp3(char* pcm_bytes, int n){
lame_t lame = lame_init();
// 采样率 24kHz
lame_set_in_samplerate(lame, 24000);
// 单声道
lame_set_num_channels(lame, 1);
lame_set_mode(lame, MONO);
// 音质,值越低质量越高,编码越慢
lame_set_quality(lame, 5);
lame_init_params(lame);

int size = n/2 + n%2;
short int pcms[size];
memcpy(pcms, pcm_bytes, n);
unsigned char mp3_buffer[n];
int encoded = lame_encode_buffer(lame, pcms, NULL, size, mp3_buffer, n);
memcpy(pcm_bytes, mp3_buffer, encoded);
int last = lame_encode_flush(lame, mp3_buffer, n);
if(last>0){
for(int i=0; i<last; i++){
pcm_bytes[++encoded] = mp3_buffer[i];
}
}
lame_close(lame);
return encoded;
}

/*
将pcm文件转为mp3文件
*/
void encode_pcm_file(const char * inFileName, const char * outFileName){
FILE * inFile = fopen(inFileName, "rb");
struct stat stat_buf;
stat(inFileName, &stat_buf);
int len = (int)stat_buf.st_size;
char bytes[len];
fread(bytes, sizeof(char), len, inFile);
fclose(inFile);
printf("pcm file len: %d\n", len);

int mp3_len = pcm2mp3(bytes, len);
printf("convert finished, result len: %d\n", mp3_len);

FILE * outFile = fopen(outFileName, "wb");
fwrite(bytes, sizeof(char), mp3_len, outFile);
fclose(outFile);
printf("mp3 file saved: %s\n", outFileName);
}

编译该文件,得到pcm转mp3的程序,可以将上面得到的pcm转为mp3。

合并成一个程序

创建main.c,将上面两个步骤合并即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#include <stdio.h>
#include "Decoder.h"
#include "encode_mp3.h"
#include <string.h>

int main(int argc, const char * argv[]){
if(argc != 3){
printf("Usage: %s silkInFile mp3OutFile\n", argv[0]);
return 0;
}
// decode
const char * slkInFile = argv[1];
const char * mp3OutFile = argv[2];
char pcmTmpFile[strlen(slkInFile) + 5];
sprintf(pcmTmpFile, "%s%s", slkInFile, ".pcm");
printf("decoding slk to file: %s\n", pcmTmpFile);
int ret = decode(slkInFile, pcmTmpFile);
if(ret != 0){
printf("deocde err!!! abort\n");
return -1;
}
printf("converting to mp3: %s\n", mp3OutFile);
encode_pcm_file(pcmTmpFile, mp3OutFile);
printf("ok, deleting tmp file\n");
remove(pcmTmpFile);
}

使用clang编译

1
clang SLK2MP3/*.c silk/*.c -o slk2mp3 -I/usr/local/include/lame/ -Isilk -ISLK2MP3 -Llib -lmp3lame

注意在编译时可能会出如下错误

1
2
3
1 warning generated.
ld: can't open output file for writing: slk2mp3, errno=21 for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

这是因为参数-o slk2mp3指定的输出文件在当前目录中有同名文件夹(大写的也不行)导致冲突无法创建,改个名字就行了。

1
clang SLK2MP3/*.c silk/*.c -o slk2mp3converter -I/usr/local/include/lame/ -Isilk -ISLK2MP3 -Llib -lmp3lame

下载

这里有编译好的linux/macos程序,有需要的可以直接拿去用,windows下可使用silk-v3-decoder
用到的测试文件test_amr.zip

本文首发于www_wisedream_net,转载请注明出处!!!