mkv封装格式+ebml语法

文章部分内容参考https://www.matroska.org/technical/specs/index.html

1.mkv封装格式简介


  • Matroska 开源多媒体容器标准。MKV属于其中的一部分。Matroska常见的有.MKV视频格式、MKA音频格式、.MKS字幕格式、.MK3D files (stereoscopic/3D video)。MKV这种多媒体封装是建立在EBML语言的基础上,EBML是可扩展的二进制语言,优势在于其可变长度的整数存储,节省空间。

2.EBML语言

  • EBML中级别
    EBML中的形式表达是树形结构,对应元素出现的树节点深度定义为级别,最高级别为0,类似于树的根节点,特殊级别表达:“+” 表示该元素在该层级可递归存在;“g” 表示为存在于任意级别。
    mkv文件的组织可以看作有两部分组成:
    1.首先是EBML Header 2.segment ,mkv/wbem文件中只包含一个segment,包含其它TOP-LEVEL级别节点。
  • EBML中的语义(EbmlSemantic)
    EbmlSemantic:语义规范是预定义好的。元素基本的语义规则可总结为两类
    value和bool语义: 其中有关bool的描述Mandatory(必须包含)和Unique(是否包含一个);有关value的描述有EbmlUInteger、EbmlDate、EbmlFloat、EbmlSInteger、EbmlUnicodeString。而标识元素最为显著的属性为EbmlId。
const EbmlSemantic EbmlHead_ContextList[] =
{
    EbmlSemantic(true, true, EVersion::ClassInfos),        ///< EBMLVersion
    EbmlSemantic(true, true, EReadVersion::ClassInfos),    ///< EBMLReadVersion
    EbmlSemantic(true, true, EMaxIdLength::ClassInfos),    ///< EBMLMaxIdLength
    EbmlSemantic(true, true, EMaxSizeLength::ClassInfos),  ///< EBMLMaxSizeLength
    EbmlSemantic(true, true, EDocType::ClassInfos),        ///< DocType
    EbmlSemantic(true, true, EDocTypeVersion::ClassInfos), ///< DocTypeVersion
    EbmlSemantic(true, true, EDocTypeReadVersion::ClassInfos), ///< DocTypeReadVersion
};

//EbmlSemanticContext上下文,需要
const EbmlSemanticContext EbmlHead_Context = EbmlSemanticContext(countof(EbmlHead_ContextList), EbmlHead_ContextList, NULL, *GetEbmlGlobal_Context, &EbmlHead::ClassInfos);

EbmlId EbmlHead_TheId(0x1A45DFA3, 4);  //容器格式的可辨识行信息;
const EbmlCallbacks EbmlHead::ClassInfos(EbmlHead::Create, EbmlHead_TheId, "EBMLHead", EbmlHead_Context);
EbmlSemantic KaxSegment_ContextList[8] =
{
    EbmlSemantic(false, false, KaxCluster::ClassInfos),    //
    EbmlSemantic(false, false, KaxSeekHead::ClassInfos),  //SeekHead;
    EbmlSemantic(false, true,  KaxCues::ClassInfos),      //speed seeking;
    EbmlSemantic(false, false, KaxTracks::ClassInfos),    //包含有Tracks;
    EbmlSemantic(true,  true,  KaxInfo::ClassInfos),     //segment infomation;
    EbmlSemantic(false, true,  KaxChapters::ClassInfos),
    EbmlSemantic(false, true,  KaxAttachements::ClassInfos),
    EbmlSemantic(false, true,  KaxTags::ClassInfos),
};

const EbmlSemanticContext KaxMatroska_Context = EbmlSemanticContext(countof(KaxMatroska_ContextList), KaxMatroska_ContextList, NULL, *GetKaxGlobal_Context, NULL);
const EbmlSemanticContext KaxSegment_Context = EbmlSemanticContext(countof(KaxSegment_ContextList), KaxSegment_ContextList, NULL, *GetKaxGlobal_Context, &KaxSegment::ClassInfos);

EbmlId KaxSegment_TheId(0x18538067, 4);
const EbmlCallbacks KaxSegment::ClassInfos(KaxSegment::Create, KaxSegment_TheId, "Segment", KaxSegment_Context);

2.EbmlMaster
派生于EbmlMaster的为Top-Level级节点,缺少value语义,主要用来控制下层级别节点,这里以KaxCluster为例进行说明:

