最近项目中需要用到G729的编解码,查了下FFmpeg,发现只支持G729的解码,没有编码,连用第三方支持都没有。于是开始了漫长的找G729编码器之路。
说ITU官网有,大片大片的英文把我吓得不轻;说VoiceAge有封装,去VA官网也没看见;听说是开源的,赶紧去github上搜,也不知道哪个是官方的,反正引用都不多的样子。————我只是想说,这个g729的编码库真的不好找。也不好用。所以,才有了这篇文章。
下载库
最终,我使用了在github上找到的https://github.com/DoubangoTe...。用它的原因,一是我们项目用过doubango,这个名字眼熟。二是,知道咋编译。步骤如下:
./autogen.sh
./configure
make
make install
库编译问题
下载好上面的库,make的时候发现编译不过,说符号bad_lsf
找不到。
库的代码里声明了extern Word16 bad_lsf;
,但是没有定义。解决办法是找一个会编译的.c文件,加如下一行代码:
Word16 bad_lsf =0;
此处应注意,不要把这一行加到coder.c
,decoder.c
这样包含main函数的文件里,因为那些是Demo代码,编译库的时候不编译的。
注: 有答案是好,但知道找到答案的方法更重要。以上解决办法,是在上面的github库的issue里找到的。当使用库有问题的时候,这是一个很好的找答案的地方。
怎么使用编码库
g729库没有文档,甚至,都不能一眼看出来哪个是接口的头文件。不过刚才提到了coder.c
是示例代码,当然是看示例代码学咯。
核心接口如下:
//初始化
Init_Pre_Process();
Init_Coder_ld8a();
Init_Cod_cng();`
//处理数据
Pre_Process(new_speech, L_FRAME);
Coder_ld8a(prm, frame, vad_enable);
prm2bits_ld8k( prm, serial);
简单来说,编码过程分两步。首先从音频数据中提取出一系列参数prm
,然后把参数压缩为二进制串serial
。
编码参数
输入的PCM数据参数:
8K采样率,16位采样深度。一帧为10ms,80个样点。
编码后长度增加,啥?
写好代码就动手测试,发现写出来的文件比编码前的纯pcm还长,啥情况?居然变长了,那还要你何用!网上一查,发现这还是个普遍现象,需要改代码。
原来,prm2bits_ld8k里的处理有问题。加密后需要80个bit,然而prm2bits_ld8k里的处理有问题,每个bit用一个World16来存放,再加上两个World16的头,所以数据不减反增。修复了这个问题,就OK了。压缩比能达到16:1。
编码出来的数据有两个Word16的头,第一个是SYNC_WORD
,用于确认这是个正常g729帧。第二个是编码后数据的长度。
修复代码如下(增加接口函数bits2prm_ld8k_new
, prm2bits_ld8k_new
,和辅助函数byte2bit, bit2byte
。):
Word16 byte2bit(int bitlen,unsigned char * bits,int bitpos)
{
int i;
int bit = 0;
Word16 newbyte = 0;
Word16 value = 0;
unsigned char *p = bits + (bitpos / 8);
for (i = 0 ;i< bitlen;i++)
{
bit = (*p >> (7 - bitpos % 8)) &0x01;
if (bit == 1)
{
newbyte = (1<<(bitlen -i-1));
value |= newbyte;
}
bitpos++;
if(bitpos%8 == 0)
p++;
}
return value;
}
void prm2bits_ld8k_new(
Word16 prm[], /* input : encoded parameters (PRM_SIZE parameters) */
Word16 bits[] /* output: serial bits (SERIAL_SIZE ) bits[0] = bfi
bits[1] = 80 */
)
{
int count = 0;
Word16 i;
*bits++ = SYNC_WORD; /* bit[0], at receiver this bits indicates BFI */
count++;
int bitpos = 0;
switch(prm[0]){
/* not transmitted */
case 0 : {
*bits = RATE_0;
count++;
break;
}
case 1 : {
*bits++ = (RATE_8000 + 15 ) / 16;
count++;
for (i = 0; i < PRM_SIZE; i++) {
bit2byte( prm[i+1], bitsno[i], bits, bitpos );
bitpos += bitsno[i];
// int2bin(prm[i+1], bitsno[i], bits);
// bits += bitsno[i];
}
count += (bitpos + 7 )/8;//上取整
break;
}
case 2 : {
#ifndef OCTET_TX_MODE
*bits++ = RATE_SID;
for (i = 0; i < 4; i++) {
int2bin(prm[i+1], bitsno2[i], bits);
bits += bitsno2[i];
}
#else
*bits++ = (RATE_SID_OCTET+ 15 ) / 16;
for (i = 0; i < 4; i++) {
bit2byte( prm[i+1], bitsno2[i], bits, bitpos );
bitpos += bitsno2[i];
// int2bin(prm[i+1], bitsno2[i], bits);
// bits += bitsno2[i];
}
*bits++ = BIT_0;
#endif
break;
}
default : {
printf("Unrecognized frame type\n");
exit(-1);
}
}
return;
}
void bit2byte(Word16 para,int bitlen,unsigned char * bits,int bitpos)
{
int i;
int bit = 0;
unsigned char newbyte = 0;
unsigned char *p = bits + (bitpos / 8);
for (i = 0 ;i> (bitlen - i -1) ) &0x01;
newbyte = (1 << (7-bitpos%8));
if(bit == 1)
*p |= newbyte;
else
*p &= ~newbyte;
bitpos++;
if (bitpos % 8 == 0)
p++;
}
}
void bits2prm_ld8k_new(
Word16 bits[], /* input : serial bits (80) */
Word16 prm[] /* output: decoded parameters (11 parameters) */
)
{
Word16 i;
Word16 nb_bits;
int bitpos = 0;
nb_bits = *bits++; /* Number of bits in this frame */
if(nb_bits == RATE_8000 / sizeof( Word16 )) {
prm[1] = 1;
prm += 2;
for (i = 0; i < PRM_SIZE; i++) {
// prm[i+2] = bin2int(bitsno[i], bits);
// bits += bitsno[i];
*prm++=byte2bit(bitsno[i],bits,bitpos);
bitpos += bitsno[i];
}
}
else
#ifndef OCTET_TX_MODE
if(nb_bits == RATE_SID) {
prm[1] = 2;
prm += 2;
for (i = 0; i < 4; i++) {
*prm++=byte2bit(bitsno[i],bits,bitpos);
bitpos += bitsno[i];
}
}
#else
/* the last bit of the SID bit stream under octet mode will be discarded */
if(nb_bits == RATE_SID_OCTET) {
prm[1] = 2;
prm += 2;
for (i = 0; i < 4; i++) {
*prm++=byte2bit(bitsno[i],bits,bitpos);
bitpos += bitsno[i];
}
}
#endif
else {
prm[1] = 0;
}
return;
}
FFmpeg解码试试
改了g729的源代码,心里惴惴不安,长度是下去了,格式到底是不是这么约定的还不知道呢。
用FFmpeg测试解码,发现报错:
Packet size 14 is unknown.
查看FFmpeg的g729解码源码(搜ff_g729_decoder,找到文件g729dec.c),发现包大小应该是G729_8K_BLOCK_SIZE(10)
。而我们编码的总长度是14(80个bit,10byte,外加2个World16)。不动脑子一猜,好像不加2个world16的头,长度就正好。于是去掉头,再测,解码,成功了。播放解码结果的pcm,声音正常。到底g729编码就确认OK啦。
参考:
g729编解码的总结