| /***************************************************************************** |
| * matroska.c: |
| ***************************************************************************** |
| * Copyright (C) 2005 Mike Matsnev |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. |
| *****************************************************************************/ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include "common/osdep.h" |
| #include "matroska.h" |
| |
| #define CLSIZE 1048576 |
| #define CHECK(x) do { if ((x) < 0) return -1; } while (0) |
| |
| struct mk_Context { |
| struct mk_Context *next, **prev, *parent; |
| struct mk_Writer *owner; |
| unsigned id; |
| |
| void *data; |
| unsigned d_cur, d_max; |
| }; |
| |
| typedef struct mk_Context mk_Context; |
| |
| struct mk_Writer { |
| FILE *fp; |
| |
| unsigned duration_ptr; |
| |
| mk_Context *root, *cluster, *frame; |
| mk_Context *freelist; |
| mk_Context *actlist; |
| |
| int64_t def_duration; |
| int64_t timescale; |
| int64_t cluster_tc_scaled; |
| int64_t frame_tc, prev_frame_tc_scaled, max_frame_tc; |
| |
| char wrote_header, in_frame, keyframe; |
| }; |
| |
| static mk_Context *mk_createContext(mk_Writer *w, mk_Context *parent, unsigned id) { |
| mk_Context *c; |
| |
| if (w->freelist) { |
| c = w->freelist; |
| w->freelist = w->freelist->next; |
| } else { |
| c = malloc(sizeof(*c)); |
| memset(c, 0, sizeof(*c)); |
| } |
| |
| if (c == NULL) |
| return NULL; |
| |
| c->parent = parent; |
| c->owner = w; |
| c->id = id; |
| |
| if (c->owner->actlist) |
| c->owner->actlist->prev = &c->next; |
| c->next = c->owner->actlist; |
| c->prev = &c->owner->actlist; |
| c->owner->actlist = c; |
| |
| return c; |
| } |
| |
| static int mk_appendContextData(mk_Context *c, const void *data, unsigned size) { |
| unsigned ns = c->d_cur + size; |
| |
| if (ns > c->d_max) { |
| void *dp; |
| unsigned dn = c->d_max ? c->d_max << 1 : 16; |
| while (ns > dn) |
| dn <<= 1; |
| |
| dp = realloc(c->data, dn); |
| if (dp == NULL) |
| return -1; |
| |
| c->data = dp; |
| c->d_max = dn; |
| } |
| |
| memcpy((char*)c->data + c->d_cur, data, size); |
| |
| c->d_cur = ns; |
| |
| 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_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; |
| } |
| |
| static int mk_closeContext(mk_Context *c, unsigned *off) { |
| if (c->id) { |
| CHECK(mk_writeID(c->parent, c->id)); |
| CHECK(mk_writeSize(c->parent, c->d_cur)); |
| } |
| |
| if (c->parent && off != NULL) |
| *off += c->parent->d_cur; |
| |
| CHECK(mk_flushContextData(c)); |
| |
| if (c->next) |
| c->next->prev = c->prev; |
| *(c->prev) = c->next; |
| c->next = c->owner->freelist; |
| c->owner->freelist = c; |
| |
| return 0; |
| } |
| |
| static void mk_destroyContexts(mk_Writer *w) { |
| mk_Context *cur, *next; |
| |
| for (cur = w->freelist; cur; cur = next) { |
| next = cur->next; |
| free(cur->data); |
| free(cur); |
| } |
| |
| for (cur = w->actlist; cur; cur = next) { |
| next = cur->next; |
| free(cur->data); |
| free(cur); |
| } |
| |
| w->freelist = w->actlist = w->root = NULL; |
| } |
| |
| 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_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; |
| } |
| |
| 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_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; |
| } |
| |
| 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_writeFloat(mk_Context *c, unsigned id, float f) { |
| CHECK(mk_writeID(c, id)); |
| CHECK(mk_writeSize(c, 4)); |
| CHECK(mk_writeFloatRaw(c, f)); |
| return 0; |
| } |
| |
| static unsigned mk_ebmlSizeSize(unsigned s) { |
| if (s < 0x7f) |
| return 1; |
| if (s < 0x3fff) |
| return 2; |
| if (s < 0x1fffff) |
| return 3; |
| if (s < 0x0fffffff) |
| return 4; |
| return 5; |
| } |
| |
| static unsigned mk_ebmlSIntSize(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; |
| |
| 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; |
| |
| return 8 - i; |
| } |
| |
| mk_Writer *mk_createWriter(const char *filename) { |
| mk_Writer *w = malloc(sizeof(*w)); |
| if (w == NULL) |
| return NULL; |
| |
| memset(w, 0, sizeof(*w)); |
| |
| w->root = mk_createContext(w, NULL, 0); |
| if (w->root == NULL) { |
| free(w); |
| return NULL; |
| } |
| |
| w->fp = fopen(filename, "wb"); |
| if (w->fp == NULL) { |
| mk_destroyContexts(w); |
| free(w); |
| return NULL; |
| } |
| |
| w->timescale = 1000000; |
| |
| return w; |
| } |
| |
| 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; |
| } |
| |
| static int mk_closeCluster(mk_Writer *w) { |
| if (w->cluster == NULL) |
| return 0; |
| CHECK(mk_closeContext(w->cluster, 0)); |
| w->cluster = NULL; |
| CHECK(mk_flushContextData(w->root)); |
| return 0; |
| } |
| |
| static 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) |
| 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_startFrame(mk_Writer *w) { |
| if (mk_flushFrame(w) < 0) |
| return -1; |
| |
| w->in_frame = 1; |
| w->keyframe = 0; |
| |
| return 0; |
| } |
| |
| int mk_setFrameFlags(mk_Writer *w,int64_t timestamp, int keyframe) { |
| if (!w->in_frame) |
| return -1; |
| |
| w->frame_tc = timestamp; |
| w->keyframe = keyframe != 0; |
| |
| if (w->max_frame_tc < timestamp) |
| w->max_frame_tc = timestamp; |
| |
| return 0; |
| } |
| |
| 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); |
| } |
| |
| int mk_close(mk_Writer *w) { |
| int ret = 0; |
| if (mk_flushFrame(w) < 0 || mk_closeCluster(w) < 0) |
| ret = -1; |
| if (w->wrote_header) { |
| fseek(w->fp, w->duration_ptr, SEEK_SET); |
| if (mk_writeFloatRaw(w->root, (float)((double)(w->max_frame_tc+w->def_duration) / w->timescale)) < 0 || |
| mk_flushContextData(w->root) < 0) |
| ret = -1; |
| } |
| mk_destroyContexts(w); |
| fclose(w->fp); |
| free(w); |
| return ret; |
| } |
| |