libmatroska 与 libebml 在 Android 平台上使用

222 阅读6分钟

上一节《libmatroska 与 libebml 移植到 Android 支持 mkv 格式解析与创建》成功编译了 Android 平台可用的 libmatroska.so 和 libebml.so。具体如何使用它们还没开始,这一节以创建 mkv 格式为例对它们进行使用。当然还要排坑!这花费了我不少力气去找到库中的 bug。

一、使用 libmatroska 与 libebml

首先在 Android Studio 中新建一个 Project。

  1. 复制上一节编译出的 so 和 头文件到 libs/ 路径下

在这里插入图片描述

  1. matroska_export.h 头文件是编译 libmatroska 时自动生成的,也需要将其拷贝到 libs/include/matroska 下,具体 libs/include/ 路径下头文件布局如下

在这里插入图片描述
在这里插入图片描述

  1. 使用 jni 调用 libmatroska.so 和 libebml.so

编写 java 层接口

class JniMatroskaCore {

    init {
        System.loadLibrary("ebml")
        System.loadLibrary("matroska")
        System.loadLibrary("matroska_core")
    }

    external fun writeMkvTest()
}

编写 native 层接口,这段代码是写 mkv 测试数据的。

#include <jni.h>
#include <iostream>

#include "ebml/StdIOCallback.h"

#include "ebml/EbmlHead.h"
#include "ebml/EbmlSubHead.h"
#include "ebml/EbmlVoid.h"
#include "matroska/FileKax.h"
#include "matroska/KaxSegment.h"
#include "matroska/KaxTracks.h"
#include "matroska/KaxCluster.h"
#include "matroska/KaxSeekHead.h"
#include "matroska/KaxCues.h"
#include "matroska/KaxInfoData.h"

using namespace LIBMATROSKA_NAMESPACE;
using namespace std;

const uint64 TIMECODE_SCALE = 1000000;

const bool bWriteDefaultValues = false;

