static int mk_writeSize(mk_Context *c, unsigned size) { #ifndef __CW32__ unsigned char c_size[5] = { 0x08, size >> 24, size >> 16, size >> 8, size }; #else unsigned char c_size[5]; c_size[0] = 0x08; c_size[1] = size >> 24; c_size[2] = size >> 16; c_size[3] = size >> 8; c_size[4] = size; #endif if (size < 0x7f) { c_size[4] |= 0x80; return mk_appendContextData(c, c_size+4, 1); } if (size < 0x3fff) { c_size[3] |= 0x40; return mk_appendContextData(c, c_size+3, 2); } if (size < 0x1fffff) { c_size[2] |= 0x20; return mk_appendContextData(c, c_size+2, 3); } if (size < 0x0fffffff) { c_size[1] |= 0x10; return mk_appendContextData(c, c_size+1, 4); } return mk_appendContextData(c, c_size, 5); }
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; }
static int mk_writeID(mk_Context *c, unsigned id) { unsigned char c_id[4] = { id >> 24, id >> 16, id >> 8, id }; if (c_id[0]) return mk_appendContextData(c, c_id, 4); if (c_id[1]) return mk_appendContextData(c, c_id+1, 3); if (c_id[2]) return mk_appendContextData(c, c_id+2, 2); return mk_appendContextData(c, c_id+3, 1); }
static int mk_writeSInt(mk_Context *c, unsigned id, int64_t si) { #ifdef __CW32__ unsigned char c_si[8]; #else unsigned char c_si[8] = { si >> 56, si >> 48, si >> 40, si >> 32, si >> 24, si >> 16, si >> 8, si }; #endif unsigned i = 0; #ifdef __CW32__ c_si[0] = si >> 56; c_si[1] = si >> 48; c_si[2] = si >> 40; c_si[3] = si >> 32; c_si[4] = si >> 24; c_si[5] = si >> 16; c_si[6] = si >> 8; c_si[7] = si; #endif CHECK(mk_writeID(c, id)); if (si < 0) while (i < 7 && c_si[i] == 0xff && c_si[i+1] & 0x80) ++i; else while (i < 7 && c_si[i] == 0 && !(c_si[i+1] & 0x80)) ++i; CHECK(mk_writeSize(c, 8 - i)); CHECK(mk_appendContextData(c, c_si+i, 8 - i)); return 0; }
static int mk_writeUInt(mk_Context *c, unsigned id, int64_t ui) { #ifdef __CW32__ unsigned char c_ui[8]; #else unsigned char c_ui[8] = { ui >> 56, ui >> 48, ui >> 40, ui >> 32, ui >> 24, ui >> 16, ui >> 8, ui }; #endif unsigned i = 0; #ifdef __CW32__ c_ui[0] = ui >> 56; c_ui[1] = ui >> 48; c_ui[2] = ui >> 40; c_ui[3] = ui >> 32; c_ui[4] = ui >> 24; c_ui[5] = ui >> 16; c_ui[6] = ui >> 8; c_ui[7] = ui; #endif CHECK(mk_writeID(c, id)); while (i < 7 && c_ui[i] == 0) ++i; CHECK(mk_writeSize(c, 8 - i)); CHECK(mk_appendContextData(c, c_ui+i, 8 - i)); return 0; }
static int mk_writeStr(mk_Context *c, unsigned id, const char *str) { size_t len = strlen(str); CHECK(mk_writeID(c, id)); CHECK(mk_writeSize(c, len)); CHECK(mk_appendContextData(c, str, len)); return 0; }
static int mk_writeID(mk_Context *c, unsigned id) { #ifndef __CW32__ unsigned char c_id[4] = { id >> 24, id >> 16, id >> 8, id }; #else unsigned char c_id[4]; c_id[0] = id >> 24; c_id[1] = id >> 16; c_id[2] = id >> 8; c_id[3] = id; #endif if (c_id[0]) return mk_appendContextData(c, c_id, 4); if (c_id[1]) return mk_appendContextData(c, c_id+1, 3); if (c_id[2]) return mk_appendContextData(c, c_id+2, 2); return mk_appendContextData(c, c_id+3, 1); }
int mk_addFrameData(mk_Writer *w, const void *data, unsigned size) { if (!w->in_frame) return -1; if (w->frame == NULL) if ((w->frame = mk_createContext(w, NULL, 0)) == NULL) return -1; return mk_appendContextData(w->frame, data, size); }
static int mk_writeUInt(mk_Context *c, unsigned id, int64_t ui) { unsigned char c_ui[8] = { ui >> 56, ui >> 48, ui >> 40, ui >> 32, ui >> 24, ui >> 16, ui >> 8, ui }; unsigned i = 0; CHECK(mk_writeID(c, id)); while (i < 7 && c_ui[i] == 0) ++i; CHECK(mk_writeSize(c, 8 - i)); CHECK(mk_appendContextData(c, c_ui+i, 8 - i)); return 0; }
static int mk_writeSize(mk_Context *c, unsigned size) { unsigned char c_size[5] = { 0x08, size >> 24, size >> 16, size >> 8, size }; if (size < 0x7f) { c_size[4] |= 0x80; return mk_appendContextData(c, c_size+4, 1); } if (size < 0x3fff) { c_size[3] |= 0x40; return mk_appendContextData(c, c_size+3, 2); } if (size < 0x1fffff) { c_size[2] |= 0x20; return mk_appendContextData(c, c_size+2, 3); } if (size < 0x0fffffff) { c_size[1] |= 0x10; return mk_appendContextData(c, c_size+1, 4); } return mk_appendContextData(c, c_size, 5); }
static int mk_flushContextID(mk_Context *c) { unsigned char ff = 0xff; if (c->id == 0) return 0; CHECK(mk_writeID(c->parent, c->id)); CHECK(mk_appendContextData(c->parent, &ff, 1)); c->id = 0; return 0; }
static int mk_flushContextData(mk_Context *c) { if (c->d_cur == 0) return 0; if (c->parent) CHECK(mk_appendContextData(c->parent, c->data, c->d_cur)); else if (fwrite(c->data, c->d_cur, 1, c->owner->fp) != 1) return -1; c->d_cur = 0; return 0; }
int mk_addFrameData(mk_Writer *w, mk_Track *track, const void *data, unsigned size) { if (!track->in_frame) return -1; if (track->frame.data == NULL) { if ((track->frame.data = mk_createContext(w, NULL, 0)) == NULL) return -1; } md5_update(&w->segment_md5, (unsigned char *) data, size); return mk_appendContextData(track->frame.data, data, size); }
static int mk_writeFloatRaw(mk_Context *c, float f) { union { float f; unsigned u; } u; unsigned char c_f[4]; u.f = f; c_f[0] = u.u >> 24; c_f[1] = u.u >> 16; c_f[2] = u.u >> 8; c_f[3] = u.u; return mk_appendContextData(c, c_f, 4); }
static int mk_writeSInt(mk_Context *c, unsigned id, int64_t si) { unsigned char c_si[8] = { si >> 56, si >> 48, si >> 40, si >> 32, si >> 24, si >> 16, si >> 8, si }; unsigned i = 0; CHECK(mk_writeID(c, id)); if (si < 0) while (i < 7 && c_si[i] == 0xff && c_si[i+1] & 0x80) ++i; else while (i < 7 && c_si[i] == 0 && !(c_si[i+1] & 0x80)) ++i; CHECK(mk_writeSize(c, 8 - i)); CHECK(mk_appendContextData(c, c_si+i, 8 - i)); return 0; }
int mk_close(mk_Writer *w) { int i, ret = 0; mk_Track *tk; int64_t max_frame_tc = w->tracks_arr[0]->max_frame_tc; uint64_t segment_size = 0; unsigned char c_size[8]; unsigned char segment_uid[16]; md5_finish(&w->segment_md5, segment_uid); for (i = w->num_tracks - 1; i >= 0; i--) { tk = w->tracks_arr[i]; if (mk_flushFrame(w, tk) < 0) ret = -1; } if (mk_closeCluster(w) < 0) ret = -1; w->seek_data.cues = w->f_pos - w->segment_ptr; if (mk_closeContext(w->cues, 0) < 0) ret = -1; if (mk_flushContextData(w->root) < 0) ret = -1; if (w->cluster.seekhead) { w->seek_data.seekhead = w->f_pos - w->segment_ptr; if (mk_closeContext(w->cluster.seekhead, 0) < 0) ret = -1; if (mk_flushContextData(w->root) < 0) ret = -1; } if (w->attachments != NULL) { w->seek_data.attachments = w->f_pos - w->segment_ptr; mk_writeAttachments(w); if (mk_flushContextData(w->root) < 0) ret = -1; } if (w->tags != NULL) { w->seek_data.tags = w->f_pos - w->segment_ptr; mk_writeTags(w); if (mk_flushContextData(w->root) < 0) ret = -1; } if (w->chapters != NULL) { if (w->vlc_compat) { if (mk_flushContextData(w->root) < 0) ret = -1; if (mk_seekFile(w, w->segment_ptr + RESERVED_SEEKHEAD + 3) < 0) ret = -1; } w->seek_data.chapters = w->f_pos - w->segment_ptr; mk_writeChapters(w); if (mk_flushContextData(w->root) < 0) ret = -1; if (w->vlc_compat) { if (mk_writeVoid(w->root, (RESERVED_CHAPTERS - (w->f_pos - w->segment_ptr - RESERVED_SEEKHEAD - 3))) < 0) ret = -1; if (mk_flushContextData(w->root) < 0) ret = -1; } } if (w->wrote_header) { if (w->vlc_compat) { if (mk_seekFile(w, w->segment_ptr) < 0) ret = -1; } if (mk_writeSeekHead(w, &w->seek_data.seekhead) < 0) ret = -1; w->seek_data.seekhead -= w->segment_ptr; if (w->vlc_compat) { if (mk_flushContextData(w->root) < 0) ret = -1; if (mk_writeVoid(w->root, (RESERVED_SEEKHEAD - (w->f_pos - w->segment_ptr))) < 0) ret = -1; } if (mk_flushContextData(w->root) < 0) ret = -1; if (!w->vlc_compat) { int i = w->seek_data.segmentinfo; w->seek_data.segmentinfo = 0; w->seek_data.tracks = 0; w->seek_data.cues = 0; w->seek_data.chapters = 0; w->seek_data.attachments = 0; w->seek_data.tags = 0; if (mk_seekFile(w, w->segment_ptr) < 0) ret = -1; if (mk_writeSeekHead(w, NULL) < 0 || mk_flushContextData(w->root) < 0) ret = -1; // The conditional below is easier to understand, but incorrect // because f_pos is unsigned and causes the lhs to be evaluated // as an unsigned quantity. // if (((i + w->segment_ptr) - w->f_pos - 2) > 1) if ((i + w->segment_ptr) > (w->f_pos + 3)) if (mk_writeVoid(w->root, (i + w->segment_ptr) - w->f_pos - 2) < 0 || mk_flushContextData(w->root) < 0) ret = -1; } if (mk_seekFile(w, w->duration_ptr) < 0) ret = -1; if (mk_writeFloatRaw(w->root, (float) ((double) (max_frame_tc + w->def_duration) / w->timescale)) < 0 || mk_flushContextData(w->root) < 0) ret = -1; if (mk_seekFile(w, w->segment_ptr - 8) < 0) ret = -1; segment_size = w->f_eof - w->segment_ptr; for (i = 7; i > 0; --i) c_size[i] = segment_size >> (8 * (7 - i)); c_size[i] = 0x01; if (mk_appendContextData(w->root, &c_size, 8) < 0 || mk_flushContextData(w->root) < 0) ret = -1; if (mk_seekFile(w, w->segmentuid_ptr) < 0) ret = -1; if (mk_writeBin(w->root, MATROSKA_ID_SEGMENTUID, segment_uid, sizeof(segment_uid)) < 0 || mk_flushContextData(w->root) < 0) ret = -1; } /* update any track private data that may have changed */ for (i = w->num_tracks - 1; i >= 0; i--) { tk = w->tracks_arr[i]; if (tk->private_data_size && tk->private_data) { if (mk_seekFile(w, tk->private_data_ptr) < 0) ret = -1; if (mk_writeBin(w->root, MATROSKA_ID_CODECPRIVATE, tk->private_data, tk->private_data_size) < 0 || mk_flushContextData(w->root) < 0) ret = -1; free(tk->private_data); } w->tracks_arr[i] = NULL; --w->num_tracks; free(tk); } if (mk_closeContext(w->root, 0) < 0) ret = -1; mk_destroyContexts(w); fclose(w->fp); free(w->tracks_arr); free(w); return ret; }
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; }
static int mk_writeBin(mk_Context *c, unsigned id, const void *data, unsigned size) { CHECK(mk_writeID(c, id)); CHECK(mk_writeSize(c, size)); CHECK(mk_appendContextData(c, data, size)); return 0; }