class KaxCluster : public EbmlMaster {
    public:
        KaxCluster();
        static EbmlElement & Create() {return *(new KaxCluster);}
        const EbmlCallbacks & Generic() const {return ClassInfos;}
        static const EbmlCallbacks ClassInfos;
        operator const EbmlId &() const {return ClassInfos.GlobalId;}
        /*I帧*/
        bool AddFrame(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock);
        /*P帧*/
        bool AddFrame(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock, const KaxBlockGroup & PastBlock);

        /*B帧*/
        bool AddFrame(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock, const KaxBlockGroup & PastBlock, const KaxBlockGroup & ForwBlock);

        /*!
            \brief Render the data to the stream and retrieve the position of BlockGroups for later cue entries
        */
        uint32 Render(IOCallback & output, KaxCues & CueToUpdate, bool bSaveDefault = false);

        /*!
            \return the global timecode of this Cluster
        */
        uint64 GlobalTimecode() const;

        KaxBlockGroup & GetNewBlock();

        /*!
            \brief release all the frames of all Blocks
            \note this is a convenience to be able to keep Clusters+Blocks in memory (for future reference) withouht being a memory hog
        */
        void ReleaseFrames();

        /*返回offset偏移量*/
        uint64 GetPosition() const;
        void SetParent(const KaxSegment & aParentSegment) {ParentSegment = &aParentSegment;}
        void SetPreviousTimecode(uint64 aPreviousTimecode, int64 aTimecodeScale) {
            bPreviousTimecodeIsSet = true; 
            PreviousTimecode = aPreviousTimecode;
            SetGlobalTimecodeScale(aTimecodeScale);
        }

        /*!
            \note dirty hack to get the mandatory data back after reading
            \todo there should be a better way to get mandatory data
        */
        void InitTimecode(uint64 aTimecode, int64 aTimecodeScale) {
            SetGlobalTimecodeScale(aTimecodeScale);
            MinTimecode = MaxTimecode = PreviousTimecode = aTimecode * TimecodeScale;
            bFirstFrameInside = bPreviousTimecodeIsSet = true;
        }

        int16 GetBlockLocalTimecode(uint64 GlobalTimecode) const;

        uint64 GetBlockGlobalTimecode(int16 LocalTimecode);

        void SetGlobalTimecodeScale(uint64 aGlobalTimecodeScale) {
            TimecodeScale = aGlobalTimecodeScale;
            bTimecodeScaleIsSet = true;
        }
        uint64 GlobalTimecodeScale() const {
            assert(bTimecodeScaleIsSet); 
            return TimecodeScale;
        }

    protected:
        KaxBlockGroup    * currentNewBlock;
        const KaxSegment * ParentSegment;

        uint64 MinTimecode, MaxTimecode, PreviousTimecode;
        int64  TimecodeScale;

        bool bFirstFrameInside; // used to speed research
        bool bPreviousTimecodeIsSet;
        bool bTimecodeScaleIsSet;

        /*!
            \note method used internally
        */
        bool AddFrameInternal(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock, const KaxBlockGroup * PastBlock, const KaxBlockGroup * ForwBlock);

};

KaxCluster记录了一个最小时间戳和最大时间戳参数值,AddFrameInternal函数接口实现添加帧并更新时间戳,参数MyNewBlock为动态创建的GroupBlock对应引用。KaxCluster记录一个当前最新BlockGroup节点currentNewBlock
,通过currentNewBlock来新增Block类型节点,并返回一个指针引用给MyNewBlock。
那么接下来分析一下EbmlMaster中的UpdateSize函数

uint64 EbmlMaster::UpdateSize(bool bSaveDefault){
Size = 0;
    assert(CheckMandatory());
    size_t Index;
    for (Index = 0; Index < ElementList.size(); Index++) {
        if (!bSaveDefault && (ElementList[Index])->IsDefaultValue())
            continue;
        (ElementList[Index])->UpdateSize(bSaveDefault);

        uint64 SizeToAdd = (ElementList[Index])->ElementSize(bSaveDefault);
        Size += SizeToAdd;
    }
    return Size;
}

