崔 颖,赵 宇,马永涛,刘开华
(天津大学 电子信息工程学院,天津300072)
数字广播调幅系统DRM(Digital Radio Mondiale)采用先进音频编码AAC(Advanced Audio Coding)作为其主要的信源编码方式[1],在与模拟调幅广播同样的带宽(9 kHz或10 kHz)下实现了调频的音质。DRM不仅解决了模拟调幅广播抗干扰能力差等缺点,而且在音频业务的基础上又增加了文本、图像、数据等附加业务,丰富了调幅广播的内容,大大提高了调幅广播的市场竞争力,成为调幅广播发展的必然趋势。
信源编码是DRM系统的关键技术,其压缩节目音频源信号,只需较少的传输带宽就可保证接收端的重建音频信号有较好的音质。DRM音频解码器的实现和优化决定了系统能否正确实现音频解码,并完成音频的实时播放,使用户得到良好音质。本文中AAC音频解码程序在DSP硬件平台上运行,由于硬件平台性能有限,要求音解码器不仅要能确保音频质量,还要不能占用DSP系统太多的资源。因此研究DRM音频解码器在高性能DSP平台上的实现及其优化具有非常重要的现实意义。
通用MPEG-4 AAC音频编解码器的原理和实现技术已经很成熟,不再详述。DRM系统的信源编码方案中所采用的频带恢复技术(SBR)提供了类似于MPEG-4 AAC中感知噪声整形(PNS)模块的功能,故DRM系统采用的音频编码方案不包括PNS模块,同时也去除了长期预测(LTP)、采样率可分级(SSR)等复杂的模块,降低了算法复杂度,对处理器的处理能力要求也相对较低,适合应用于嵌入式开发平台上。AAC的采样率有12 kHz和24 kHz两种,5个(12 kHz采样频率)或 10个(24 kHz采样频率)音频帧组成一个持续时间固定为400 ms的音频超级帧。本文优化之前首先在PC机的VC++6.0环境下实现了DRM广播信号的正确解码和实时播放,测试信号为单声道、48 kHz采样,采用AAC音频编码的wav格式的DRM广播信号源,其中AAC的采样率为24 kHz,即一个音频超帧包含10个子帧。在VC++6.0环境下运行整个工程,经同步、解调和信道解码后获得DRM信号源中的AAC音频编码数据,在每次AAC子帧解码前将每子帧数据输出到一个文件。在DSP上测试音频解码程序时,可以直接提取AAC数据进行解码,解码流程如图1所示。解码过程如下:
(1)对传来的AAC子帧数据进行比特流分解,根据DRM系统中音频子帧结构获取语法单元、霍夫曼码字等各部分的数据。
(2)进行霍夫曼解码,这部分用到了一系列的霍夫曼码书进行查询解码。频域数据和比例因子的获得都在这一步。该过程需要在将顺序打乱的码字重新组合在一起的同时,进行霍夫曼解码,并将解码之后的数据放置到正确的位置上,准备进行下一步的反量化。
(3)对解码后的频域数据进行反量化。
(4)将反量化的结果乘以(2)中生成的比例因子。
(5)滤波器组部分。这部分在解码时采用了逆改进离散余弦变换 (IMDCT)[2],还包括一个加窗的过程和叠加的过程。功能模块的输出为信号的时域值。
图1 DRM系统的AAC解码流程图
TMS320C6416(简称C6416)[3]是一种高性能的 32 bit定点DSP芯片。本文使用的C6416的工作频率达到600 MHz。其特点包括:具有8个功能单元的高级超长指令体系结构的CPU;所有指令有条件执行;支持8/16/32 bit可变长度数据访问;支持常用算术运算的饱和与归一化操作;两级高速缓存(Cache)存储器结构及丰富的片内外设,如增强型直接存储器访问EDMA控制器、多通道缓冲串口McBSP等。C6416开发板上除C6416 DSP芯片外,还带有外扩的 512 K×8 bit的 FLASH。
开发环境采用DSP集成开发环境CCS(Code Composer Studio),它集成了代码编辑、编译、工程管理、代码生成与调试、代码性能剖析、数据查看、绘制数据图像、DSP/BIOS参数设置,以及提供各种优化建议等工具模块。
本文优化之前先在PC机VC++6.0环境下实现了DRM广播信号的正确解码和实时播放,但移植到DSP平台之后不能实时播放。本文单独将音频解码部分的程序移植到DSP开发平台CCS3.1开发环境中,测试数据为前文中存储的AAC解码前每子帧的数据。优化前,不带有SBR解码的情况下,音频解码程序中一个音频超帧里每个子帧解码所花费的周期数,即运行效率如下:
子帧 1:1 901 300 子帧 2:3 667 994
子帧 3:3 469 783 子帧 4:3 397 752
子帧 5:1 745 753 子帧 6:3 416 357
子帧 7:3 439 464 子帧 8:3 331 484
子帧 9:1 721 339 子帧 10:1 764 605
(共计 27 855 831周期)
在DSP上单独测试音频解码的效率,计算所花费的时钟周期数的语句如下:
st=clock();aac_frame_decode(,,,);end=clock();printf("clock cost%d ",end-st);
其中aac_frame_decode()是信道解码与音频解码的接口函数,该函数调用AAC解码程序,在此,其参数与返回值已省略。在CCS环境下,运行程序前点击Profile剖析菜单下Clock选项中的Enable,就打开了CCS的程序运行周期计数功能。两次clock函数调用返回值之差就是解码函数花费的周期数。表1是利用CCS3.1中Profile工具分别计算的程序优化前后一个超帧解码过程中每个函数所花费的周期数。
优化的本质是提高程序的运行效率,同时保持程序原有功能准确无误。本文针对本课题中的具体问题,提出以下对应的优化措施:
(1)去除原程序中用不到的函数。最初的音频解码程序是针对所有MPEG-4 AAC标准使用的,其中包含了很多功能模块,如MP4解码、PNS解码、LTP解码等。这些功能在DRM系统的音频编码标准AAC中是用不到的,所以应当将它们去除,否则其生成的代码不仅无用,而且会占用大量存储空间。在CCS3.1中点击Profile菜单下的Analysis Toolkit选项中的 Code coverage and Exclusive Profiler,按照提示运行程序,可以得到一个Excel文件。该文件将代码覆盖程度、每个函数调用次数以及执行函数CPU所花费的周期数等展示出来,从而可以方便地找到每个文件中一直没有运行的函数,去除这些函数可节省大量存储空间且保证程序功能无误。
(2)循环体优化。原始的音频解码程序已经采用了一些常用的算法级别的优化,如 IMDCT的快速算法[4]、霍夫曼解码的查表快速算法等,但仍没有达到最理想的速度。主要原因是for循环和定点化的问题。在AAC解码器中,循环体几乎占用了60%的资源,因此它的优化非常重要。要想充分发挥C6416 DSP处理器的8个功能单元并行执行指令的功能,需要让编译器尽可能多地生成由2条以上指令组成的超长指令。C/C++编译器可以对代码进行不同级别的优化。高级优化由专门的优化器完成,与目标DSP有关的低级优化由代码生成器完成。图2是编译器、优化器和代码生成器的执行图。
图2 编译器、优化器和代码生成器的执行图
最简单的执行优化的方法是用cl6x编译程序[5],在命令行设置-On选项即可。n是优化的级别(n为0、1、2或3),它控制优化的类型和程度。-O3级别下,编译器可对循环代码实现软件流水[6],优化器将会充分利用处理器的8个功能单元,尽可能多地生成并行指令,使最后的可执行代码运行速度达到最高,以达到优化代码的目的。
软件流水是用来安排循环指令并使这个循环多次迭代并行执行的一种技术。简单循环情况下,软件流水能够正常的发挥作用,但在多层嵌套循环情况下,软件流水往往会失败。简化循环是充分发挥软件流水的通用而有效的办法。如本文中一个函数reordered_spectral_data()中存在多达5层嵌套的for循环,有的循环中还有条件分支函数,在-O3优化情况下,解一个超帧10次调用这个函数之后,共花费7 613 426个周期,与其他函数开销相比如图3(a)所示。这是因为优化器只能对最内层的循环进行软件流水操作,而外层的循环只能按照原语句执行,于是大量语句只能以最慢的方式执行。
经过对这个函数中的循环语句进行分析和调试,同时参照AAC标准,在保证功能正确实现的基础上对该函数做如下改进:这个函数的功能是将打乱顺序的霍夫曼码字重新排序,并进行霍夫曼解码。在码字重新排序的过程中有码书查找的步骤,编码时码书的选择是有优先级别的,解码时需要从最高级到最低级依次判断是否存在以这个码书编码的码字,如果有就进行解码,没有就判断下一个码书,每判断一个码书就是一次大循环。事实上,码书级别都很低,先前大部分循环中判断的码书都是错误的,所以可以另外用一个小的循环检查出级别最高的码书,这样就会在真正解码的嵌套循环中省去许多无用的循环。另外在嵌套的for循环中,有一层是针对窗组进行的循环,即有几个窗组需要解码,就循环几次。事实上,窗组往往只有一个,多窗组的情况极为少见,所以这层for循环在大部分情况下可以去除。但多窗组的情况毕竟存在,可以事先用一个if条件判断窗组个数,再根据结果条件执行相应的程序。这就使程序在大多数情况下能够节省大量的运行时间,因为它会执行软件流水生成的代码,而在极少情况下会执行效率最慢的未经优化的代码。
按照上述的分析进行优化之后,结果如图3(b)所示。reordered_spectral_data()函数优化之后的开销为:10次调用这个函数共花费1 029 512周期,与优化前的7 613 426周期相比,运行速度提高了7倍多。
图3 优化前后函数reordered_spectral_data()的开销比例对比图
(3)消除存储器相关性。如果C6416编译器可以确定两条指令是不相关的,则安排它们并行执行,否则安排指令串行执行。有几个方法可以帮助编译器确定哪些指令不相关:使用restrict关键字声明指针;一起使用-pm选项和-O3选项确定程序优先级,在程序优先级中,所有源文件都被编译成一个模块,从而使编译器更有效地消除相关性;使用-mt选项向编译器说明代码不存在存储器相关性,即允许编译器在无存储器相关性的假设下进行优化。
(4)使用内联函数。C6416编译器提供的内联函数是直接映射为内联指令的特殊函数,内联函数的代码高效、长度短。可以使用内联函数并行优化C代码。
(5)在循环前加上 #pragma MUST_ITERATE(,,),向编译器传递循环次数信息,编译器会生成更好的循环代码,或消除因不知道循环次数而产生的冗余循环以便减小整个代码量。
通过实施上述各种优化方法,从算法程序上的等效替代到充分利用编译的优化功能,音频解码程序运行效率有了明显的提高。下面是同一个超帧解码中每个子帧花费的周期数,总计数周期为2 641 488,与优化前程序花费的27 855 831个周期相比,运行时间不到原来的 1/10。
子帧 1:396 470 子帧 2:250 626
子帧 3:247 983 子帧 4:226 120
子帧 5:254 623 子帧 6:224 668
子帧 7:231 056 子帧 8:254 901
子帧 9:277 520 子帧 10:279 424
(共计 2 641 488周期)
从表1可以看出,优化后一些函数的访问次数为0,这是由于优化采用了内联函数的功能,它们的代码被内联在一些较大的函数中,如 faad_getbits()函数;另一种情况:如对于pns_decode()函数,由于DRM系统的音频编码标准中的SBR技术提供了相当于PNS的功能,所以AAC中的PNS模块没有使用,优化中被删除。对比表1中优化前后数据可知,很多函数花费的周期大大减少,如decode_scale_factors函数等。
本文给出了DRM音频解码器在TMS320C6416上的实现方案,并结合TMS320C6416的特性,从去除无用函数、内联函数替换、循环体优化等多方面对音频解码程序进行了优化。在充分利用EDMA硬件资源的音频驱动程序的配合下,优化后的音频解码程序能够完成音乐的实时播放。DRM中一个AAC超帧的时间为400 ms,优化后解码一个音频超帧耗费的周期数从2 800万降至300万,即每秒音频解码需750万周期,远小于C6416每秒所能执行的周期数(600 M),不但能保证音频解码和播放的实时性,还为DRM广播信号解码系统执行音频解码之前的解调和信道解码程序节省了很大的周期资源和处理空间。
表1 优化前后音频解码各函数在一个超帧解码过程中所耗费的周期数
[1]ETSI ES 201 980 V3.1.1[S].Digital Radio Mondiale(DRM):System Specification,2009.
[2]CHO Yang Ki,SONG Tae Hoon,Kim Hi Seok.An optimized algorithm for computing the modified discrete cosine transform and its inverse transform[J],IEEE,2004:626-628.
[3]Texas Instruments.TMS320C6414,TMS320C6415,TMS320-C6416 fixed-point digital signal processors.SPRS 146N.2005.
[4]窦维蓓,刘若珩,王建昕,等,基于 DSP的IMDCT快速算法[J].清华大学学报,2000,40(3):99-103.
[5]Texas Instruments Incorporated.TMS320C6000系列DSP编程工具与指南[M].北京:清华大学出版社,2006.
[6]马君国,王远模,常华俊,等.在DSP处理器上并行实现ATR算法[EB/OL].http://www.eeworld.com.cn/designarticles/dsp/200703/11490.html.