extern "C"
JNIEXPORT void JNICALL
Java_<your package name>_JniMatroskaCore_writeMkvTest(JNIEnv *env, jobject thiz) {
    // write the head of the file (with everything already configured)
    StdIOCallback out_file("/storage/emulated/0/muxed.mkv", MODE_CREATE);
    / Writing EBML test
    EbmlHead FileHead;

    EDocType &MyDocType = GetChild<EDocType>(FileHead);
    *static_cast<EbmlString *>(&MyDocType) = "matroska";

    EDocTypeVersion &MyDocTypeVer = GetChild<EDocTypeVersion>(FileHead);
    *(static_cast<EbmlUInteger *>(&MyDocTypeVer)) = MATROSKA_VERSION;

    EDocTypeReadVersion &MyDocTypeReadVer = GetChild<EDocTypeReadVersion>(FileHead);
    *(static_cast<EbmlUInteger *>(&MyDocTypeReadVer)) = 1;

    FileHead.Render(out_file, bWriteDefaultValues);

    KaxSegment FileSegment;

    // size is unknown and will always be, we can render it right away
    uint64 SegmentSize = FileSegment.WriteHead(out_file, 5, bWriteDefaultValues);

    KaxTracks &MyTracks = GetChild<KaxTracks>(FileSegment);

    // reserve some space for the Meta Seek writen at the end
    EbmlVoid Dummy;
    Dummy.SetSize(300); // 300 octets
    Dummy.Render(out_file, bWriteDefaultValues);

    KaxSeekHead MetaSeek;

    // fill the mandatory Info section
    KaxInfo &MyInfos = GetChild<KaxInfo>(FileSegment);
    KaxTimecodeScale &TimeScale = GetChild<KaxTimecodeScale>(MyInfos);
    *(static_cast<EbmlUInteger *>(&TimeScale)) = TIMECODE_SCALE;

    KaxDuration &SegDuration = GetChild<KaxDuration>(MyInfos);
    *(static_cast<EbmlFloat *>(&SegDuration)) = 0.0;

    *((EbmlUnicodeString *) &GetChild<KaxMuxingApp>(MyInfos)) = L"libmatroska 1.5.2";
    *((EbmlUnicodeString *) &GetChild<KaxWritingApp>(MyInfos)) = L"Demo";
    GetChild<KaxWritingApp>(MyInfos).SetDefaultSize(25);

    filepos_t InfoSize = MyInfos.Render(out_file);
    MetaSeek.IndexThis(MyInfos, FileSegment);

    // fill track 1 params
    KaxTrackEntry &MyTrack1 = GetChild<KaxTrackEntry>(MyTracks);
    MyTrack1.SetGlobalTimecodeScale(TIMECODE_SCALE);

    KaxTrackNumber &MyTrack1Number = GetChild<KaxTrackNumber>(MyTrack1);
    *(static_cast<EbmlUInteger *>(&MyTrack1Number)) = 1;

    KaxTrackUID &MyTrack1UID = GetChild<KaxTrackUID>(MyTrack1);
    *(static_cast<EbmlUInteger *>(&MyTrack1UID)) = 7;

    *(static_cast<EbmlUInteger *>(&GetChild<KaxTrackType>(MyTrack1))) = track_audio;

    KaxCodecID &MyTrack1CodecID = GetChild<KaxCodecID>(MyTrack1);
    *static_cast<EbmlString *>(&MyTrack1CodecID) = "Dummy Audio Codec";

    MyTrack1.EnableLacing(true);

    // Test the new ContentEncoding elements
    KaxContentEncodings &cencodings = GetChild<KaxContentEncodings>(MyTrack1);
    KaxContentEncoding &cencoding = GetChild<KaxContentEncoding>(cencodings);
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentEncodingOrder>(cencoding))) = 10;
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentEncodingScope>(cencoding))) = 11;
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentEncodingType>(cencoding))) = 12;

    KaxContentCompression &ccompression = GetChild<KaxContentCompression>(cencoding);
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentCompAlgo>(ccompression))) = 13;
    GetChild<KaxContentCompSettings>(ccompression).CopyBuffer((const binary *) "hello1", 6);

    KaxContentEncryption &cencryption = GetChild<KaxContentEncryption>(cencoding);
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentEncAlgo>(cencryption))) = 14;
    GetChild<KaxContentEncKeyID>(cencryption).CopyBuffer((const binary *) "hello2", 6);
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentSigAlgo>(cencryption))) = 15;
    *(static_cast<EbmlUInteger *>(&GetChild<KaxContentSigHashAlgo>(cencryption))) = 16;
    GetChild<KaxContentSigKeyID>(cencryption).CopyBuffer((const binary *) "hello3", 6);
    GetChild<KaxContentSignature>(cencryption).CopyBuffer((const binary *) "hello4", 6);

    // audio specific params
    KaxTrackAudio &MyTrack1Audio = GetChild<KaxTrackAudio>(MyTrack1);

    KaxAudioSamplingFreq &MyTrack1Freq = GetChild<KaxAudioSamplingFreq>(MyTrack1Audio);
    *(static_cast<EbmlFloat *>(&MyTrack1Freq)) = 44100.0;
    MyTrack1Freq.ValidateSize();

    KaxAudioChannels &MyTrack1Channels = GetChild<KaxAudioChannels>(MyTrack1Audio);
    *(static_cast<EbmlUInteger *>(&MyTrack1Channels)) = 2;

    // fill track 2 params
    KaxTrackEntry &MyTrack2 = GetNextChild<KaxTrackEntry>(MyTracks, MyTrack1);
    MyTrack2.SetGlobalTimecodeScale(TIMECODE_SCALE);

    KaxTrackNumber &MyTrack2Number = GetChild<KaxTrackNumber>(MyTrack2);
    *(static_cast<EbmlUInteger *>(&MyTrack2Number)) = 200;

    KaxTrackUID &MyTrack2UID = GetChild<KaxTrackUID>(MyTrack2);
    *(static_cast<EbmlUInteger *>(&MyTrack2UID)) = 13;

    *(static_cast<EbmlUInteger *>(&GetChild<KaxTrackType>(MyTrack2))) = track_video;

    KaxCodecID &MyTrack2CodecID = GetChild<KaxCodecID>(MyTrack2);
    *static_cast<EbmlString *>(&MyTrack2CodecID) = "Dummy Video Codec";

    MyTrack2.EnableLacing(false);

    // video specific params
    KaxTrackVideo &MyTrack2Video = GetChild<KaxTrackVideo>(MyTrack2);

    KaxVideoPixelHeight &MyTrack2PHeight = GetChild<KaxVideoPixelHeight>(MyTrack2Video);
    *(static_cast<EbmlUInteger *>(&MyTrack2PHeight)) = 200;

    KaxVideoPixelWidth &MyTrack2PWidth = GetChild<KaxVideoPixelWidth>(MyTrack2Video);
    *(static_cast<EbmlUInteger *>(&MyTrack2PWidth)) = 320;

    uint64 TrackSize = MyTracks.Render(out_file, bWriteDefaultValues);

    KaxTracks *pMyTracks2 = static_cast<KaxTracks *>(MyTracks.Clone());

    MetaSeek.IndexThis(MyTracks, FileSegment);

    // "manual" filling of a cluster"
    /// \todo whenever a BlockGroup is created, we should memorize it's position
    KaxCues AllCues;
    AllCues.SetGlobalTimecodeScale(TIMECODE_SCALE);

    KaxCluster Clust1;
    Clust1.SetParent(FileSegment); // mandatory to store references in this Cluster
    Clust1.SetPreviousTimecode(0, TIMECODE_SCALE); // the first timecode here
    Clust1.EnableChecksum();

    // automatic filling of a Cluster
    // simple frame
    KaxBlockGroup *MyNewBlock, *MyLastBlockTrk1 = NULL, *MyLastBlockTrk2 = NULL, *MyNewBlock2;
    DataBuffer *data1 = new DataBuffer((binary *) "tototototo", countof("tototototo"));
    bool isSucc = Clust1.AddFrame(MyTrack1, 250 * TIMECODE_SCALE, *data1, MyNewBlock, LACING_EBML);

    if (MyNewBlock != NULL)
        MyLastBlockTrk1 = MyNewBlock;
    DataBuffer *data2 = new DataBuffer((binary *) "TOTOTOTO", countof("TOTOTOTO"));
    Clust1.AddFrame(MyTrack1, 260 * TIMECODE_SCALE, *data2, MyNewBlock); // to test EBML lacing
    if (MyNewBlock != NULL)
        MyLastBlockTrk1 = MyNewBlock;

    DataBuffer *data3 = new DataBuffer((binary *) "tototototo", countof("tototototo"));
    Clust1.AddFrame(MyTrack1, 270 * TIMECODE_SCALE, *data3, MyNewBlock); // to test lacing
    if (MyNewBlock != NULL) {
        MyLastBlockTrk1 = MyNewBlock;
    } else {
        MyLastBlockTrk1->SetBlockDuration(50 * TIMECODE_SCALE);
    }

    //KaxBlockBlob *Blob1 = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE);
    //Blob1->SetBlockGroup(*MyLastBlockTrk1);
    //AllCues.AddBlockBlob(*Blob1);

    DataBuffer *data4 = new DataBuffer((binary *) "tototototo", countof("tototototo"));
    Clust1.AddFrame(MyTrack2, 23 * TIMECODE_SCALE, *data4,
                    MyNewBlock); // to test with another track

    // frame for Track 2
    DataBuffer *data5 = new DataBuffer((binary *) "tttyyy", countof("tttyyy"));
    Clust1.AddFrame(MyTrack2, 107 * TIMECODE_SCALE, *data5, MyNewBlock);

    // simulate the writing of the stream :
    // - write an empty element with enough size for the cue entry
    // - write the cluster(s)
    // - seek back in the file and write the cue entry over the empty element

    uint64 ClusterSize = Clust1.Render(out_file, AllCues, bWriteDefaultValues);
    Clust1.ReleaseFrames();
    MetaSeek.IndexThis(Clust1, FileSegment);

    KaxCluster Clust2;
    Clust2.SetParent(FileSegment); // mandatory to store references in this Cluster
    Clust2.SetPreviousTimecode(300 * TIMECODE_SCALE, TIMECODE_SCALE); // the first timecode here
    Clust2.EnableChecksum();

    DataBuffer *data8 = new DataBuffer((binary *) "tttyyy", countof("tttyyy"));
    bool isDone = Clust2.AddFrame(MyTrack1, 350 * TIMECODE_SCALE, *data8, MyNewBlock, *MyLastBlockTrk1);
    LOGD("isDone=%d", isDone);

    ClusterSize += Clust2.Render(out_file, AllCues, bWriteDefaultValues);
    Clust2.ReleaseFrames();

    // older version, write at the end    AllCues.Render(out_file);
    filepos_t CueSize = AllCues.Render(out_file, bWriteDefaultValues);
    MetaSeek.IndexThis(AllCues, FileSegment);

    //delete Blob1;

    // Chapters
    KaxChapters Chapters;
    Chapters.EnableChecksum();
    KaxEditionEntry &aEdition = GetChild<KaxEditionEntry>(Chapters);
    KaxChapterAtom &aAtom = GetChild<KaxChapterAtom>(aEdition);
    KaxChapterUID &aUID = GetChild<KaxChapterUID>(aAtom);
    *static_cast<EbmlUInteger *>(&aUID) = 0x67890;

    KaxChapterTimeStart &aChapStart = GetChild<KaxChapterTimeStart>(aAtom);
    *static_cast<EbmlUInteger *>(&aChapStart) = 0;

    KaxChapterTimeEnd &aChapEnd = GetChild<KaxChapterTimeEnd>(aAtom);
    *static_cast<EbmlUInteger *>(&aChapEnd) = 300 * TIMECODE_SCALE;

    KaxChapterDisplay &aDisplay = GetChild<KaxChapterDisplay>(aAtom);
    KaxChapterString &aChapString = GetChild<KaxChapterString>(aDisplay);
    *static_cast<EbmlUnicodeString *>(&aChapString) = L"Le film réduit à un chapitre";

    KaxChapterLanguage &aChapLang = GetChild<KaxChapterLanguage>(aDisplay);
    *static_cast<EbmlString *>(&aChapLang) = "fra";

    KaxChapterDisplay &aDisplay2 = GetNextChild<KaxChapterDisplay>(aAtom, aDisplay);
    KaxChapterString &aChapString2 = GetChild<KaxChapterString>(aDisplay2);
    *static_cast<EbmlUnicodeString *>(&aChapString2) = L"The movie in one chapter";

    KaxChapterLanguage &aChapLang2 = GetChild<KaxChapterLanguage>(aDisplay2);
    *static_cast<EbmlString *>(&aChapLang2) = "eng";

    filepos_t ChapterSize = Chapters.Render(out_file, bWriteDefaultValues);
    MetaSeek.IndexThis(Chapters, FileSegment);

    // Write some tags
    KaxTags AllTags;
    AllTags.EnableChecksum();
    KaxTag &aTag = GetChild<KaxTag>(AllTags);
    KaxTagTargets &Targets = GetChild<KaxTagTargets>(aTag);
    KaxTagSimple &TagSimple = GetChild<KaxTagSimple>(aTag);

    KaxTagTrackUID &TrackUID = GetChild<KaxTagTrackUID>(Targets);
    *static_cast<EbmlUInteger *>(&TrackUID) = 0x12345;

    KaxTagChapterUID &ChapterUID = GetChild<KaxTagChapterUID>(Targets);
    *static_cast<EbmlUInteger *>(&ChapterUID) = 0x67890;

    KaxTagName &aTagName = GetChild<KaxTagName>(TagSimple);
    *static_cast<EbmlUnicodeString *>(&aTagName) = L"NAME";

    KaxTagString &aTagtring = GetChild<KaxTagString>(TagSimple);
    *static_cast<EbmlUnicodeString *>(&aTagtring) = L"Test?23";

    filepos_t TagsSize = AllTags.Render(out_file, bWriteDefaultValues);
    MetaSeek.IndexThis(AllTags, FileSegment);

    TrackSize += pMyTracks2->Render(out_file, bWriteDefaultValues);
    MetaSeek.IndexThis(*pMyTracks2, FileSegment);

    // \todo put it just before the Cue Entries
    filepos_t MetaSeekSize = Dummy.ReplaceWith(MetaSeek, out_file, bWriteDefaultValues);

    // let's assume we know the size of the Segment element
    // the size of the FileSegment is also computed because mandatory elements we don't write ourself exist
    if (FileSegment.ForceSize(SegmentSize - FileSegment.HeadSize() + MetaSeekSize
                              + TrackSize + ClusterSize + CueSize + InfoSize + TagsSize +
                              ChapterSize)) {
        FileSegment.OverwriteHead(out_file);
    }

    out_file.close();
}

