精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
解码器做播放流的数学处理部分。它从输入组件的多路分解器(modules/demux目录下,管理包以重新编译连续的基本流)和输出线程(包的样本被解码器重构并且播放它)分离出来。基本上解码器只是纯粹的算法,不和硬件打交道。
输入线程调用Input_DecoderNew(src\input\decoder.c)派生合适的解码器,通过CreateDecoder选择更恰当 的解码器组件,每一个解码器组件都要检查decoder_config.i_type然后返回一个记录,然后和decoder_config_t一起运行 module.pf_run()。一般的decoder_config_t的结构给出了解码器的ES ID和其类型,一个指向stream_control_t结构(给出播放状态的信息)的指针,decoder_fifo_t和 pf_init_bit_stream函数,然后运行DecoderThread(解码器主循环)来解码。CreateDecoder:将解码器结构中的 输出回调函数指针(音频,视频,SPU)附值,这些函数建立或者销毁这些输出设备,解码后的数据由此被正确的传递出去;调用Module_Need将特定 的解码器组件加载进解码器结构,组件的回调函数指针pf_activate(通常是open函数)完成对pf_decode_XXX函数指针的附值(其中 的音频,视频函数指针用刚才解码器的相应输出回调函数的包裹函数附值)。DecoderThread:调用被赋予特定函数值的pf_decode_XXX 函数指针进行解码工作。
这个输入组件(包)为传递到解码器的流数据提供高级的API。首先先看这个包的结构,它被定义在include/input_ext-dec.h里。
data_packet_t包含一个指向数据物理地址的指针,解码器读取数据只能从p_payload_start开始到 p_payload_end结束,然后如果p_next不空的话,解码器就跳转到下一个包。如果b_discard_payload的标志位显示时则说明 包地内容是混乱的因此这个包就会被丢弃。data_packet_t被包含进pes_packet_t里,pes_packet_t表示出了可以表明一个 完全的pes包的data_packet_t链表目录。对于PS流,一个pes_packet_t通常只包含一个data_packet_t。而对于TS 流,一个PES被分解在许多的TS包里。PES包里含有PTS日期(在MPEG规范里有详细说明)和目前的读取速度,这个可以用于修改日期 (i_rate)。
b_data_alignment(如果在系统层里可用的话)表明假如这个包是随机的接入点的话,那么b_discontinuity就会知道先前的包是不是被丢弃过。
PES Packet 对于节目流的PES包结构图
对于节目流,PES包只含有一个数据包,它的缓冲器包括PS头,PES头和数据的有效负载。
对于传输流的PES包结构图
对于传输流,PES包可以含有无限制的数据包(上图是三个),它的缓冲器包括TS头,PES头和数据的有效负载。
这个结构由输入和decoder_fifo_t解码器共同组成,它的功能是PES包的循环FIFO被解码。输入为FIFO提供了一些宏指 令:DECODER_FIFO_ISEMPTY,DECODER_FIFO_ISFULL,DECODER_FIFO_START,DECODER_FIFO_INCSTART,DECODER_FIFO_END,DECODER_FIFO_INCEND。 在对FIFO进行任何操作前一定要用p_decoder_fifo->date_lock。下一个包被 DECODER_FIFO_START(*p_decoder_fifo)所解码。当这些完成后,要先调用 p_decoder_fifo->pf_delete_pes(p_decoder_fifo->p_packets_mgt,decoder_fifo_start(*p_decoder_fifo)) 然后调用ecoder_fifo_nestart(*p_decoder_fifo),使得把PES返回到缓冲器管理。如果FIFO是空的话 (DECODER_FIFO_ISEMPTY),可以在携带着条件信号 (Vlc_cond_wait(&p_fifo->data_wait,&p_fifo->data_lock))的新的信 息包到达之前先中断它(加锁)。当文件被播放完或者用户退出时,p_fifo->b_die要设置成1,这意味着要释放所有的数据结构并且要尽快调 用到vic_thread_exit()。
由于基本流可以任意地别分割,因此传统的读包方法并不方便。这个输入组件就提供了容易读取比特流的方法。这个可选可不选,如果选了话就不必再进入缓冲器了。
这个比特流允许调用GetBits(),当有必要时这个函数在不干扰用户的情况下读取包的缓存、修改数据包和pes包。因此对于读取连续的基本流会非常方便,用户不必去处理包的边界和FIFO,这些由比特流来处理。
下面着重分析一个32位的缓冲器:bit_fifo_t。它包括字缓冲和一些重要的bit(高位),这个输入组件提供了以下五个内联函数来管理这个缓冲器:
(1)GetBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):
从比特缓冲返回到下一个1bit。如果没有足够多的比特时,这个函数就从decoder_fifo_t里抓取一个字的大小来补充。这个函数只能保证工作在24bits,有时也会工作在31bits,但这是副作用。要是使其可以读取32bit,就必须修改这个函数。
(2)RemoveBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):
这个函数除了比特不返回外(为了节省CPU循环)和GetBits()一样,它同样有其的局限,因此在必要时也得修改这个函数。
(3)ShowBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):
除了在读取完没获得多的比特外和GetBits()一样,因此接着需要调用RemoveBits()。要注意的是除非对字节进行排列的话那么这个函数不能工作在高于24bits的情况下
(4)RealignBits(bit_stream_t*p_bit_stream):
为了使缓冲的前几个比特排列成字节的边界这个函数丢弃掉比特流的高n(n<8)位,这有利于找到排列好的开始代码(MPEG)。
(5)GetChunk(bit_stream_t*p_bit_stream,byte_t*p_buffer,size_t i_buf_len):
它和函数memcpy())类似,只不过它把比特流作为第一个参数。P_buffer必须被分配并且至少要i_buf_len这么大,这有利于用户 要保存路径时备份数据。所有以上的函数重新构造了连续的基本流范例。当比特缓冲器是空的话,这些函数就在当前包里取随后的字;当包是空的话,就转到下一个 data_packet_t,如果转不到的话就转到下一个pes_packet_t(见
p_bit_stream->pf_next_data_packet),所有的这些都是明晰的。为了能用到比特流,必须要调用 p_decoder_config->pf_init_bit_stream(bit_stream_t*p_bit_stream,decoder_fifo_t*p_fifo) 来设置所有的变量。这可能需要从包里获得规范的信息(比如说对于PTS)。如果 p_bit_stream->p_bit_stream_callback不空的话,那么包必须要改变(在较低版本中的 video_parser.c有例子说明),调用的函数的第二个参数表明它是个新的data_packet还是新的pes_Packet_t,这个结构可 以保存到p_bit_sream->p_callback_arg。
当要调用pf_init_bit_stream时,pf_bitstream_callback还没有被定义,因此只能先转到第一个包,这个可以在调用pf_init_bit_stream后通过指针再来回调比特流。
VLC内置了MPEGI,2层的音频解码器,一个MPEG MP@ML的视频解码器,一个AC3(来自LiViD)解码器,一个DVD SPU解码器和一个LPCM解码器。可以仿照视频解析器用户可以自己写特定功能的解码器。
MPEG音频解码器虽然是本地化的,但是并不支持第三层解码(这个非常不方便),AC3解码器是来自于Aaron Holtzman’s libac3的端口,SPU解码器也是本地化的。这些可以在AC3解码器里的比特流的回调里有说明。这这种情况下,必须要跳过PES的前三个字节(不是基 本流的部分)。
对于MPEG视频解码器,VLC媒体播放器在其解码器的主层上提供了MPEG-1,MPEG-2的主要框架,这对于VLC来说己经非常成熟。既然这 个主要框架被视频解析器和视频解码器这两个逻辑实体所分开,因此它是面向比特的,它最初的目的是把比特流解析功能从高并行的数学算法中分离出来。在理论 上,这里必须只有一个视频解析线程(否则在读取比特流时会产生混乱)和视频解码线程池,这些用来做即时IDCT和在一些块上做动态补偿。它不支持也不会支 持MPEG-4或者DivX解码。它不是一个编码器。尽管还有部分没被测试(比如说差分动态向量)但是它完全支持MPEG-2 MP@ML规范。对于这个解码器最有趣的文件是vpar_synchro.c,它解释了dropping算法的全部框架。在nutshell里,如果它足 够强大的话就能对全部的IPB进行解码,或者在用户有足够的时间的话就能对全部的IP和B进行解码(这个基于on-the-fly时间统计)。另一个有趣 的文件vpar_blocks.c,它描述了全部的块(包括系数和动态向量)解析算法,在这个文件的最后需要产生一个对普通图片类型的最优化函数和一个减 缓类属函数。这里同样有不同程度的最优化函数(可以慢速编辑文件但快速解码)为VPAR_OPTIM_LEVEL,level 0表示没优化,level 1表示优化MPEG-l和MPEG-2图片框架,level 2表示优化MPEG-1和MPEG-2域和图片框架。
动态补偿技术是非常依赖于平台的(比如MMX或者AltiVec版本),因此必须把它放plugins\Motion目录下。这对于视频解码器来说是非常 方便的,这样的插件可以用到其他的视频解码器中。一个动态的插件必须定义6个函数(直接来自于规范): vdec_MotionFieldField420,vdec_MotionField16x8420,vdec_MotionFieldDMV420,vdec_MotionFrameFrame420,vdec_MotionFrameField420,vdec_MotionFrameDMV420。 而对于在MP@ML标准里被禁止的格式自然是不能用(因为需要的编译的时间长)。
像动态补偿一样,IDCT技术也是一种平台规范。因此需要把它放到phigins\idct目录下。这个模块是用来做IDCT运算和把数据复制到最后的图片上,这里需要定义以下7种方法:
(1)vdec_IDCT(decode_config_t*p_config,dctelem_t*p_clock,int):
做完全的2-D IDCT运算。64个系数在p_block里。
(2)vdec_SparseIDCT(vdec_thread_t*p_vdec,dctelem_t*p_clock,int i_sparse_pos):
只用一个非零系数(有i_sparse_pos来设计)在块上做IDCT运算。可以把这个函数定义在phigins\idct\idct_common.c(在初始化时间段里对这些64位的矩阵进行预计算)里。
(3)vdec_InitIDCT(vdec_thread_t*p_vdec):
对vdec_SparselDCT做初始化的填充。
(4)vdec_NormScan(ppi_scan):
通常情况下,这个函数不做任何运算。在做一些较小的优化时,用它来对MPEG导航矩阵(在ISO/IEC 13818-2里有详细介绍)做某些系数的求逆运算。
(5)vdec_InitDecode(struct vdec_thread_s*p_vdec):
初始化IDCT和选择配置列表。
(6)vdec_DecodeMacroblockC(struct vdec_thread_s*p_vdec,struct macroblock_s*p_mb):
对整个宏块解码并把这个数据复制到最终要的图片里(包括彩色信息)。
(7) vdec_DecodeMacroblockBW(struct vdec_thread_s*p_vdec, struct macroblock_s*p_mb):
对整个宏块解码并把这个数据复制到最终要的图片里(除了彩色信息),它用在gayscale模式里。
在必要情况下,VLC的MPEG视频解码器将会利用到多处理器。这个思想来自于解码器的池,这个池可以在多个宏块上同时实现IDCT/动态补偿功能。这个管理池的模块在src\video——decoder\vpar_pool.c里。但是这个模块的处理速度要慢一些。