int cluster_helper_c::render() { std::vector<render_groups_cptr> render_groups; KaxCues cues; cues.SetGlobalTimecodeScale(g_timecode_scale); bool use_simpleblock = !hack_engaged(ENGAGE_NO_SIMPLE_BLOCKS); LacingType lacing_type = hack_engaged(ENGAGE_LACING_XIPH) ? LACING_XIPH : hack_engaged(ENGAGE_LACING_EBML) ? LACING_EBML : LACING_AUTO; int64_t min_cl_timecode = std::numeric_limits<int64_t>::max(); int64_t max_cl_timecode = 0; int elements_in_cluster = 0; bool added_to_cues = false; // Splitpoint stuff if ((-1 == m->header_overhead) && splitting()) m->header_overhead = m->out->getFilePointer() + g_tags_size; // Make sure that we don't have negative/wrapped around timecodes in the output file. // Can happend when we're splitting; so adjust timecode_offset accordingly. m->timecode_offset = boost::accumulate(m->packets, m->timecode_offset, [](int64_t a, const packet_cptr &p) { return std::min(a, p->assigned_timecode); }); int64_t timecode_offset = m->timecode_offset + get_discarded_duration(); for (auto &pack : m->packets) { generic_packetizer_c *source = pack->source; bool has_codec_state = !!pack->codec_state; if (g_video_packetizer == source) m->max_video_timecode_rendered = std::max(pack->assigned_timecode + pack->get_duration(), m->max_video_timecode_rendered); if (discarding()) { if (-1 == m->first_discarded_timecode) m->first_discarded_timecode = pack->assigned_timecode; m->last_discarded_timecode_and_duration = std::max(m->last_discarded_timecode_and_duration, pack->assigned_timecode + pack->get_duration()); continue; } if (source->contains_gap()) m->cluster->SetSilentTrackUsed(); render_groups_c *render_group = nullptr; for (auto &rg : render_groups) if (rg->m_source == source) { render_group = rg.get(); break; } if (!render_group) { render_groups.push_back(render_groups_cptr(new render_groups_c(source))); render_group = render_groups.back().get(); } min_cl_timecode = std::min(pack->assigned_timecode, min_cl_timecode); max_cl_timecode = std::max(pack->assigned_timecode, max_cl_timecode); DataBuffer *data_buffer = new DataBuffer((binary *)pack->data->get_buffer(), pack->data->get_size()); KaxTrackEntry &track_entry = static_cast<KaxTrackEntry &>(*source->get_track_entry()); kax_block_blob_c *previous_block_group = !render_group->m_groups.empty() ? render_group->m_groups.back().get() : nullptr; kax_block_blob_c *new_block_group = previous_block_group; if (!pack->is_key_frame() || has_codec_state || pack->has_discard_padding()) render_group->m_more_data = false; if (!render_group->m_more_data) { set_duration(render_group); render_group->m_durations.clear(); render_group->m_duration_mandatory = false; BlockBlobType this_block_blob_type = !use_simpleblock ? BLOCK_BLOB_NO_SIMPLE : must_duration_be_set(render_group, pack) ? BLOCK_BLOB_NO_SIMPLE : !pack->data_adds.empty() ? BLOCK_BLOB_NO_SIMPLE : has_codec_state ? BLOCK_BLOB_NO_SIMPLE : pack->has_discard_padding() ? BLOCK_BLOB_NO_SIMPLE : BLOCK_BLOB_ALWAYS_SIMPLE; render_group->m_groups.push_back(kax_block_blob_cptr(new kax_block_blob_c(this_block_blob_type))); new_block_group = render_group->m_groups.back().get(); m->cluster->AddBlockBlob(new_block_group); new_block_group->SetParent(*m->cluster); added_to_cues = false; } // Now put the packet into the cluster. render_group->m_more_data = new_block_group->add_frame_auto(track_entry, pack->assigned_timecode - timecode_offset, *data_buffer, lacing_type, pack->has_bref() ? pack->bref - timecode_offset : -1, pack->has_fref() ? pack->fref - timecode_offset : -1); if (has_codec_state) { KaxBlockGroup &bgroup = (KaxBlockGroup &)*new_block_group; KaxCodecState *cstate = new KaxCodecState; bgroup.PushElement(*cstate); cstate->CopyBuffer(pack->codec_state->get_buffer(), pack->codec_state->get_size()); } if (-1 == m->first_timecode_in_file) m->first_timecode_in_file = pack->assigned_timecode; if (-1 == m->first_timecode_in_part) m->first_timecode_in_part = pack->assigned_timecode; m->min_timecode_in_file = std::min(timecode_c::ns(pack->assigned_timecode), m->min_timecode_in_file.value_or_max()); m->max_timecode_in_file = std::max(pack->assigned_timecode, m->max_timecode_in_file); m->max_timecode_and_duration = std::max(pack->assigned_timecode + pack->get_duration(), m->max_timecode_and_duration); if (!pack->is_key_frame() || !track_entry.LacingEnabled()) render_group->m_more_data = false; render_group->m_durations.push_back(pack->get_unmodified_duration()); render_group->m_duration_mandatory |= pack->duration_mandatory; cues_c::get().set_duration_for_id_timecode(source->get_track_num(), pack->assigned_timecode - timecode_offset, pack->get_duration()); if (new_block_group) { // Set the reference priority if it was wanted. if ((0 < pack->ref_priority) && new_block_group->replace_simple_by_group()) GetChild<KaxReferencePriority>(*new_block_group).SetValue(pack->ref_priority); // Handle BlockAdditions if needed if (!pack->data_adds.empty() && new_block_group->ReplaceSimpleByGroup()) { KaxBlockAdditions &additions = AddEmptyChild<KaxBlockAdditions>(*new_block_group); size_t data_add_idx; for (data_add_idx = 0; pack->data_adds.size() > data_add_idx; ++data_add_idx) { auto &block_more = AddEmptyChild<KaxBlockMore>(additions); GetChild<KaxBlockAddID >(block_more).SetValue(data_add_idx + 1); GetChild<KaxBlockAdditional>(block_more).CopyBuffer((binary *)pack->data_adds[data_add_idx]->get_buffer(), pack->data_adds[data_add_idx]->get_size()); } } if (pack->has_discard_padding()) GetChild<KaxDiscardPadding>(*new_block_group).SetValue(pack->discard_padding.to_ns()); } elements_in_cluster++; if (!new_block_group) new_block_group = previous_block_group; else if (g_write_cues && (!added_to_cues || has_codec_state)) { added_to_cues = add_to_cues_maybe(pack); if (added_to_cues) cues.AddBlockBlob(*new_block_group); } pack->group = new_block_group; m->track_statistics[ source->get_uid() ].process(*pack); } if (!discarding()) { if (0 < elements_in_cluster) { for (auto &rg : render_groups) set_duration(rg.get()); m->cluster->SetPreviousTimecode(min_cl_timecode - timecode_offset - 1, (int64_t)g_timecode_scale); m->cluster->set_min_timecode(min_cl_timecode - timecode_offset); m->cluster->set_max_timecode(max_cl_timecode - timecode_offset); m->cluster->Render(*m->out, cues); m->bytes_in_file += m->cluster->ElementSize(); if (g_kax_sh_cues) g_kax_sh_cues->IndexThis(*m->cluster, *g_kax_segment); m->previous_cluster_tc = m->cluster->GlobalTimecode(); cues_c::get().postprocess_cues(cues, *m->cluster); } else m->previous_cluster_tc = -1; } m->min_timecode_in_cluster = -1; m->max_timecode_in_cluster = -1; m->cluster->delete_non_blocks(); return 1; }
/*! The first file is a "binary" file with data scaling from 0x00 to 0xFF repeatedly The second file is a "text" file with data scaling from 'z' to 'a' */ int main(int argc, char **argv) { cout << "Creating \"muxed.mkv\"" << endl; try { // write the head of the file (with everything already configured) StdIOCallback out_file("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 0.5.0"; *((EbmlUnicodeString *)&GetChild<KaxWritingApp>(MyInfos)) = L"éàôï"; 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(); #if MATROSKA_VERSION >= 2 KaxAudioPosition & MyTrack1Pos = GetChild<KaxAudioPosition>(MyTrack1Audio); binary *_Pos = new binary[5]; _Pos[0] = '0'; _Pos[1] = '1'; _Pos[2] = '2'; _Pos[3] = '3'; _Pos[4] = '\0'; MyTrack1Pos.SetBuffer(_Pos, 5); #endif // MATROSKA_VERSION 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()); // KaxTracks * pMyTracks2 = new KaxTracks(MyTracks); 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 *data7 = new DataBuffer((binary *)"tototototo", countof("tototototo")); Clust1.AddFrame(MyTrack1, 250 * TIMECODE_SCALE, *data7, MyNewBlock, LACING_EBML); if (MyNewBlock != NULL) MyLastBlockTrk1 = MyNewBlock; DataBuffer *data0 = new DataBuffer((binary *)"TOTOTOTO", countof("TOTOTOTO")); Clust1.AddFrame(MyTrack1, 260 * TIMECODE_SCALE, *data0, MyNewBlock); // to test EBML lacing if (MyNewBlock != NULL) MyLastBlockTrk1 = MyNewBlock; DataBuffer *data6 = new DataBuffer((binary *)"tototototo", countof("tototototo")); Clust1.AddFrame(MyTrack1, 270 * TIMECODE_SCALE, *data6, MyNewBlock); // to test lacing if (MyNewBlock != NULL) { MyLastBlockTrk1 = MyNewBlock; } else { MyLastBlockTrk1->SetBlockDuration(50 * TIMECODE_SCALE); } DataBuffer *data5 = new DataBuffer((binary *)"tototototo", countof("tototototo")); Clust1.AddFrame(MyTrack2, 23 * TIMECODE_SCALE, *data5, MyNewBlock); // to test with another track // add the "real" block to the cue entries KaxBlockBlob *Blob1 = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE); Blob1->SetBlockGroup(*MyLastBlockTrk1); AllCues.AddBlockBlob(*Blob1); // frame for Track 2 DataBuffer *data8 = new DataBuffer((binary *)"tttyyy", countof("tttyyy")); Clust1.AddFrame(MyTrack2, 107 * TIMECODE_SCALE, *data8, MyNewBlock, *MyLastBlockTrk2); KaxBlockBlob *Blob2 = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE); Blob2->SetBlockGroup(*MyNewBlock); AllCues.AddBlockBlob(*Blob2); // frame with a past reference DataBuffer *data4 = new DataBuffer((binary *)"tttyyy", countof("tttyyy")); Clust1.AddFrame(MyTrack1, 300 * TIMECODE_SCALE, *data4, MyNewBlock, *MyLastBlockTrk1); // frame with a past & future reference if (MyNewBlock != NULL) { DataBuffer *data3 = new DataBuffer((binary *)"tttyyy", countof("tttyyy")); if (Clust1.AddFrame(MyTrack1, 280 * TIMECODE_SCALE, *data3, MyNewBlock2, *MyLastBlockTrk1, *MyNewBlock)) { MyNewBlock2->SetBlockDuration(20 * TIMECODE_SCALE); MyLastBlockTrk1 = MyNewBlock2; } else { printf("Error adding a frame !!!"); } } KaxBlockBlob *Blob3 = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE); Blob3->SetBlockGroup(*MyLastBlockTrk1); AllCues.AddBlockBlob(*Blob3); //AllCues.UpdateSize(); // 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 *data2 = new DataBuffer((binary *)"tttyyy", countof("tttyyy")); Clust2.AddFrame(MyTrack1, 350 * TIMECODE_SCALE, *data2, MyNewBlock, *MyLastBlockTrk1); KaxBlockBlob *Blob4 = new KaxBlockBlob(BLOCK_BLOB_NO_SIMPLE); Blob4->SetBlockGroup(*MyNewBlock); AllCues.AddBlockBlob(*Blob4); 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); // 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é123"; 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); #ifdef VOID_TEST MyInfos.VoidMe(out_file); #endif // VOID_TEST // 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); } #if 0 delete[] buf_bin; delete[] buf_txt; #endif // 0 #ifdef OLD MuxedFile.Close(1000); // 1000 ms #endif // OLD out_file.close(); delete Blob1; delete Blob2; delete Blob3; delete Blob4; } catch (exception & Ex) { cout << Ex.what() << endl; } return 0; }