运行以后你会发现,程序马上挂掉了,有坑,卧槽!

--------- beginning of crash
05-11 13:39:26.548 1400-1400/xxx.matroskamkvtest A/libc: Fatal signal 4 (SIGILL), code 1, fault addr 0xaf8518c0 in tid 1400 (matroskamkvtest)

二、修正 libmatroska

经过排查发现以上代码全部跑过了,但报错了,是因为内存释放造成的。某地址已经释放,再次 delete 以后出现的。原因定位到 KaxBlock.h 头文件。

添加 B Frame 会造成报错问题。

Clust2.AddFrame(MyTrack1, 350 * TIMECODE_SCALE, *data8, MyNewBlock, *MyLastBlockTrk1);

KaxCluster::AddFrame 实际调用 AddFrameInternal 完成 B Frame 数据添加。

KaxCluster.cpp

bool KaxCluster::AddFrame(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock, const KaxBlockGroup & PastBlock, LacingType lacing)
{
  assert(Blobs.size() == 0); // mutually exclusive for the moment
  return AddFrameInternal(track, timecode, buffer, MyNewBlock, &PastBlock, NULL, lacing);
}

因为我们传入的 PastBlock 不为 NULL,因此进入 1.这里实际发生添加帧数据 注释处。

KaxCluster.cpp

