最近在适配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
文件夹中:encoder
和decoder
.
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即可。
用例分析
相对来说,使用起来非常简单,一共就只有两个接口函数:
void encoder_init(CoderState *c, Word16 num_bits)
:初始化数据,其中num_bits可以理解为20ms的编码数据量,基本上是(比特率/50)。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字节的头,其中前两字节是固定数据,后两字节是长度。
写在最后
- 在一个服务器上调了很久,发现编码一直出问题,回来之后换了个服务器,直接一把过,可能是x86和arm的区别,等后续发现问题原因后更新。
- 后续发现LINK_LIBRARIES(m) 可能会影响编译,这一行需要删掉。
- 在不同系统中可能会出现stm_start\stm_end等遍历重定义,需要对想要文件修改。
- 从源码也看出g719代码量不大,但是弄懂却不容易,毕竟牵扯大量数学算法,具体可在文档中查看,至于算法的原理,就随缘更新吧。