mkv封装格式+ebml语法
文章部分内容参考https://www.matroska.org/technical/specs/index.html1.mkv封装格式简介Matroska 开源多媒体容器标准。MKV属于其中的一部分。Matroska常见的有.MKV视频格式、MKA音频格式、.MKS字幕格式、.MK3D files (stereoscopic/3D video)。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)