![]() |
|
||||||||||||||
| . 网站首页 . 新闻 . 新品 . 方案 . 专访 . 活动 . DSP . EDA . 评测 . 技术文库 . 会员区 . 商城 . 服务导航 . 邮购 . 资源 . 社区 . | ||
|
||
|
|||||
| 基于G.729A的手机VOIP研发体会 | |||||
作者:陈罡 文章来源:北京理工大学 点击数: 更新时间:2008-6-6 ![]() |
|||||
|
多媒体终端是通信网络的重要组成部分,一个多媒体通信终端需要融合系统、传输、图像、语音、数据等多种功能,这就需要其核心处理器具备强大的处理能力。 1、语音编码原理和G.729A编解码器 语音编码算法从上个世纪七八十年代兴起,已发展得相当成熟和完善。语音编码一般分为三种类型:波形编码,参数编码和波形参数混合编码。波形编码是以逼近声音波形为目标的,其代表的算法有G.711,他的声音清楚度好,语音的自然度高,但是压缩效率比较差,常在32kbps以上。参数编码是把人的声道抽象成一个发声模型,对这个模型的参数进行编码,特点是语音压缩效率高,但是自然度比较差,能够在极低速率进行编码。波形参数混合编码结合了以上两者的长处,代表算法有G.723,G.729等,能在4-16kbps的速率上进行高质量的语音合成。 G.729采用的是共轭结构的代数码激励线性预测算法(Conjugate Structure Algebraic Code Excited Linear Prediction,CS-ACELP),这是一种基于CELP编码模型的算法。由于G.729编码器能够实现很高的语音质量(MOS分4.1)和很低的算法延时,被广泛地应用于数据通信的各个领域,如IP Phone和H.323系统等。G.729是对8KHz采样16bit量化的线性PCM语音信号进行编码,压缩后数据速率为8Kbps,具备16:1的高压缩率。 CS-ACELP编码器是以码本激励线性预测编码(CELP)模型为基础的。编码器对10ms长的语音帧进行处理,逐帧地提取CELP模型参数(LP滤波器系数、自适应码本和固定码本索引和增益),然后对这些参数进行编码和传输。编码参数的比特分配显示在表1中。 ![]() 表1 G.729码字分配表 在解码器端,这些参数用来恢复激励信号和合成滤波器参数。重建语音是使激励信号通过线性预测(LP)滤波器滤波后得到的,如图1所示。其中,短时合成滤波器是个10阶线性预测滤波器,而长时合成滤波器,或称基音合成滤波器,是利用自适应码本的方法来实现的。计算出合成语音后,还要进行后滤波以提高语音质量。 ![]() 图1 G729解码器示意图 G.729以10ms为一帧,编码时需要前一子帧作为参考帧,所以编码延时只有15ms,特别适用于实时通信系统。 G.729A是G.729的简化版本,两者的编解码结构相同,码流能够互通。 主要的简化有: 感觉加权滤波器采用量化的LP滤波系数, 简化了自适应和固定码本的搜索,在解码器中只使用整数延迟, 简化了谐波后滤波器。 经过这些简化,G.729A的运算量比G.729减少约一半,而语音质量只有很小的下降(MOS分3.9),因此在实际的工程应用中一般都采用G.729A。 2、ITU-T G.729A Speech Coder使用方法 对于G.729A语音编码算法有了一些了解以后,作为工程师来说最关心的还是如何使用这个编码器。现在采用标准c语言编写的G.729A语音编码器是能够下载到源代码的,有兴趣的朋友只要用"ITU-T G.729A 语音编码"作为关键字google一下,就能够找到很多关于他的代码。这里需要说明的是,网上下载的G.729A是无法直接用于工程项目的,首先就是运行效率,由于是itu-t作为原理性阐述的代码发布,他的c语言代码是以大家能够看懂为目的设计的,而不是考虑运行效率和速度;其次,itu-t的speech codec在编码完毕后,进行了“串行化”的操作。 我仔细研究ITUG729代码后发现: 编码器的输出及解码器的输入不是编码生成的参数向量,而是经过1bit->Word16(即2字节)转换的bit流 ,从而使得编码器输出数据不是原始PCM的1/16(理论上g.729编码和wav的文档的大小比例约为1:16),而实际上,我们采用他的coder编码出来的文档由于上述“串行化”的原因和原始wav几乎相同大,而且甚至比原始的wav还要大个十几K。 ITU-T为了在标准化方针中进行丢帧隐藏测试,对语音编解码器参考软件的码流格式一般需要为ITU-T G.192中规定的格式,即用16位的0x007F表示1个比特'0',用0x0081表示1个比特'1',每个帧头会有同步字和包的长度。对于同步字,0x6B20表示该帧为坏帧,0x6B21表示该帧为好帧。这样固然很好,但是。。。导致了编码后数据的增大。 那么如何来解决上述问题呢?解决的方法就是——去掉串行化代码,或重新编写串行化代码。 我们打开bits.c,就能够看到里面定义的如下4个函数: static void int2bin(int value, int no_of_bits, INT16 *bitstream); static int bin2int(int no_of_bits, INT16 *bitstream); void prm2bits_ld8k(int prm[], INT16 bits[]) ; void bits2prm_ld8k(INT16 bits[], int prm[]) ; 这个文档就是编码后文档大小没有什么变化的关键所在了,能够用如下的代码替换: static void bit2byte(Word16 para,int bitlen,unsigned char * bits,int bitpos) ; static Word16 byte2bit(int bitlen,unsigned char * bits,int bitpos) ; void prm2bits_ld8k(Word16 *para,unsigned char *bits) { int i; int bitpos = 0; for (i = 0;i 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 bitpos++; if (bitpos % 8 == 0) p++; } } void bits2prm_ld8k(unsigned char *bits,Word16 *para) { int i; int bitpos = 0; for (i = 0;i 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 > (7 - bitpos % 8)) &0x01; if (bit == 1) { newbyte = (1 return value; } 通过上述的修改,已能够确保,每次一帧160个字节的pcm16音频数据流,能够编码成为22个字节的编码参数prm,经过改写过的prm2bits_ld8k处理后,会转换成为10个字节的最终语音编码数据,这个时候,才真正体现出g.729a的威力。 编码器的代码能够采用如下的修改方法 (1)修改coder.c: unsigned char serial[SERIAL_SIZE]; /* 输出数据的数据类型由Word16改为unsigned char */ (2)修改coder.c文档,对编码器调用方式进行修改(关键部分代码): frame =0; while( fread(new_speech, sizeof(Word16), L_FRAME, f_speech) == L_FRAME) { printf("Frame =%d\r", frame++); Pre_Process(new_speech, L_FRAME); Coder_ld8a(prm); prm2bits_ld8k( prm, serial); fwrite(serial, 1, SERIAL_SIZE, f_serial); } return (0); (3)把ld8a.h头文档中关于“串行化”长度的常量定义修改为10个字节: #define SERIAL_SIZE 10 (4)解码器decoder.c进行类似的修改(关键部分代码): frame = 0; while( fread(serial, 1, SERIAL_SIZE, f_serial) == SERIAL_SIZE) { printf("Frame =%d\r", frame++); bits2prm_ld8k(serial, &parm[1]); /* 注意这里一定要是&parm[1] */ parm[0] = 0; /* 假设没有丢帧 */ parm[4] = 0 ; /* 假设数据效验正常 */ Decod_ld8a(parm, synth, Az_dec, T2); Post_Filter(synth, Az_dec, T2); Post_Process(synth, L_FRAME); fwrite(synth, sizeof(short), L_FRAME, f_syn); } return (0) ; 通过上面的修改,标准的ITU-T的G.729语音编码器和解码器就基本能够达到1:16的编码效率了。 修改bits2prm_ld8k和prm2bits_ld8k之后的编码的数据其实也不是1/16的,因为编码器中的比特分配并不是均匀的,有的时候一个参数需要用5bit来表示,但是他并不是在内存中用5bit表示,而是用了一个word16来表示的。所以这样编码后得到的数据还是要比1/16大一些,原理上来讲,编完码后的prm[]大小应该是11(共11个参数),但是编码器的bits2prm()在其前面又加了一位校验是否丢包的校验位,这样1个block编码后的prm[0]这位实际上是个校验位。您要注意一下,解码器在读取编码后文档内容的时候,不是直接读取的字节,而是通过一个函数read_frame(),进行读取的,一个block读取prm[]大小为12。这样假如您把bits2prm去掉,编码后一个block用11个word16表示,但是解码的时候一次读取12个,这样就错了。所以需要对解码器作一下相应的修改,使他不再去关注那个prm[0],一次读入11个数据。您看一下解码函数也会发现,在读入12个prm后,开头就是这么一句:bfi = *prm++,您把这个bfi作为参数传入函数,使其不再通过prm[0]得到,应该就能够了。 3、编解码器的优化问题 由于语音编码的特点,编解码的函数都是由一些基本的加减乘除的简单函数组织而成的,这些函数定义在BASIC_OP.C和OPER_32B.C两个文档里面,假如能够对这些简单函数进行嵌入式汇编的优化,就能达到事半功倍的效果。例如: #define mult_r(var1, var2) #define L_add(L_var1,L_var2) #define L_mult(var1,var2) #define L_shl(L_var1,var2) 等等,能够大大地加快速度,同时调整编译选项,采用最优化的编译模式,g.729a的代码有很大一部分的运算量集中在循环体计算中,因而针对循环的优化也很有必要,尤其是深层循环,假如有能力都改成arm汇编,就能够适用于手机voip等发展方向的应用。外国经过优化的g.729a的库,在dm642 dsp芯片上运行的时候,仅仅占有0.7%的cpu,而且是实时语音编码,真是很难设想他们都是怎么优化的(但是价钱也很高,而且只给二进制库和调用头文档,不给源代码)。 好了,这些是我对g.729a库使用的一点心得,希望对大家有用。 |
|||||
| 欢迎点击进入:TI德州中文网 (国内唯一针对TI应用的中文技术网站) 文章录入:admin 责任编辑:admin | |||||
| 【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 | |||||
| 最新热点 | 最新推荐 | 相关文章 | ||
| DM642上G.729A编解码算法的实… |
| 网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!) |
| | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 管理登录 | | |||
|
|