大家好,欢迎来到IT知识分享网。
MKV 文件格式解析
前言
MKV(Matroska Video File)是一种Matroska媒体格式的多媒体封装格式(Multimedia Container Format,简称MCF)。但Matroska媒体格式除了MKV外,常见的还有MKA (Matroska Audio File)单一音频文件,可以有多条及多种类型的音轨,MKS (Matroska Subtitles)字幕文件,Matroska来自于俄语,影射俄罗斯娃娃,就是下面这个啦,表示一层包着另外一层。
MKV采用可扩展二进制元语言EBML(Extensible Binary Meta Language)来描述其文件结构,EBML用元素(Elements)来描述EBML文档,组织结构如下:
Element元素ID用大端方式编码,起始位0的个数代表了ID的长度,ID长度=起始0的个数+1。除起始位0外,其余bit全1的ID为保留 ID,元素ID可以包括其子ID。
Element数据长度也采用大端方式编码,起始位0的个数代表了数据长度(包括头部)占用的字节个数,字节个数=起始0的个数+1。可以是1~8个字节:
EBML组成的文件包含EBML头和EBML体两部分。Matroska 文件可以看作是包含EBML头和Segment两部分的文件。
MKV文件分析
MKV文件分析工具有 EBML Tree Viewer AVI-Mux GUI和MKVToolnix,下面图片是用AVI-Mux解析的某MKV文件
EBML头
Segment
Matroska 文件的segment包含了音视频数据和播放音视频数据所需要的信息。Matroska 文件中可以有多个segment,但不是所有的播放器都支持这样做。Segment的结构示意图如下:
Segment的第一级子元素元素名及ID如下:
SeekPosition对应元素在Segment中的位置。这个位置的base是SeekHead的起始位置,SeekPosition+SeekHead的起始位置才是元素在文件中的绝对位置。也就是说定位头Seekhead存储的信息是剪辑Segment里面的其他Master元素的类型和位置,比如剪辑信息SegmentInfo,轨道Tracks,索引表Cues,标签Tags信息所在的位置,这个位置信息也是个相对值,是定位头Seekhead的位置的相对值,实际地址等于定位头所在绝对位置+定位条目里面的位置SeekPosition。
2. Info
包含了Segment的时间戳base和时长。
以下是Info元素的解释:
详细元素解释如下:
如下是Cluster中元素的详细解释:
看一个BlockGroup中包括Block的例子:
元素解释如下:
后记:
在笔者看来,MKV文件格式中如果出现了错误,寻求一个完美的解决方案相当麻烦。MKV中的元素ID不具有唯一性,可能和数据相同,这就造成了在resync的时候,误把数据当成元素ID的可能性。
Gstreamer的MKV parse
static GstFlowReturn gst_matroska_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { …… /* 非连续,清除adapter里面的buffer及信息 */ if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buffer))) { GST_DEBUG_OBJECT (parse, "got DISCONT"); gst_adapter_clear (parse->common.adapter); GST_OBJECT_LOCK (parse); gst_matroska_read_common_reset_streams (&parse->common, GST_CLOCK_TIME_NONE, FALSE); GST_OBJECT_UNLOCK (parse); } gst_adapter_push (parse->common.adapter, buffer); buffer = NULL; next: available = gst_adapter_available (parse->common.adapter); /* 获取下一个id及元素的大小length,needed是元素id本身占用的字节数加上元素长度length所占用的字节数(比如: * 00000000 1a 45 df a3 a3 42 86 81 .E...B.. * 00000008 01 42 f7 81 01 42 f2 81 .B...B.. * 00000010 04 42 f3 81 08 42 82 88 .B...B.. * 00000018 6d 61 74 72 6f 73 6b 61 matroska * 00000020 42 87 81 04 42 85 81 02 B...B... * EBML头ID为0x1A45DFA3,占用4字节,它的长度字段0xa3占用一个字节, * 头里面的内容占用35个字节,0xa开头没有0,故用一字节,则ID=0x1A45DFA3,length=0x23,need=5), 如果长度字段全部是FF的话,则设置成最大长度G_MAXUINT64 */ ret = gst_matroska_read_common_peek_id_length_push (&parse->common, GST_ELEMENT_CAST (parse), &id, &length, &needed); /* 返回值一共三种,OK,EOS或者ERROR,ERROR时代表长度或者ID错误 */ if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_EOS)) { /* ebml_segment_length未设置默认值,但遇到ID_SEGMENT时,会配置这个参数,文件中这个参数为0时,会配置成G_MAXUINT64,ebml_segment_start就是segment在文件中开始的位置 */ if (parse->common.ebml_segment_length != G_MAXUINT64 && parse->common.offset >= parse->common.ebml_segment_start + parse->common.ebml_segment_length) { /* 代表处理过的数据已经超过了段segment的长度。 */ return GST_FLOW_EOS; } else { /* * 获取ID或者长度出现错误,设置成SCANNING,事实上gstreamer只有在CLUSTER时,才支持,非CLUSTER相关的ID在scanning的时候会被丢弃,所以如果其他信息错误,是无法播放的。 * parsing error: we need to flush a byte from the adapter if the id is * not a cluster and so on until we found a new cluser or the * INVALID_DATA_THRESHOLD is exceeded, we reuse gst_matroska_parse_parse_id * setting the state to GST_MATROSKA_READ_STATE_SCANNING so the bytes * are skipped until a new cluster is found */ gint64 bytes_scanned; if (parse->common.start_resync_offset == -1) { /* 标记开始查找下一个ID的开始位置 */ parse->common.start_resync_offset = parse->common.offset; parse->common.state_to_restore = parse->common.state; } bytes_scanned = parse->common.offset - parse->common.start_resync_offset; /* 重新同步的数据量不大,还可以继续同步 */ if (bytes_scanned <= INVALID_DATA_THRESHOLD) { GST_WARNING_OBJECT (parse, "parse error, looking for next cluster, actual offset %" G_GUINT64_FORMAT ", start resync offset %" G_GUINT64_FORMAT, parse->common.offset, parse->common.start_resync_offset); parse->common.state = GST_MATROSKA_READ_STATE_SCANNING; ret = GST_FLOW_OK; } else { /* 重新同步的数据量太大了,返回错误 */ GST_WARNING_OBJECT (parse, "unrecoverable parse error, next cluster not found and threshold " "exceeded, bytes scanned %" G_GINT64_FORMAT, bytes_scanned); return ret; } } } GST_LOG_OBJECT (parse, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, " "size %" G_GUINT64_FORMAT ", needed %d, available %d", parse->common.offset, id, length, needed, available); /* ID长度及长度字段的字节数大于可用数据量,返回 */ if (needed > available) return GST_FLOW_OK; ret = gst_matroska_parse_parse_id (parse, id, length, needed); if (ret == GST_FLOW_EOS) { /* need more data */ return GST_FLOW_OK; } else if (ret != GST_FLOW_OK) { return ret; } else goto next; }
参考网页:
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/118207.html




















