音频编解码之G719集成

最近在适配G719编解码,这是ITU-T于2008年发布的第一个全频带20-20kHz音频编码器,处理采样频率48kHz,位深16,单声道PCM信号,编码器帧长20ms,理论延时40ms。

不得不说百度搜索真是太恶心了,搜半天搜不到,谷歌第一位就是源码。G719链接,学会上网即可下载。

G719编译

代码压缩包解压后由四个文件:
解压包
分别是int版本代码,float版本代码、论文word版本和pdf版本。
原理部分这里不做介绍,后边看情况,主要是研究一下代码的编译。

Fix-point-200806-Release-1.0a

编译脚本,源码中提供两个版本,分别是gcc和vs版本,由于常用的linux运行,所以直接介绍一下gcc下编译。

  • vs版本相对简单,导入vs,设置对应sdk版本,就能编译。

linux环境

没啥要求,虚拟机、笔记本、云服务器均可,带有cmake就行。

编译

其实说明文档中已经说的十分详细了,直接执行make命令进行编译。

cd /home/ubuntu/Fix-point-200806-Release-1.0a/gcc
make -f makefile.gcc

生成的文件在Fix-point-200806-Release-1.0a/out文件夹中:encoderdecoder.

demo运行

源码提供了运行demo,位于test_vectors文件夹中:test_bitexact_vectors.bash
注意,直接执行可能报一堆错,改一下文件编码就ok。
执行报错
vscode打开,右下角CRLF切换为LF即可运行。
运行结果
查看一下脚本,可以总结出来使用规则:

encoder -r 码率 -i 输入pcm -o 输出编码文件
decoder -o 输出解码pcm -i 输入编码文件

可能会疑惑,解码为啥不需要码率?
答:码率信息会存储在编码的每一帧里面,解码只需要编码帧数据即可。

官方提供的demo文件能跑起来,接下来就是集成了。

G719集成

编译动态库

集成到应用里一般以库的形式,一般是动态库,官方提供的编译是编译二进制,因此需要对编译文件进行调整。

提供一个简单的cmake,该文件在gcc同级目录中即可:

cmake_minimum_required (VERSION 3.8)
project ("g719")
add_compile_options(-fPIC)
add_definitions("-Wall -g -w -O3")
set(G719_ENCODE_SRC_SET 
	../src/encoder/bitallocsum.c
	../src/encoder/code2idx.c
	../src/encoder/detect_transient.c
	../src/encoder/diffcod.c
	../src/encoder/encode_frame.c
	../src/encoder/encoder_init.c
	../src/encoder/encoder_rom.c
	../src/encoder/flvqenc.c
	../src/encoder/huffcheck.c
	../src/encoder/logqnorm.c
	../src/encoder/lvq1.c
	../src/encoder/lvq2.c
	../src/encoder/noise_adjust.c
	../src/encoder/normalizecoefs.c
	../src/encoder/packingc.c
	../src/encoder/procnf.c
	../src/encoder/procnobitsbfm.c
	../src/encoder/qcoefs.c
	../src/encoder/reordernorm.c
	../src/encoder/trans_direct.c
	../src/encoder/wtda.c
)

set(G719_DECODE_SRC_SET
	../src/decoder/decode_frame.c
	../src/decoder/decoder_init.c
	../src/decoder/decoder_rom.c
	../src/decoder/dprocnf.c
	../src/decoder/dprocnobitsbfm.c
	../src/decoder/dqcoefs.c
	../src/decoder/fill_spectrum.c
	../src/decoder/flvqdec.c
	../src/decoder/hdeclvq.c
	../src/decoder/hdecnrm.c
	../src/decoder/trans_inv.c
	../src/decoder/unpackc.c
	../src/decoder/window_ola.c
)

set(G719_BASICOP_SRC_SET
	../basicop/basop32.c
	../basicop/control.c
	../basicop/count.c
	../basicop/enh1632.c
	../basicop/enh40.c
)

set(G719_COMMON_SRC_SET
	../src/common/bitalloc.c
	../src/common/bitstream.c
	../src/common/codesearch.c
	../src/common/common_rom.c
	../src/common/complxop.c
	../src/common/dct.c
	../src/common/idx2code.c
	../src/common/interleave_spectrum.c
	../src/common/recovernorm.c
	../src/common/reordvct.c
	../src/common/weight.c
)

include_directories(
	../basicop/
	../src/common/
	../src/encoder/
	../src/include/
)
add_library (g719 SHARED ${G719_ENCODE_SRC_SET} ${G719_DECODE_SRC_SET} ${G719_COMMON_SRC_SET} ${G719_BASICOP_SRC_SET})