bool KaxCluster::AddFrameInternal(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, KaxBlockGroup * & MyNewBlock, const KaxBlockGroup * PastBlock, const KaxBlockGroup * ForwBlock, LacingType lacing)
{
  ......

  MyNewBlock = NULL;

  if (lacing == LACING_NONE || !track.LacingEnabled()) {
    currentNewBlock = NULL;
  }

  // 强制创建一个新块
  if (currentNewBlock == NULL || uint32(track.TrackNumber()) != uint32(currentNewBlock->TrackNumber()) || PastBlock != NULL || ForwBlock != NULL) {
    KaxBlockGroup & aNewBlock = GetNewBlock();
    MyNewBlock = currentNewBlock = &aNewBlock;
  }

  if (PastBlock != NULL) {
    if (ForwBlock != NULL) {
      ......
    } else {
      // 1.这里实际发生添加帧数据
      if (currentNewBlock->AddFrame(track, timecode, buffer, *PastBlock, lacing)) {
        // 此区块中允许更多数据
        return true;
      } else {
        currentNewBlock = NULL;
        return false;
      }
    }
  } else {
    ......
  }
}

下面的注释是函数的执行流程说明。我们重点关注 SetReferencedBlock。

KaxBlock.cpp

bool KaxBlockGroup::AddFrame(const KaxTrackEntry & track, uint64 timecode, DataBuffer & buffer, const KaxBlockGroup & PastBlock, LacingType lacing)
{
  //  assert(past_timecode < 0);
  // 获取 KaxBlock 对象
  KaxBlock & theBlock = GetChild<KaxBlock>(*this);
  assert(ParentCluster != NULL);
  // 设置 Parent
  theBlock.SetParent(*ParentCluster);
  ParentTrack = &track;
  // Block 中添加帧数据
  bool bRes = theBlock.AddFrame(track, timecode, buffer, lacing);
  // 获取一个 ReferenceBlock
  KaxReferenceBlock & thePastRef = GetChild<KaxReferenceBlock>(*this);
  // 将 PastBlock 关联到 ReferenceBlock 上
  thePastRef.SetReferencedBlock(PastBlock);
  thePastRef.SetParentBlock(*this);

  return bRes;
}

