单码流与多码流分析
VOD4.6客户端不支持rm10的播放,通过分析发现rm10文件是多码流的,它的各条数据流按块分布,与以前的单码流音视频交错分布不同。而有些rm9的文件也是多码流的,所以不能播放的原因是单、多码流的不同。
单码流和多码流不只是数据分布的不同,它们的头部也有很大的不同。
MDPR部分:在单码流中每一个流对应一个MDPR,在多码流中也是如此,但是所不同的是,多码流多了LogicalStream,音视频各拥有一个LogicalStream,可以凭借这一点分辨单、多码流,LogicalStream中主要记录了音频或视频所包含实际的物理流以及每条流的起始地址等等。数据结构如下:
class LogicalStream
{
UINT32 size;
UINT16 object_version;
UINT16 num_physical_streams;
UINT16 *physical_stream_numbers;
UINT32 *data_offsets;
UINT16 num_rules;
UINT16 *rule_to_physical_stream_number_map;
UINT16 num_properties;
NameValueProperty *properties;
};
DATA部分:单码流一般只有一个DATA,而多码流每对应一个流就有一个DATA,但这也不是一定的,个别的流可能只有MDPR,但是没有DATA。多DATA与多码流的按块分布是很一致的。
class DataHeader
{
UINT32 object_id;
UINT32 size;
UINT16 object_version;
UINT32 num_interleave_packets;
UINT32 next_data_header;
};
class PacketHeader
{
UINT16 object_version;
UINT16 length;
UINT16 stream_number;
UINT32 timestamp;
UINT16 flags;
};
typedef PacketHeader * PacketHeaderPtr;
class PacketHeader1
{
UINT16 object_version;
UINT16 length;
UINT16 stream_number;
UINT32 timestamp;
UINT16 asm_rule;
UINT8 asm_flags;
};
以上是DATA部分的数据结构,DataHeader部分是相同的,而PacketHeader是用于单码流的,PacketHeader1是用于多码流的,注意:两者的大小相差一个字节。就是这点造成的VOD4.6一点图像都显示不出来。后来通过试验证明了PacketHeader后两者的实际数据部分结构是完全相同的。
在DATA部分还有一点要注意的是,上面的asm_flags用来判断是否关键帧。对于Video
,在单码中flag值为2代表是关键帧,而多码流2或3都为关键帧;对于Audio,多码流与单码流不同的是,flag总为3,是否关键帧由asm_rule来决定,asm_rule为00是关键帧,01则是非关键帧。
VOD4.8中对于多码流的支持
单码流跟多码流的区别还不止于此,但是要支持多码流的播放,知道以上的区别就足够了。
首先,根据是否有LogicalStream,可以判断是否多码流。如果是的话,就要解析出LogicalStream结构中有用的字段,如 num_physical_streams 、physical_stream_numbers、data_offsets。
然后按照一定的规则为音视频各选择一条流用于播放,接着就引发了一个问题,在以前的文件中,音频与视频是按照一定得比例交错分布的,那时我们不用考虑何时读音频何时读视频,只有一个DATA,从头到尾读就行了,但是由于多码流的音频与视频不是交错分布的,这样的话,如果还是按照这个模式,那就读完视频,才读音频,这样显然不行,解决的办法是再增加一个线程用于接收数据,原来的接收线程我们可以让它只接受视频数据,增加的线程用来接收音频数据。我们再对两个接收线程加以控制,让它们按照一定的条件去读,保持音视频的同步就可以了。
还有要注意的是,PacketHeader、关键帧和Seek的问题。PacketHeader、关键帧照前面的分析改写,进行兼容性操作,对于Seek,以前是以视频流为主流,由于交错分布,音频跟着就读出来了,但是现在不行了,要音视频分别Seek,注意要以视频的timestamp为准,否则拖拽时就要出现画面迟钝。
最后,由于音视频分别读取,要注意,每个接收线程都要有一个dataOffset,而两个线程都要完成才能表示文件结束。