int mk_writeSeek(mk_Writer *w, mk_Context *c, unsigned seek_id, uint64_t seek_pos) { mk_Context *s; if ((s = mk_createContext(w, c, MATROSKA_ID_SEEKENTRY)) == NULL) /* Seek */ return -1; CHECK(mk_writeUInt(s, MATROSKA_ID_SEEKID, seek_id)); /* SeekID */ CHECK(mk_writeUInt(s, MATROSKA_ID_SEEKPOSITION, seek_pos)); /* SeekPosition */ CHECK(mk_closeContext(s, 0)); return 0; }
int mk_flushFrame(mk_Writer *w) { int64_t delta, ref = 0; unsigned fsize, bgsize; unsigned char c_delta_flags[3]; if (!w->in_frame) return 0; delta = w->frame_tc/w->timescale - w->cluster_tc_scaled; //if (delta > 32767ll || delta < -32768ll) if (delta > 32767I64 || delta < -32768I64) //lsp051226 CHECK(mk_closeCluster(w)); if (w->cluster == NULL) { w->cluster_tc_scaled = w->frame_tc / w->timescale; w->cluster = mk_createContext(w, w->root, 0x1f43b675); // Cluster if (w->cluster == NULL) return -1; CHECK(mk_writeUInt(w->cluster, 0xe7, w->cluster_tc_scaled)); // Timecode delta = 0; } fsize = w->frame ? w->frame->d_cur : 0; bgsize = fsize + 4 + mk_ebmlSizeSize(fsize + 4) + 1; if (!w->keyframe) { ref = w->prev_frame_tc_scaled - w->cluster_tc_scaled - delta; bgsize += 1 + 1 + mk_ebmlSIntSize(ref); } CHECK(mk_writeID(w->cluster, 0xa0)); // BlockGroup CHECK(mk_writeSize(w->cluster, bgsize)); CHECK(mk_writeID(w->cluster, 0xa1)); // Block CHECK(mk_writeSize(w->cluster, fsize + 4)); CHECK(mk_writeSize(w->cluster, 1)); // track number c_delta_flags[0] = delta >> 8; c_delta_flags[1] = delta; c_delta_flags[2] = 0; CHECK(mk_appendContextData(w->cluster, c_delta_flags, 3)); if (w->frame) { CHECK(mk_appendContextData(w->cluster, w->frame->data, w->frame->d_cur)); w->frame->d_cur = 0; } if (!w->keyframe) CHECK(mk_writeSInt(w->cluster, 0xfb, ref)); // ReferenceBlock w->in_frame = 0; w->prev_frame_tc_scaled = w->cluster_tc_scaled + delta; if (w->cluster->d_cur > CLSIZE) CHECK(mk_closeCluster(w)); return 0; }
int mk_createChapterSimple(mk_Writer *w, uint64_t start, uint64_t end, char *name) { mk_Context *ca, *cd; unsigned long chapter_uid; /* * Generate a random UID for this Chapter. * NOTE: This probably should be a CRC32 of some unique chapter information. * In place of being completely random. */ chapter_uid = random(); if (w->chapters == NULL) { unsigned long edition_uid; edition_uid = random(); /* Chapters */ if ((w->chapters = mk_createContext(w, w->root, MATROSKA_ID_CHAPTERS)) == NULL) return -1; /* EditionEntry */ if ((w->edition_entry = mk_createContext(w, w->chapters, MATROSKA_ID_EDITIONENTRY)) == NULL) return -1; /* EditionUID - See note above about Chapter UID. */ CHECK(mk_writeUInt(w->edition_entry, MATROSKA_ID_EDITIONUID, edition_uid)); /* EditionFlagDefault - This is set to 1 to force this Edition to be the default one. */ CHECK(mk_writeUInt(w->edition_entry, MATROSKA_ID_EDITIONFLAGDEFAULT, 1)); /* EditionFlagOrdered - Force simple chapters. */ CHECK(mk_writeUInt(w->edition_entry, MATROSKA_ID_EDITIONFLAGORDERED, 0)); } /* ChapterAtom */ if ((ca = mk_createContext(w, w->edition_entry, MATROSKA_ID_CHAPTERATOM)) == NULL) return -1; CHECK(mk_writeUInt(ca, MATROSKA_ID_CHAPTERUID, chapter_uid)); /* ChapterUID */ CHECK(mk_writeUInt(ca, MATROSKA_ID_CHAPTERTIMESTART, start)); /* ChapterTimeStart */ if (end != start) /* Only create a StartTime if chapter length would be 0. */ CHECK(mk_writeUInt(ca, MATROSKA_ID_CHAPTERTIMEEND, end)); /* ChapterTimeEnd */ if (name != NULL) { /* ChapterDisplay */ if ((cd = mk_createContext(w, ca, MATROSKA_ID_CHAPTERDISPLAY)) == NULL) return -1; CHECK(mk_writeStr(cd, MATROSKA_ID_CHAPTERSTRING, name)); /* ChapterString */ CHECK(mk_closeContext(cd, 0)); } CHECK(mk_closeContext(ca, 0)); return 0; }
int mk_flushFrame(mk_Writer *w, mk_Track *track) { mk_Context *c, *tp; int64_t delta, ref = 0; unsigned fsize, bgsize; uint8_t flags, c_delta[2]; int i; char *laced = NULL; uint64_t length = 0; uint64_t block_duration = 0; if (!track->in_frame) return 0; delta = track->frame.timecode / w->timescale - w->cluster.tc_scaled; block_duration = track->frame.duration / w->timescale; /* NOTE: If we switch rapidly back-and-forth between tracks with * drastically different timecodes this causes a new cluster to * be written each time a switch is made. This causes unnecessary * overhead. The calling application is assumed to have interleaved * track samples based on timestamp. */ /* Soft limit: If the frame is a video keyframe and we are not closer than * 2 seconds to the last cluster, start a new cluster. */ if (track->track_type == MK_TRACK_VIDEO && track->frame.keyframe && delta > 2000ll) CHECK(mk_closeCluster(w)); /* Hard limit: if the current cluster is greater than 20 seconds, * start a new cluster */ if (delta > 20000ll || delta < -20000ll) CHECK(mk_closeCluster(w)); if (w->cluster.context == NULL) { w->cluster.tc_scaled = track->frame.timecode / w->timescale; /* Cluster */ w->cluster.context = mk_createContext(w, w->root, MATROSKA_ID_CLUSTER); if (w->cluster.context == NULL) return -1; w->cluster.pointer = w->f_pos - w->segment_ptr; /* Cluster SeekEntry */ CHECK(mk_writeSeek(w, w->cluster.seekhead, MATROSKA_ID_CLUSTER, w->cluster.pointer)); /* Cluster Timecode */ CHECK(mk_writeUInt(w->cluster.context, MATROSKA_ID_CLUSTERTIMECODE, w->cluster.tc_scaled)); delta = 0; w->cluster.block_count = 0; } /* Calculate the encoded lacing sizes. */ switch (track->frame.lacing) { case MK_LACING_XIPH: laced = mk_laceXiph(track->frame.lacing_sizes, track->frame.lacing_num_frames, &length); break; case MK_LACING_EBML: { uint64_t u_size = 0; /* Add one below for the frame count. */ length += mk_ebmlSizeSize(track->frame.lacing_sizes[0]) + 1; for (i = 1; i < track->frame.lacing_num_frames; i++) { u_size = llabs(track->frame.lacing_sizes[i] - track->frame.lacing_sizes[i - 1]); /* Shift by one so we get the right size for a signed number. */ length += mk_ebmlSizeSize((u_size) << 1); } break; } case MK_LACING_FIXED: { laced = calloc(1, sizeof(*laced)); laced[0] = track->frame.lacing_num_frames; ++length; break; } default: break; } fsize = track->frame.data ? track->frame.data->d_cur : 0; bgsize = fsize + 4 + mk_ebmlSizeSize(fsize + 4 + length) + 1 + length; if (!track->frame.keyframe) { ref = track->prev_frame_tc_scaled - w->cluster.tc_scaled - delta; bgsize += 1 + 1 + mk_ebmlSIntSize(ref); } if (block_duration > 0) /* BlockDuration */ { bgsize += 1 + 1 + mk_ebmlUIntSize(block_duration); } CHECK(mk_writeID(w->cluster.context, MATROSKA_ID_BLOCKGROUP)); /* BlockGroup */ CHECK(mk_writeSize(w->cluster.context, bgsize)); CHECK(mk_writeID(w->cluster.context, MATROSKA_ID_BLOCK)); /* Block */ CHECK(mk_writeSize(w->cluster.context, fsize + 4 + length)); /* BlockSize */ CHECK(mk_writeSize(w->cluster.context, track->track_id)); /* track number */ w->cluster.block_count++; c_delta[0] = delta >> 8; c_delta[1] = delta; /* Timecode relative to Cluster. */ CHECK(mk_appendContextData(w->cluster.context, c_delta, 2)); /* flags = ( track->frame.keyframe << 8 ) | track->frame.lacing; */ flags = track->frame.lacing << 1; /* Flags: Bit 5-6 describe what type of lacing to use. */ CHECK(mk_appendContextData(w->cluster.context, &flags, 1)); if (track->frame.lacing) { if (track->frame.lacing == MK_LACING_EBML) { /* Number of frames in lace - 1 */ CHECK(mk_appendContextData(w->cluster.context, &track->frame.lacing_num_frames, 1)); /* Size of 1st frame. */ CHECK(mk_writeSize(w->cluster.context, track->frame.lacing_sizes[0])); for (i = 1; i < track->frame.lacing_num_frames; i++) { /* Size difference between previous size and this size. */ CHECK(mk_writeSSize(w->cluster.context, track->frame.lacing_sizes[i] - track->frame.lacing_sizes[i - 1])); } } else if (length > 0 && laced != NULL) { CHECK(mk_appendContextData(w->cluster.context, laced, length)); free(laced); laced = NULL; } } if (track->frame.data) { CHECK(mk_appendContextData(w->cluster.context, track->frame.data->data, track->frame.data->d_cur)); track->frame.data->d_cur = 0; } if (!track->frame.keyframe) /* ReferenceBlock */ CHECK(mk_writeSInt(w->cluster.context, MATROSKA_ID_REFERENCEBLOCK, ref)); if (block_duration > 0) /* BlockDuration */ CHECK(mk_writeUInt(w->cluster.context, 0x9b, block_duration)); /* This may get a little out of hand, but it seems sane enough for now. */ if (track->frame.keyframe && (track->track_type == MK_TRACK_VIDEO)) { /* if (track->frame.keyframe && (track->track_type & MK_TRACK_VIDEO) && ((track->prev_cue_pos + 3*CLSIZE) <= w->f_pos || track->frame.timecode == 0)) { */ /* CuePoint */ if ((c = mk_createContext(w, w->cues, MATROSKA_ID_CUEPOINT)) == NULL) return -1; /* CueTime */ CHECK(mk_writeUInt(c, MATROSKA_ID_CUETIME, (track->frame.timecode / w->timescale))); /* CueTrackPositions */ if ((tp = mk_createContext(w, c, MATROSKA_ID_CUETRACKPOSITIONS)) == NULL) return -1; /* CueTrack */ CHECK(mk_writeUInt(tp, MATROSKA_ID_CUETRACK, track->track_id)); /* CueClusterPosition */ CHECK(mk_writeUInt(tp, MATROSKA_ID_CUECLUSTERPOSITION, w->cluster.pointer)); /* CueBlockNumber */ /* CHECK(mk_writeUInt(c, MATROSKA_ID_CUEBLOCKNUMBER, w->cluster.block_count)); */ CHECK(mk_closeContext(tp, 0)); CHECK(mk_closeContext(c, 0)); track->prev_cue_pos = w->f_pos; } track->in_frame = 0; track->prev_frame_tc_scaled = w->cluster.tc_scaled + delta; return 0; }
int mk_writeHeader(mk_Writer *w, const char *writingApp) { mk_Context *c; mk_Track *tk; int i; int64_t offset = 0; if (w->wrote_header) return -1; md5_starts(&w->segment_md5); /* Initalize MD5 */ CHECK(mk_writeEbmlHeader(w, "matroska", MATROSKA_VERSION, MATROSKA_VERSION)); /* Segment */ if ((c = mk_createContext(w, w->root, MATROSKA_ID_SEGMENT)) == NULL) return -1; CHECK(mk_flushContextID(c)); w->segment_ptr = c->d_cur; CHECK(mk_closeContext(c, &w->segment_ptr)); if (w->vlc_compat) { CHECK(mk_writeVoid(w->root, RESERVED_SEEKHEAD)); /* Reserved space for SeekHead */ CHECK(mk_writeVoid(w->root, RESERVED_CHAPTERS)); /* Reserved space for Chapters */ } else { w->seek_data.seekhead = 0x80000000; CHECK(mk_writeSeekHead(w, &w->seekhead_ptr)); w->seek_data.seekhead = 0; } if ((c = mk_createContext(w, w->root, MATROSKA_ID_INFO)) == NULL) /* SegmentInfo */ return -1; w->seek_data.segmentinfo = w->root->d_cur - w->segment_ptr; /* Reserve space for a SegmentUID (16 bytes + 1 byte longer EBML ID), to be written it later. */ CHECK(mk_writeVoid(c, 16 + 1)); CHECK(mk_writeStr(c, MATROSKA_ID_MUXINGAPP, PACKAGE_STRING)); /* MuxingApp */ CHECK(mk_writeStr(c, MATROSKA_ID_WRITINGAPP, writingApp)); /* WritingApp */ CHECK(mk_writeUInt(c, MATROSKA_ID_TIMECODESCALE, w->timescale)); /* TimecodeScale */ CHECK(mk_writeFloat(c, MATROSKA_ID_DURATION, 0)); /* Duration */ w->duration_ptr = c->d_cur - 4; CHECK(mk_closeContext(c, &offset)); w->duration_ptr += offset; w->segmentuid_ptr = offset; w->seek_data.tracks = w->root->d_cur - w->segment_ptr; if (w->tracks) { offset = 0; CHECK(mk_closeContext(w->tracks, &offset)); for (i = 0; i < w->num_tracks; i++) { tk = w->tracks_arr[i]; if (tk->private_data_size) tk->private_data_ptr += offset; } } CHECK(mk_flushContextData(w->root)); w->wrote_header = 1; w->def_duration = w->tracks_arr[0]->default_duration; return 0; }
int mk_writeHeader(mk_Writer *w, const char *writingApp, const char *codecID, const void *codecPrivate, unsigned codecPrivateSize, int64_t default_frame_duration, int64_t timescale, unsigned width, unsigned height, unsigned d_width, unsigned d_height) { mk_Context *c, *ti, *v; if (w->wrote_header) return -1; w->timescale = timescale; w->def_duration = default_frame_duration; if ((c = mk_createContext(w, w->root, 0x1a45dfa3)) == NULL) // EBML return -1; CHECK(mk_writeUInt(c, 0x4286, 1)); // EBMLVersion CHECK(mk_writeUInt(c, 0x42f7, 1)); // EBMLReadVersion CHECK(mk_writeUInt(c, 0x42f2, 4)); // EBMLMaxIDLength CHECK(mk_writeUInt(c, 0x42f3, 8)); // EBMLMaxSizeLength CHECK(mk_writeStr(c, 0x4282, "matroska")); // DocType CHECK(mk_writeUInt(c, 0x4287, 1)); // DocTypeVersion CHECK(mk_writeUInt(c, 0x4285, 1)); // DocTypeReadversion CHECK(mk_closeContext(c, 0)); if ((c = mk_createContext(w, w->root, 0x18538067)) == NULL) // Segment return -1; CHECK(mk_flushContextID(c)); CHECK(mk_closeContext(c, 0)); if ((c = mk_createContext(w, w->root, 0x1549a966)) == NULL) // SegmentInfo return -1; CHECK(mk_writeStr(c, 0x4d80, "Haali Matroska Writer b0")); CHECK(mk_writeStr(c, 0x5741, writingApp)); CHECK(mk_writeUInt(c, 0x2ad7b1, w->timescale)); CHECK(mk_writeFloat(c, 0x4489, 0)); w->duration_ptr = c->d_cur - 4; CHECK(mk_closeContext(c, &w->duration_ptr)); if ((c = mk_createContext(w, w->root, 0x1654ae6b)) == NULL) // tracks return -1; if ((ti = mk_createContext(w, c, 0xae)) == NULL) // TrackEntry return -1; CHECK(mk_writeUInt(ti, 0xd7, 1)); // TrackNumber CHECK(mk_writeUInt(ti, 0x73c5, 1)); // TrackUID CHECK(mk_writeUInt(ti, 0x83, 1)); // TrackType CHECK(mk_writeUInt(ti, 0x9c, 0)); // FlagLacing CHECK(mk_writeStr(ti, 0x86, codecID)); // CodecID if (codecPrivateSize) CHECK(mk_writeBin(ti, 0x63a2, codecPrivate, codecPrivateSize)); // CodecPrivate if (default_frame_duration) CHECK(mk_writeUInt(ti, 0x23e383, default_frame_duration)); // DefaultDuration if ((v = mk_createContext(w, ti, 0xe0)) == NULL) // Video return -1; CHECK(mk_writeUInt(v, 0xb0, width)); CHECK(mk_writeUInt(v, 0xba, height)); CHECK(mk_writeUInt(v, 0x54b0, d_width)); CHECK(mk_writeUInt(v, 0x54ba, d_height)); CHECK(mk_closeContext(v, 0)); CHECK(mk_closeContext(ti, 0)); CHECK(mk_closeContext(c, 0)); CHECK(mk_flushContextData(w->root)); w->wrote_header = 1; return 0; }