返回值Size的大小由所有子节点大小的总和,获取大小之前需要将元素大小进行更新调用接口UpdateSize(对比元素节点默认值)。而ElementSize大小的计算通过Size + EbmlId(*this).Length + CodedSizeLength()得到。元素的大小=value对应的长度Size +id对应的长度Length +表示Size用的字节数。

EbmlElement元素保存

uint32 EbmlElement::MakeRenderHead(IOCallback & output)
{
    binary FinalHead[4+8]; // Class D + 64 bits coded size
    unsigned int FinalHeadSize;
    FinalHeadSize = EbmlId(*this).Length;
    EbmlId(*this).Fill(FinalHead);
    int CodedSize = CodedSizeLength();
    // Set the EBML bits
    FinalHead[FinalHeadSize] = 1 << (8 - CodedSize);
    //后面位表达codesize的个数;
    uint64 TempSize = GetSize();
    int SizeMask = 0xFF;
    for (int i=1; i<CodedSize; i++) {
        FinalHead[FinalHeadSize + CodedSize - i] = TempSize & 0xFF; //256取余数;
        TempSize >>= 8;  //256取得数;
        SizeMask >>= 1; 
    }
//  SizeMask <<= 1;
    // first one use a OR with the "EBML size head"
    FinalHead[FinalHeadSize] |= TempSize & 0xFF & SizeMask;
    FinalHeadSize += CodedSize; 
    output.writeFully(FinalHead, FinalHeadSize);
    ElementPosition = output.getFilePointer() - FinalHeadSize;
    SizePosition = ElementPosition + EbmlId(*this).Length;
    return FinalHeadSize;
}

继续对block的分析,
Size = 1 + (1-8) + 4 + (4 + (4)) octets. So from 6 to 21 octets.
1字节可得到track对应的num;
2-3可得到block对应的时间戳;
4字节可分为高低8位进一步获取关键帧、lace、是否可见、可丢弃等参数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MKVToolNix 是一组用于创建,更改,拆分,合并和检查 Matroska 文件(MKV)的工具。借助这些工具,您可以获取有关(mkvinfo)Matroska 文件的信息,从(mkvextract)Matroska 文件中提取曲目/数据,以及从其他媒体文件创建(mkvmerge)Matroska 文件。 Matroska 是一种新的多媒体文件格式,旨在成为未来的新容器格式。 您可以在 http://www.matroska.org/ 上找到有关它及其基础技术,可扩展二进制元语言(EBML)的更多信息。 MKV 封装工具 MKVToolnix 中文版MKVToolnix 中文版 MKVToolnix 是一个高级应用程序,使您可以打开,检查,编辑和混合 Matroska 文件(.mkv)。它也支持其他流行的视频文件类型,以及音轨和字幕。 完整的软件包包括单独的命令行实用程序,用于合并和提取流,查看信息以及编辑标题和章节。它们全部包装在 Windows 版本的图形界面中。 Linux 用户可以使用 Linux 的 MKVToolnix。 检查和处理 Matroska 文件 该工具使您可以查看,附加和拆分轨道,章节和标签,以及编辑各种数据。例如,当涉及常规轨道选项时,您可以设置轨道名称,语言,默认和强制轨道标志,标签以及时间码。 此外,您可以设置纵横比,FPS,延迟,立体镜模式,裁切,提示,压缩模式和自定义命令行参数,以增强功能。外部文件可以作为附件嵌入电影中。 创建和编辑电影章节 还可以创建章并定义属性,例如开始和结束时间,标志,分段和分段版本 UID,以及章名,语言和国家/地区。可配置的全局设置集中于标记文件,是否创建 WebM 兼容文件,拆分模式,文件链接等。 值得考虑的一个重要方面是 MKVToolnix 提供对批处理作业的支持,这意味着您可以在该工具执行耗时的任务时让工作站处于无人看管的状态。 您可以检查当前命令行并将其复制到剪贴板,或将其保存到文件中以供将来的项目使用,管理队列作业,添加命令行选项,从外部文件加载章节,编辑标题以及保存项目是 .mka,.mkv 或 .mk3d 格式的文件。 评估与结论 在我们的测试中,该工具使用较低的 CPU 和 RAM 并不会给计算机性能带来压力。它可以在相当长的时间内执行任务,并且不会触发操作系统挂起,崩溃或提示错误消息。 考虑到其广泛的配置参数,MKVToolnix 应该满足大多数研究,创建,编辑和生成 MKV 文件的专家用户的要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值