MP3播放器探秘 袁斌 1999年 第12期 13版 MP3在国内流行已经有很长一段时间了,但始终没见到国产MP3播放软件的蓬勃发展。目前可以见到的比较成型的MP3播放软件只有《超级解霸》软件包中的播放器。笔者通过本文想将一年多来对MP3软件的研究成果介绍给大家,但愿起到抛砖引玉的作用,能推动国内自由软件的开发及MP3编/解码器的开发。同时,笔者希望同国内对此项技术感兴趣的个人/单位展开合作。MP3播放软件具有“麻雀虽小,五脏俱全”的特点,通过开发MP3播放软件,你可以学到许多有关多媒体开发、GUI编程、插件接口控制以及网络编程的知识。 #1 一、MPEG Audio的基础知识 MPEG Audio分为MPEG1和MPEG2两大类。而MPEG1中主要有Layer1、Layer2和Layer3三种。MPEG1 Audio编码方式之间的主要区别从外部看来主要是对音频文件的压缩率和要求播放媒体提供数据的速率不同,并且内部采取的算法也有很大的不同,基本上是随Layer数增大而越来越复杂。经Layer1编码的音频文件后缀为MP1,另外两种分别为MP2和MP3。 #1 二、MPEG Layer3编/解码的基本原理 音乐CD具有44.1KHz 16Bits立体声的音频质量,一张CD可以存储74分钟的歌曲(大约15首左右)。如何将这些歌曲无损或基本无损地进行压缩,以使在同样的媒体上存储更多的歌曲,一直困扰着软件业。当MPEG协会提出MPEG Audio Layer1~Layer3后,机会产生了。通过使用MPEG1 Layer3编码技术,制作者得以用大约12∶1的压缩率记录16KHz带宽的有损音乐信号。不过,同CD原声区别不大。人的听力系统具有非常优越的性能,其动态范围超过96dB。你既可以听到扣子掉在地上这样小的声音,也可以听到波音747的强大的轰鸣声。但当我们站在飞机场听着波音747的轰鸣时,你还能分辨出扣子掉在地上的声音吗?不可能。人的听力系统适应声音的动态变化,人们对这种适应及屏蔽特性音质研究后得出对声音压缩非常有用的理论。人们很早以前就知道利用这种特性来为磁带录音降低噪音了(当没有音乐时嘶嘶声很容易听到,而当音乐信号电平很高时嘶嘶声不容易听到)。(^121301a^)当声音较强时产生屏蔽效应。在阈值曲线下的噪音或小信号声音无法被人耳听到。在较强信号出现时,允许通过更多的信号。在此时增加被量化过的小信号数据(使用无用的位来携带更多的信息)可以达到一定程度的压缩的目的。通常情况下,MP3压缩器将原始声音通过FFT(快速傅立叶变换)变化到频域,然后通过一定的算法算出何种频率声音可以携带更多的信息。而在还原时解码器所需要做的仅仅是将其从频域再变换回来。(^121301c^) #1 三、MP3解码器的实现 笔者根据德国Fraunhofer IIS (http://www.iis.fhg.de/amm/)公布的MPEG Audio解码程序进行简化后编写了一个最简MP3解码程序。有兴趣的读者可以参考对应的源程序(http://www.see.online.sh.cn/ch/sw/self/rainplay/layer3.zip)。 MP3文件由多个帧组成,也就是说帧是MP3音乐文件的最小组成单位。每个帧又由帧头和帧数据组成。每个帧头长4字节。其数据结构如下(^121301b^): typedef struct _tagHeader { unsigned int sync:12; //同步信息 unsigned int version:1; //版本 unsigned int layer:2; //层 unsigned int error_protection:1; //CRC校正 unsigned int bit_rate_index:4; //位率索引 unsigned int sample_rate_index:2; //采样率索引 unsigned int padding:1; //空白字 unsigned int extension:1; //私有标志 unsigned int channel_mode:2; //立体声模式 unsigned int mode extension:2; //保留 unsigned int copyright:1; //版权标志 unsigned int original:1; //原始媒体 unsigned int emphasis:2; //强调方式 } HEADER, *LPHEADER; 其中帧同步标记为0xFFF。 在帧头后边是Side Info(姑且称之为通道信息)。对标准的立体声MP3文件来说其长度为32字节。通道信息后面是Scale factor(增益因子)信息。当解码器在读到上述信息后,就可以进行解码了。 当MP3文件被打开后,播放器首先试图对帧进行同步,然后分别读取通道信息及增益因子等数据,再进行霍夫曼解码,至此我们已经获得解压后的数据。但这些数据仍然不能进行播放,它们还处于频域,要想听到歌曲还要将它由频域通过特定的手段转换到时域。接下来的处理分别为立体化处理;抗锯齿处理;IMDCT变换;IDCT变换及窗口化滑动处理。 #1 四、MP3播放器特效技术分析 Equalizer(均衡效果):玩过音响的朋友都知道EQ处理,通过精心调节各个独立频率段的增益,可适当地弥补音源的不足或提升其效果。一般调节范围为±20dB。在MP3播放器中实现EQ处理并不是很难,总的来说分为两种实现方法。 推荐:一类以WinAmp为代表的播放器,崇尚精确调节,其EQ对话框中对频率点的标定基本准确,用户在调节时较少出现因过增益而产生的阻塞现象。其实现原理为:将用户调节的10个频率点增益值适当地通过一对多映射对应到一个576个浮点数组成的数组,其中576是MP3解码原理中推导出来的常量。再实时地用这576个增益值同Huffman解码后,仍处于频域的MP3帧数据相乘,进行衰减/提升。这样做的优点是:频率准确,较少产生阻塞,缺点是:运算量稍大。有关这种EQ实现的具体方法,请参见X11AMP公布的源程序。 不推荐:另一类以XAudio为代表的播放器或许是还不懂WinAmp是怎么实现EQ的或许是认为平平淡淡才是真。在实现EQ时采用了较为不精确的处理。其实现原理为:直接用32个增益值同每帧中32个子带(sub-bands)分量相乘。其优点是运算简单,时滞小。缺点为频率调节极其不准确,由于MP3编码原理的限制,甚至于到现在还没人能准确地标定出各个子带的频率。Wplay大胆地在其EQ对话框中标出了频率值,但那是盲目地抄WinAmp的,你只要用几个单一频率的MP3文件测试一下就知道了。目前90%以上实现了EQ功能的MP3播放器都是采用了这类方法。 Visualization(音乐可视化处理):相比之下,可视化处理的实现在MP3播放器中实现起来要简单一些。如果你只要显示动态波形,那么你只要处理好解码线程和可视化线程之间的缓冲的同步就行了。而如果你还要实现频谱显示(spectrum),则还要将解码线程送来的数据先进行FFT(快速傅立叶变换),然后再显示。有关具体情况请参考我在Rainplay UI for freeamp中的代码(http://www.freeamp.org/pub/freeamp/freeamp-snap.zip)。 DSP(数字音效处理):同以上的两种相比,DSP处理的技术含量最多。需要开发者有比较高的数学知识及编程水平。由于DSP涉及的内容同本文或多或少相距远了一些,所以如果你对DSP有兴趣请到附录中推荐的相关网址或参考相关书籍。在这里提一下,WinAmp中的DSP只是简单地开放出数据供外部插件处理。 #1 五、开发MP3播放器常用的技术 不规则窗口图形化技术:MP3播放器简直将Windows中的不规则窗口发挥到了淋漓尽致的地步。大大小小、形态各异的播放器各具特色、形状各异,非常吸引用户。有关这一技术的实现,其实相当简单。只需根据要处理的图形并根据某一色键计算出一区域(region)并赋予某窗口即可。如果读者感兴趣,可以参考笔者主页中相关程序。(http://rainplay.yeah.net)Skin实现技术:实现多Skin处理这一技术十分简单,大家看一看WinAmp中的Skin说明即可。需要说明的是尽量简洁的同时最好兼容WinAmp的Skin机制,才会获得广大用户的支持。 多线程协作技术:你的MP3播放器性能的好坏全看这一点做得如何了。处理不好,你的MP3播放器就会像老牛拉破车一样。为了提高效率,你要善于使用信号灯同步技术而不要使用Windows的消息系统。 插件引用技术:基本上讲这并不难做到,关键是如何定义好通用、简单的接口。笔者推荐大家使用类封装技术。感觉有难度的读者可以参考Freeamp的代码。 允许第三方再开发:WinAmp的广泛流行在相当的程度上得益于其插件允许用户进行再开发,让广大MP3迷也过一把开发的瘾!所以,你的播放器最好也遵从这条规律。