首先获取了一个获取 BlockBlob 对象,接着调用 SetBlockGroup 将传递来的 PastBlock 引用传入。

KaxBlockData.cpp

void KaxReferenceBlock::SetReferencedBlock(const KaxBlockGroup & aRefdBlock)
{
  FreeBlob();
  // 获取 BlockBlob
  KaxBlockBlob *block_blob = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE);
  // 给 BlockBlob 设置 BlockGroup
  block_blob->SetBlockGroup(*const_cast<KaxBlockGroup*>(&aRefdBlock));
  ......
}

SetBlockGroup 函数非常简单,只是将 PastBlock 引用赋值给 Block.group。

KaxBlock.cpp

void KaxBlockBlob::SetBlockGroup( KaxBlockGroup &BlockRef )
{
  assert(!bUseSimpleBlock);
  Block.group = &BlockRef;
}

问题就出在这里了,在我们释放 KaxBlockBlob 的时候会删除 PastBlock 对象。

KaxBlock.h

~KaxBlockBlob() {
#if MATROSKA_VERSION >= 2
    if (bUseSimpleBlock)
      delete Block.simpleblock;
    else
#endif // MATROSKA_VERSION
      delete Block.group;
  }

因为我们传递过来的 PastBlock 对象是通过 GetNewBlock 生成的,它会 new 一个 KaxBlockGroup 对象,并将其加入到 std::vector 容器中,最后析构的时候会遍历 std::vector,删除所有元素会释放 KaxBlockGroup 对象。

