乐趣区

G729编解码

最近项目中需要用到 G729 的编解码,查了下 FFmpeg, 发现只支持 G729 的解码,没有编码,连用第三方支持都没有。于是开始了漫长的找 G729 编码器之路。
说 ITU 官网有,大片大片的英文把我吓得不轻; 说 VoiceAge 有封装,去 VA 官网也没看见; 听说是开源的,赶紧去 github 上搜,也不知道哪个是官方的,反正引用都不多的样子。————我只是想说,这个 g729 的编码库真的不好找。也不好用。所以,才有了这篇文章。

下载库

最终,我使用了在 github 上找到的 https://github.com/DoubangoTe…。用它的原因,一是我们项目用过 doubango,这个名字眼熟。二是,这里面有 makefile, 可以直接./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 还长,啥情况?居然变长了,那还要你何用!网上一查,发现这还是个普遍现象,需要改代码。
原来,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++)
  {bit = (para >> (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 编解码的总结

退出移动版