通过cmake编译可以编译出so库。

这里去掉了两个main函数所在的文件src/decoder/decoder.c和src/encoder/encoder.c,这两个是官方提供的示例。

链接

cmake_minimum_required (VERSION 3.8)
project ("g719so")
add_compile_options(-fPIC)
add_definitions("-Wall -g -w -O3")
set(G719_SRC_FILE_SET 
	../encoder.c
)

include_directories(
	./basicop/
	./src/common/
	./src/encoder/
	./src/include/
)
link_directories(./gcc/)
add_executable (g719so SHARED ${G719_SRC_FILE_SET})
target_link_libraries(g719so g719)

只编译encoder即可。

用例分析

相对来说,使用起来非常简单,一共就只有两个接口函数:

  1. void encoder_init(CoderState *c, Word16 num_bits):初始化数据,其中num_bits可以理解为20ms的编码数据量,基本上是(比特率/50)。
  2. void encode_frame(Word16 *audio, Word16 num_bits, Word16 *bitstream, CoderState *c):编码函数:audio为输入,bitstream为输出,c是初始化的状态结构体,num_bits同上,也可不传。

具体用例:

int main(int argc, char *argv[])
{

   FILE     *fp_in;
   FILE     *fp_out;
   Word32   frame;
   char     *in_filename  = "";
   char     *out_filename = "";
   Word32   rate;
   Word16   num_bits;
   Word16   quiet = FALSE;
   Word16   in[FRAME_LENGTH];
   Word16   bitstream[MAX_BITS_PER_FRAME];
   Word16   samples;
   CoderState		c; //存储编解码状态数据
   banner();
   parse_cmdline(argc, argv, &in_filename, &out_filename, &rate, &quiet);
   fp_in = fopen(in_filename,"rb");
   if(fp_in == NULL) 
   {
      fprintf(stderr, "\n Error opening input file %s",in_filename);
      exit(EXIT_FAILURE);   
   }
   if((rate > 128000) || (rate < 32000)) //必须是32-128k且是4k的倍数
   {
      fprintf(stderr,"\n Invalid bitrate");
      exit(EXIT_FAILURE);   
   }
   num_bits = (Word16)(rate/50);
   fp_out = fopen(out_filename,"wb");
   if(fp_out == NULL) 
   {
      fprintf(stderr, "\n Error opening output raw file");
      exit(EXIT_FAILURE);   
   }
   encoder_init(&c,num_bits);//初始化
   frame = 0;
   do 
   {
      samples = read_data(fp_in, in, FRAME_LENGTH);
      encode_frame(in, num_bits, bitstream, &c);//编码
      write_bitstream(fp_out, bitstream, num_bits);
      if (!quiet)
         fprintf(stderr,"Rate: %5.2f kbps Processed: %d frames \r", ((float)num_bits*50.0)/1000.0f, frame); frame++;
   } while (samples == FRAME_LENGTH);
   fprintf(stderr, "\n");
   fclose(fp_in);
   fclose(fp_out);
   exit(EXIT_SUCCESS);
}

G719仅仅使用CoderState结构体用于存储编解码过程中的中间信息,每次只调用encode_frame就好,具体的参数可以看源码注释。

上面提到解码的时候不需要提供码率信息,主要原因还是719的格式信息也可提供,当然,算法本身决定数据的长度也是一定的。

static void write_bitstream(FILE  *fp, Word16 bitstream[], Word16 num_bits)
{   
   UWord16   G192_HEADER[2];
   G192_HEADER[0] = G192_SYNC_GOOD_FRAME;
   G192_HEADER[1] = (UWord16) num_bits;
   fwrite(G192_HEADER, sizeof(UWord16), 2, fp);
   fwrite(bitstream, sizeof(Word16), num_bits, fp);
}

储存解码数据的时候已经比较清楚了,G719会在每一帧的解码数据前添加4字节的头,其中前两字节是固定数据,后两字节是长度。

写在最后

  1. 在一个服务器上调了很久,发现编码一直出问题,回来之后换了个服务器,直接一把过,可能是x86和arm的区别,等后续发现问题原因后更新。
  • 后续发现LINK_LIBRARIES(m) 可能会影响编译,这一行需要删掉。
  • 在不同系统中可能会出现stm_start\stm_end等遍历重定义,需要对想要文件修改。
  1. 从源码也看出g719代码量不大,但是弄懂却不容易,毕竟牵扯大量数学算法,具体可在文档中查看,至于算法的原理,就随缘更新吧。
  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值