GetNewBlock 调用 AddNewChild 函数获取 KaxBlockGroup。

KaxCluster.cpp

KaxBlockGroup & KaxCluster::GetNewBlock()
{
  KaxBlockGroup & MyBlock = AddNewChild<KaxBlockGroup>(*this);
  MyBlock.SetParent(*this);
  return MyBlock;
}

进入此模板函数。

EbmlMaster.h

template <typename Type>
Type & AddNewChild(EbmlMaster & Master)
{
  return *(static_cast<Type *>(Master.AddNewElt(EBML_INFO(Type))));
}

创建新元素 EbmlElement,然后将其添加到 ElementList。

EbmlMaster.cpp

EbmlElement *EbmlMaster::AddNewElt(const EbmlCallbacks & Callbacks)
{
  // 创建新元素 EbmlElement
  EbmlElement *NewElt = &EBML_INFO_CREATE(Callbacks);
  if (NewElt == NULL)
    return NULL;
  // 将新元素添加到 ElementList(std::vector<EbmlElement *>)
  if (!PushElement(*NewElt)) {
    delete NewElt;
    NewElt = NULL;
  }
  return NewElt;
}

EBML_INFO_CREATE 宏定义为调用 Create 方法。

EbmlMaster.cpp

#define EBML_INFO_CREATE(cb)  (cb).Create()

再来分析 KaxBlockGroup 类定义。

KaxBlock.h

DECLARE_MKX_MASTER(KaxBlockGroup)
  public:
    ~KaxBlockGroup();

DECLARE_MKX_MASTER 宏定义。

KaxDefines.h

#define DECLARE_MKX_CONTEXT(x)

#define DECLARE_MKX_MASTER(x)     DECLARE_MKX_CONTEXT(x) \
class MATROSKA_DLL_API x : public EbmlMaster { \
    public: x(); \
    x(const x & ElementToClone) :EbmlMaster(ElementToClone) {} \
    EBML_CONCRETE_CLASS(x)

EBML_CONCRETE_CLASS 宏定义。

EbmlElement.h

#define EBML_CONCRETE_CLASS(Type) \
    public: \
    virtual const EbmlCallbacks & Generic() const {return ClassInfos;} \
    virtual operator const EbmlId &() const {return ClassInfos.GlobalId;} \
        virtual EbmlElement & CreateElement() const {return Create();} \
        virtual EbmlElement * Clone() const { return new Type(*this); } \
    static EbmlElement & Create() {return *(new Type);} \
    static const EbmlCallbacks ClassInfos; \

最后不难知道,创建 KaxBlockGroup 对象,首先返回一个 EbmlElement 对象,然后强转为 KaxBlockGroup 对象。

两次内存释放造成了这个 bug。那么修改问题就变得简单了,注释 KaxBlockBlob 析构函数中删除 PastBlock 对象即可。

KaxBlock.h

  ~KaxBlockBlob() {
/*#if MATROSKA_VERSION >= 2
    if (bUseSimpleBlock)
      delete Block.simpleblock;
    else
#endif // MATROSKA_VERSION
      delete Block.group;*/
  }

重新编译 libmatroska,替换 KaxBlock.h 头文件,再次运行问题已经解决了。

三、查看 mkv 数据

上面的代码,写入了一些测试数据到 /storage/emulated/0/muxed.mkv,使用 EBMLTree 工具查看是否写入成功。

在这里插入图片描述