相关文章推荐
安静的梨子  ·  AWK - 孙愚 - 博客园·  1 年前    · 
帅呆的毛衣  ·  Actian Communities·  1 年前    · 
卖萌的热水瓶  ·  Docker - Percona ...·  1 年前    · 

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如下:

1. SeekHead
包含多个seek entry,每一个seek entry是EBML的一种类型的元素,SeekID对应EBML元素ID。SeekPosition对应元素在Segment中的位置。这个位置的base是SeekHead的起始位置,SeekPosition+SeekHead的起始位置才是元素在文件中的绝对位置。也就是说定位头Seekhead存储的信息是剪辑Segment里面的其他Master元素的类型和位置,比如剪辑信息SegmentInfo,轨道Tracks,索引表Cues,标签Tags信息所在的位置,这个位置信息也是个相对值,是定位头Seekhead的位置的相对值,实际地址等于定位头所在绝对位置+定位条目里面的位置SeekPosition。

2. Info

包含了Segment的时间戳base和时长。

以下是Info元素的解释:

3. Tracks
音视频,字幕流的描述信息,TrackNumber是流的编号,更像是流的序列号,在Block的头部中使用到的Track Number便是来自于此,TrackUID是Track的唯一标识,具有长达8个字节。它可能会在后面的Tag中用到,用来标识此Tag描述的Track。CodecID以及音视频播放的参数也保存在此元素中。

详细元素解释如下:

4. Chapters
用来预览文件里面的分段信息,它提前定义了一些时间点可供预览时跳到对应时间。
5. Cluster
Cluster包含了音频视频字幕轨道的数据流,Matroska文件至少包含一个Cluster元素,每个必须包含一个时间戳,它也是第一个Block的时间戳,这个时间戳是与Segment相对。每个Block里面包含了相对第一个Block时间戳的时间偏移,因此,Block里面的时间可以看作是Segment的时间偏移+Cluster的时间偏移+Block的时间偏移。
参考帧信息也在Block中描述 。如SimpleBlock的前几个字节为: A3 20 4F EC 81 00 00 80
A3表示是SimpleBlock, 20 4F EC表示长度为20460字节,81(8表示长度1字节,1表示Track Number为1) 00 00(两个字节的time code)
80要按位解析,最高位优先:
【  7 】是Keyframe设置为1,
【6-4】保留。
【  3 】此帧解码但不显示置1
【2-1】Lacing 标记。00:没有Lacing。01:Xiph Lacing。11:EBML Lacing。 10:固定长度Lacing。
【 0 】Block可以被丢弃置1.
BlockGroup中的ReferenceBlock用来表示Block类型。Block里面必须包括TrackNumber参数,用来描述当前的Block属于哪个Track,注意这儿用的是TrackNumber而不是TrackUID。所以一个 Cluster是可以同时包含Video,Audio,Subtitle数据的,这些数据存在于不同的Block中。

如下是Cluster中元素的详细解释:

看一个BlockGroup中包括Block的例子:

6. Cues
Cues元素用来对播放的音视频数据进行Seek的,它可以定位到特定的时间点进行播放。没有Cues的Seek比较麻烦,需要对Cluster进行parse才行。Cues中应该包含多个CuePoint才是合理的,Cues可以说成就是索引表。CuePoint时间戳放在CueTime Element中的,对应时间在文件中的位置放在CueTrackPositions中。注意CueTrackPositions并不是Cluster的绝对偏移,它是相对于Segment的偏移。也就是说,CueTrackPositions需要加上Segment在文件中的位置和Segment header的长度才是最终要查找的文件位置, Cue索引的Cluster在文件中的位置 =CueTrackPosition + Segment Pos + Segment Header size

元素解释如下:

7. Attachments
给Matroska文件添加一些附加的文件信息,比如图片,网页,程序等等。
8. Tags
Segment的metadata描述。

在笔者看来,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://www.xuebuyuan.com/zh-hant/1695652.html
https://www.xuebuyuan.com/zh-hant/2111311.html
https://www.matroska.org/technical/diagram.html
https://matroska-org.github.io/libebml/specs.html
https://github.com/ietf-wg-cellar/ebml-specification/blob/master/specification.markdown#ebml-element-types

MKV不是一种压缩格式,而是Matroska的一种媒体文件,Matroska是一种新的多媒体封装格式,也称多媒体容器 (Multimedia Container)。它可将多种不同编码的视频及16条以上不同格式的音频和不同语言的字幕流封装到一个Matroska Media文件当中。MKV最大的特点就是能容纳多种不同类型编码的视频、音频及字幕流。 Matroska最大的特点就是能容纳多种不
1. 什么是webM 要说webM,先说Matroska,Matroska是一个可扩展的,开源的多媒体容器(说简单点,容器的作用,就是把视频和音频封装到一个文件)。使用这种容器的常见文件,一个是MKV,一个就是webM。两者的区别,无非是支持的音视频编码不一样,但封装原理都一样。 更多的Matroska介绍,见官网:https://matroska.org/index.html webM的音视频支持: 视频编码:支持VP8或VP9 音频编码:支持Vorbis 或 Opus 要说Matroska,那又离不开E
Matroska 开源多媒体容器标准。MKV属于其中的一部分。 Matroska常见的有.MKV视频格式、MKA音频格式、.MKS字幕格式、.MK3D files (stereoscopic/3D video). 1.EBML(Extensible Binary Meta Language) MKV是建立在EBML这种语言的基础上,所以要了解MKV格式需要先了解EBML这种语言。
MKV is a flexible, open standard video file format that quickly became the preferred file extension for HD video on the internet. MKV是一种灵活的,开放的标准视频文件格式,很快成为Internet上高清视频的首选文件扩展名。 Matroska- MKV历史 (Mat...
在文章视频解码研究之PTS(2)Mp4格式,AVI格式MKV格式中,提到mkv格式的PTS,并编写程序对某MKV文件进行解析计算,得到各个block绝对时间码(Absolute Timecode)。程序十分简单,代码如下,帮助大家理解mkv文件的格式
回答: 目前没有直接解析.mkv格式的Kinect工具或方法。Kinect的数据通常以.xef或.gbg格式保存。.xef格式只能使用Kinect Studio读取,而.gbg格式可以使用GeometryHub的工具或Magic3D进行读取和转换。如果你想解析.mkv格式的Kinect数据,你可能需要先将.mkv文件转换为.xef或.gbg格式,然后再使用相应的工具进行解析和处理。 #### 引用[.reference_title] - *1* *3* [用kinect获取动态场景连续点云文件的方法](https://blog.csdn.net/liudejun8888/article/details/104054755)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Kinect使用(二)——Kinect获取深度图RGB-D](https://blog.csdn.net/github_39611196/article/details/82354916)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]