From 0c7bdbcf54610eedac42106605f16ba7795ae9c6 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Sun, 23 May 2010 00:13:57 +0200 Subject: [PATCH 1/2] frame extraction from webm files --- Makefile | 13 +- oxframe.c => src/oxframe.c | 326 ++++++++++++++++++++++++++++++++----- 2 files changed, 294 insertions(+), 45 deletions(-) rename oxframe.c => src/oxframe.c (54%) diff --git a/Makefile b/Makefile index a47c1be..8a7edbd 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PROG = oxframe -SRC = oxframe.c +SRC = src/oxframe.c +NETEGGS = src/nestegg.o src/halloc/src/halloc.o PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin @@ -8,6 +9,7 @@ MAN1DIR ?= ${PREFIX}/man/man1 CC ?= gcc CFLAGS ?= -D_FILE_OFFSET_BITS=64 CFLAGS += -Wall -ffast-math -fsigned-char +CFLAGS += -I. -Isrc -Isrc/halloc INSTALL = install @@ -17,19 +19,22 @@ LINKFLAGS ?= -L${PREFIX}/lib LINKFLAGS += `imlib2-config --libs` LINKFLAGS += -L/usr/local/lib /usr/local/lib/liboggplay.a LINKFLAGS += -loggz -lfishsound -ltheora -lvorbisenc -lvorbis -lm -logg -lkate -lpthread +LINKFLAGS += -lvpx all: ${PROG} +src/nestegg.o: src/nestegg.h src/nestegg.c src/halloc/halloc.h +src/halloc/src/halloc.o: src/halloc/src/halloc.c src/halloc/halloc.h src/halloc/src/align.h src/halloc/src/hlist.h src/halloc/src/macros.h -${PROG}: ${SRC} - ${CC} -Wall -Wno-parentheses -O3 -fforce-addr -fomit-frame-pointer -finline-functions -funroll-loops ${CFLAGS} ${INCLUDEFLAGS} -o ${PROG} $< ${LINKFLAGS} +${PROG}: ${SRC} ${NETEGGS} + ${CC} -Wall -Wno-parentheses -O3 -fforce-addr -fomit-frame-pointer -finline-functions -funroll-loops ${CFLAGS} ${INCLUDEFLAGS} -o ${PROG} $< ${NETEGGS} ${LINKFLAGS} install: ${PROG} ${INSTALL} -c -m 555 -o root -g bin ${PROG} ${BINDIR} ${INSTALL} -c -m 555 -o root -g bin oxposterframe ${BINDIR} clean: - -@rm -f ${PROG} *~ core *.core + -@rm -f ${PROG} *~ core *.core src/*.o src/halloc/src/*.o diff --git a/oxframe.c b/src/oxframe.c similarity index 54% rename from oxframe.c rename to src/oxframe.c index d2f7852..c975cbc 100644 --- a/oxframe.c +++ b/src/oxframe.c @@ -17,10 +17,19 @@ * along with This program. If not, see . */ -#include +#include +#include #include #include #include +#include +#include "nestegg.h" + +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "vpx/vpx_decoder.h" +#include "vpx/vp8dx.h" + +#define interface (&vpx_codec_vp8_dx_algo) #include @@ -185,16 +194,282 @@ void init_state(oxstate *state) { state->format = oxImageNotSet; } -int main (int argc, char * argv[]) { - int c,long_option_index; - OggPlay * player; - OggPlayReader * reader = NULL; +//WebM +static int +stdio_read(void * p, size_t length, void * fp) +{ + size_t r; + + r = fread(p, length, 1, fp); + if (r == 0 && feof(fp)) { + return 0; + } + return r == 0 ? -1 : 1; +} + +static int +stdio_seek(int64_t offset, int whence, void * fp) +{ + return fseek(fp, offset, whence); +} + +static int64_t +stdio_tell(void * fp) +{ + return ftell(fp); +} + +static void +log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ...) +{ + va_list ap; + char const * sev = NULL; + +#ifndef DEBUG + if (severity < NESTEGG_LOG_WARNING) + return; +#endif + + switch (severity) { + case NESTEGG_LOG_DEBUG: + sev = "debug: "; + break; + case NESTEGG_LOG_WARNING: + sev = "warning: "; + break; + case NESTEGG_LOG_CRITICAL: + sev = "critical:"; + break; + default: + sev = "unknown: "; + } + + fprintf(stderr, "%p %s ", (void *) ctx, sev); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); +} +static void die_codec(vpx_codec_ctx_t *ctx, const char *s) { + const char *detail = vpx_codec_error_detail(ctx); + + printf("%s: %s\n", s, vpx_codec_error(ctx)); + if(detail) + printf(" %s\n",detail); + exit(EXIT_FAILURE); +} + +int extract_frame_ogv(oxstate *state) { int i; int fps_num = 25; int fps_denom = 1; int granuleshift = 6; long max_num, offset; + + OggPlay * player; + OggPlayReader * reader = NULL; + + reader = oggplay_file_reader_new(state->input); + player = oggplay_open_with_reader(reader); + + if (player == NULL) { + fprintf (stderr, "could not initialise oggplay with this file\n"); + exit (1); + } + + for (i = 0; i < oggplay_get_num_tracks (player); i++) { + if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_THEORA) { + oggplay_set_callback_num_frames (player, i, 1); + oggplay_get_video_fps(player, i, &fps_denom, &fps_num); + } + oggplay_set_track_active(player, i); + } + oggplay_set_data_callback(player, dump_frame_callback, state); + + max_num = 1 << granuleshift; + offset = (1000 * max_num * fps_denom) / fps_num; + + state->duration = oggplay_get_duration(player); + + /* + if (frame_pos > state->duration) { + fprintf (stderr, "can not seek to frame later than duration\n"); + exit (1); + } + */ + + if(state->frame_pos - offset > 0) { + if (oggplay_seek(player, state->frame_pos - offset) == E_OGGPLAY_CANT_SEEK) { + fprintf (stderr, "failed to seeek to %ld\n", state->frame_pos); + exit (1); + } + } + oggplay_start_decoding(player); + + oggplay_close (player); + return 0; +} + +int extract_frame_webm(oxstate *state) { + FILE * fp; + int r, type, codec_id; + nestegg * ctx; + nestegg_packet * pkt; + nestegg_video_params vparams; + uint64_t duration, pkt_tstamp; + unsigned int i, tracks; + int flags = 0; + int done = 0; + vpx_codec_ctx_t codec; + vpx_image_t *img; + + //in nanoseconds + uint64_t seek_tstamp = (uint64_t)state->frame_pos*1000000; + + nestegg_io io = { + stdio_read, + stdio_seek, + stdio_tell, + NULL + }; + + fp = fopen(state->input, "rb"); + if (!fp) { + fprintf (stderr, "could not open input file\n"); + exit (1); + } + io.userdata = fp; + ctx = NULL; + r = nestegg_init(&ctx, io, log_callback); + if (r != 0) + return EXIT_FAILURE; + + nestegg_track_count(ctx, &tracks); + nestegg_duration(ctx, &duration); + + if (seek_tstamp > duration) { + fprintf (stderr, "can not seek to frame later than duration\n"); + exit (1); + } + + /* Initialize codec */ + if(vpx_codec_dec_init(&codec, interface, NULL, flags)) + die_codec(&codec, "Failed to initialize decoder"); + + for (i = 0; i < tracks; ++i) { + type = nestegg_track_type(ctx, i); + codec_id = nestegg_track_codec_id(ctx, i); + + if (type == NESTEGG_TRACK_VIDEO && codec_id == NESTEGG_CODEC_VP8) { + nestegg_track_video_params(ctx, i, &vparams); + + if(!nestegg_track_seek(ctx, i, seek_tstamp)) { + + while (!done && (r = nestegg_read_packet(ctx, &pkt)) > 0) { + unsigned int track; + + nestegg_packet_track(pkt, &track); + if(nestegg_packet_tstamp(pkt, &pkt_tstamp) < 0) { + fprintf (stderr, "faild to get timestamp\n"); + } + + // only look for video track + if (track == i) { + unsigned int chunk, chunks; + + nestegg_packet_count(pkt, &chunks); + + // Decode each chunk of data. + for (chunk = 0; chunk < chunks; ++chunk) { + vpx_codec_iter_t iter = NULL; + unsigned char * data; + size_t data_size; + + nestegg_packet_data(pkt, chunk, &data, &data_size); + + /* Decode the frame */ + if(vpx_codec_decode(&codec, data, data_size, NULL, 0)) + die_codec(&codec, "Failed to decode frame"); + + while((img = vpx_codec_get_frame(&codec, &iter))) { + //we got a frame... + if(seek_tstamp-pkt_tstamp<=0) { + unsigned int y; + unsigned char *q, *p, *q2, *p2; + OggPlayYUVChannels from; + OggPlayRGBChannels to; + + done = 1; + + from.y_width = img->d_w; + from.y_height = img->d_h; + from.uv_width = (1 + img->d_w) / 2; + from.uv_height = (1 + img->d_h) / 2; + + from.ptry = malloc(from.y_width * from.y_height); + from.ptru = malloc(from.uv_width * from.uv_height); + from.ptrv = malloc(from.uv_width * from.uv_height); + + q =img->planes[PLANE_Y]; + p = from.ptry; + for(y=0; yd_h; y++) { + memcpy(p, q, img->d_w); + p += img->d_w; + q += img->stride[PLANE_Y]; + } + q =img->planes[PLANE_U]; + p = from.ptru; + q2 =img->planes[PLANE_V]; + p2 = from.ptrv; + for(y=0; y<(1 + img->d_h) / 2; y++) { + memcpy(p, q, (1 + img->d_w) / 2); + memcpy(p2, q2, (1 + img->d_w) / 2); + p += (1 + img->d_w) / 2; + q += img->stride[PLANE_U]; + p2 += (1 + img->d_w) / 2; + q2 += img->stride[PLANE_V]; + } + + to.ptro = malloc(from.y_width * from.y_height * 4); + to.rgb_width = from.y_width; + to.rgb_height = from.y_height; + + oggplay_yuv2bgra (&from, &to); + + write_image_file(&to, state); + free(from.ptry); + free(from.ptru); + free(from.ptrv); + free(to.ptro); + } + } + + } + } + nestegg_free_packet(pkt); + } + + if(vpx_codec_destroy(&codec)) + die_codec(&codec, "Failed to destroy codec"); + + nestegg_destroy(ctx); + + } else { + fprintf (stderr, "failed to seek\n"); + exit (1); + } + + } + } + return 0; +} + +int main (int argc, char * argv[]) { + + int c,long_option_index; oxstate state; const char *optstring = "h:x:y:p:i:o:f:"; @@ -232,7 +507,6 @@ int main (int argc, char * argv[]) { break; case 'i': state.input = optarg; - reader = oggplay_file_reader_new(state.input); break; case 'o': state.output = optarg; @@ -256,47 +530,17 @@ int main (int argc, char * argv[]) { state.format = oxJPG; } - player = oggplay_open_with_reader(reader); - if (state.input == NULL) { fprintf (stderr, "please provide input file\n"); exit (1); } - if (player == NULL) { - fprintf (stderr, "could not initialise oggplay with this file\n"); - exit (1); + + if (strstr(&(state.input[strlen(state.input)-5]), ".webm") == NULL) { //ogv + extract_frame_ogv(&state); } - - for (i = 0; i < oggplay_get_num_tracks (player); i++) { - if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_THEORA) { - oggplay_set_callback_num_frames (player, i, 1); - oggplay_get_video_fps(player, i, &fps_denom, &fps_num); - } - oggplay_set_track_active(player, i); + else { // .webm using nestegg + libvpx + extract_frame_webm(&state); } - oggplay_set_data_callback(player, dump_frame_callback, &state); - - max_num = 1 << granuleshift; - offset = (1000 * max_num * fps_denom) / fps_num; - - state.duration = oggplay_get_duration(player); - - /* - if (frame_pos > duration) { - fprintf (stderr, "can not seek to frame later than duration\n"); - exit (1); - } - */ - - if(state.frame_pos - offset > 0) { - if (oggplay_seek(player, state.frame_pos - offset) == E_OGGPLAY_CANT_SEEK) { - fprintf (stderr, "failed to seeek to %ld\n", state.frame_pos); - exit (1); - } - } - oggplay_start_decoding(player); - - oggplay_close (player); return 0; } From 6b297cd03761b96c5067834b710d4c37db767954 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Sun, 23 May 2010 00:15:58 +0200 Subject: [PATCH 2/2] add nestegg and halloc from http://github.com/kinetiknz/nestegg.git --- src/halloc/Makefile | 14 + src/halloc/README | 45 + src/halloc/halloc.h | 43 + src/halloc/src/align.h | 36 + src/halloc/src/halloc.c | 254 +++++ src/halloc/src/hlist.h | 136 +++ src/halloc/src/macros.h | 36 + src/nestegg.c | 1938 +++++++++++++++++++++++++++++++++++++++ src/nestegg.h | 288 ++++++ 9 files changed, 2790 insertions(+) create mode 100644 src/halloc/Makefile create mode 100644 src/halloc/README create mode 100644 src/halloc/halloc.h create mode 100644 src/halloc/src/align.h create mode 100644 src/halloc/src/halloc.c create mode 100644 src/halloc/src/hlist.h create mode 100644 src/halloc/src/macros.h create mode 100644 src/nestegg.c create mode 100644 src/nestegg.h diff --git a/src/halloc/Makefile b/src/halloc/Makefile new file mode 100644 index 0000000..d3df7d7 --- /dev/null +++ b/src/halloc/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -I. -ansi -Wall -pedantic + +LIBNAME = libhalloc.a +OBJS = src/halloc.o + +$(LIBNAME): $(OBJS) + ar rcs $(LIBNAME) $(OBJS) + +install: $(LIBNAME) + cp halloc.h /usr/include + cp $(LIBNAME) /usr/lib + +clean: + rm -f $(LIBNAME) $(OBJS) diff --git a/src/halloc/README b/src/halloc/README new file mode 100644 index 0000000..380fba2 --- /dev/null +++ b/src/halloc/README @@ -0,0 +1,45 @@ +halloc 1.2.1 +============ + + Hierarchical memory heap interface - an extension to standard + malloc/free interface that simplifies tasks of memory disposal + when allocated structures exhibit hierarchical properties. + + http://swapped.cc/halloc += + To build libhalloc.a with GNU tools run + make + + To install in /usr/include and /usr/lib + make install + + To cleanup the build files + make clean += + halloc-1.2.1 + * fixed a double-free bug in _set_allocator() as per + Matthew Gregan comments + + * switched to using NULL instead of 0 where applicable + + halloc-1.2.0 + * added missing include to halloc.c + + * improved standard compliance thanks to the feedback + received from Stan Tobias. Two things were fixed - + + - hblock_t structure no longer uses zero-sized 'data' + array, which happened to be common, but non-standard + extension; + + - secondly, added the code to test the behaviour of + realloc(ptr, 0). Standard allows it NOT to act as + free(), in which case halloc will use its own version + of allocator calling free() when neccessary. + + halloc-1.1.0 + * initial public release (rewrite of hhmalloc library) + +============================================================================= +Copyright (c) 2004-2010, Alex Pankratov (ap@swapped.cc). All rights reserved. + diff --git a/src/halloc/halloc.h b/src/halloc/halloc.h new file mode 100644 index 0000000..10af4e8 --- /dev/null +++ b/src/halloc/halloc.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2004-2010 Alex Pankratov. All rights reserved. + * + * Hierarchical memory allocator, 1.2.1 + * http://swapped.cc/halloc + */ + +/* + * The program is distributed under terms of BSD license. + * You can obtain the copy of the license by visiting: + * + * http://www.opensource.org/licenses/bsd-license.php + */ + +#ifndef _LIBP_HALLOC_H_ +#define _LIBP_HALLOC_H_ + +#include /* size_t */ + +/* + * Core API + */ +void * halloc (void * block, size_t len); +void hattach(void * block, void * parent); + +/* + * standard malloc/free api + */ +void * h_malloc (size_t len); +void * h_calloc (size_t n, size_t len); +void * h_realloc(void * p, size_t len); +void h_free (void * p); +char * h_strdup (const char * str); + +/* + * the underlying allocator + */ +typedef void * (* realloc_t)(void * ptr, size_t len); + +extern realloc_t halloc_allocator; + +#endif + diff --git a/src/halloc/src/align.h b/src/halloc/src/align.h new file mode 100644 index 0000000..4c6e183 --- /dev/null +++ b/src/halloc/src/align.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2004-2010 Alex Pankratov. All rights reserved. + * + * Hierarchical memory allocator, 1.2.1 + * http://swapped.cc/halloc + */ + +/* + * The program is distributed under terms of BSD license. + * You can obtain the copy of the license by visiting: + * + * http://www.opensource.org/licenses/bsd-license.php + */ + +#ifndef _LIBP_ALIGN_H_ +#define _LIBP_ALIGN_H_ + +/* + * a type with the most strict alignment requirements + */ +union max_align +{ + char c; + short s; + long l; + int i; + float f; + double d; + void * v; + void (*q)(void); +}; + +typedef union max_align max_align_t; + +#endif + diff --git a/src/halloc/src/halloc.c b/src/halloc/src/halloc.c new file mode 100644 index 0000000..5758fc0 --- /dev/null +++ b/src/halloc/src/halloc.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2004i-2010 Alex Pankratov. All rights reserved. + * + * Hierarchical memory allocator, 1.2.1 + * http://swapped.cc/halloc + */ + +/* + * The program is distributed under terms of BSD license. + * You can obtain the copy of the license by visiting: + * + * http://www.opensource.org/licenses/bsd-license.php + */ + +#include /* realloc */ +#include /* memset & co */ + +#include "halloc.h" +#include "align.h" +#include "hlist.h" + +/* + * block control header + */ +typedef struct hblock +{ +#ifndef NDEBUG +#define HH_MAGIC 0x20040518L + long magic; +#endif + hlist_item_t siblings; /* 2 pointers */ + hlist_head_t children; /* 1 pointer */ + max_align_t data[1]; /* not allocated, see below */ + +} hblock_t; + +#define sizeof_hblock offsetof(hblock_t, data) + +/* + * + */ +realloc_t halloc_allocator = NULL; + +#define allocator halloc_allocator + +/* + * static methods + */ +static void _set_allocator(void); +static void * _realloc(void * ptr, size_t n); + +static int _relate(hblock_t * b, hblock_t * p); +static void _free_children(hblock_t * p); + +/* + * Core API + */ +void * halloc(void * ptr, size_t len) +{ + hblock_t * p; + + /* set up default allocator */ + if (! allocator) + { + _set_allocator(); + assert(allocator); + } + + /* calloc */ + if (! ptr) + { + if (! len) + return NULL; + + p = allocator(0, len + sizeof_hblock); + if (! p) + return NULL; +#ifndef NDEBUG + p->magic = HH_MAGIC; +#endif + hlist_init(&p->children); + hlist_init_item(&p->siblings); + + return p->data; + } + + p = structof(ptr, hblock_t, data); + assert(p->magic == HH_MAGIC); + + /* realloc */ + if (len) + { + p = allocator(p, len + sizeof_hblock); + if (! p) + return NULL; + + hlist_relink(&p->siblings); + hlist_relink_head(&p->children); + + return p->data; + } + + /* free */ + _free_children(p); + hlist_del(&p->siblings); + allocator(p, 0); + + return NULL; +} + +void hattach(void * block, void * parent) +{ + hblock_t * b, * p; + + if (! block) + { + assert(! parent); + return; + } + + /* detach */ + b = structof(block, hblock_t, data); + assert(b->magic == HH_MAGIC); + + hlist_del(&b->siblings); + + if (! parent) + return; + + /* attach */ + p = structof(parent, hblock_t, data); + assert(p->magic == HH_MAGIC); + + /* sanity checks */ + assert(b != p); /* trivial */ + assert(! _relate(p, b)); /* heavy ! */ + + hlist_add(&p->children, &b->siblings); +} + +/* + * malloc/free api + */ +void * h_malloc(size_t len) +{ + return halloc(0, len); +} + +void * h_calloc(size_t n, size_t len) +{ + void * ptr = halloc(0, len*=n); + return ptr ? memset(ptr, 0, len) : NULL; +} + +void * h_realloc(void * ptr, size_t len) +{ + return halloc(ptr, len); +} + +void h_free(void * ptr) +{ + halloc(ptr, 0); +} + +char * h_strdup(const char * str) +{ + size_t len = strlen(str); + char * ptr = halloc(0, len + 1); + return ptr ? (ptr[len] = 0, memcpy(ptr, str, len)) : NULL; +} + +/* + * static stuff + */ +static void _set_allocator(void) +{ + void * p; + assert(! allocator); + + /* + * the purpose of the test below is to check the behaviour + * of realloc(ptr, 0), which is defined in the standard + * as an implementation-specific. if it returns zero, + * then it's equivalent to free(). it can however return + * non-zero, in which case it cannot be used for freeing + * memory blocks and we'll need to supply our own version + * + * Thanks to Stan Tobias for pointing this tricky part out. + */ + allocator = realloc; + if (! (p = malloc(1))) + /* hmm */ + return; + + if ((p = realloc(p, 0))) + { + /* realloc cannot be used as free() */ + allocator = _realloc; + free(p); + } +} + +static void * _realloc(void * ptr, size_t n) +{ + /* + * free'ing realloc() + */ + if (n) + return realloc(ptr, n); + free(ptr); + return NULL; +} + +static int _relate(hblock_t * b, hblock_t * p) +{ + hlist_item_t * i; + + if (!b || !p) + return 0; + + /* + * since there is no 'parent' pointer, which would've allowed + * O(log(n)) upward traversal, the check must use O(n) downward + * iteration of the entire hierarchy; and this can be VERY SLOW + */ + hlist_for_each(i, &p->children) + { + hblock_t * q = structof(i, hblock_t, siblings); + if (q == b || _relate(b, q)) + return 1; + } + return 0; +} + +static void _free_children(hblock_t * p) +{ + hlist_item_t * i, * tmp; + +#ifndef NDEBUG + /* + * this catches loops in hierarchy with almost zero + * overhead (compared to _relate() running time) + */ + assert(p && p->magic == HH_MAGIC); + p->magic = 0; +#endif + hlist_for_each_safe(i, tmp, &p->children) + { + hblock_t * q = structof(i, hblock_t, siblings); + _free_children(q); + allocator(q, 0); + } +} + diff --git a/src/halloc/src/hlist.h b/src/halloc/src/hlist.h new file mode 100644 index 0000000..2791f78 --- /dev/null +++ b/src/halloc/src/hlist.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2004-2010 Alex Pankratov. All rights reserved. + * + * Hierarchical memory allocator, 1.2.1 + * http://swapped.cc/halloc + */ + +/* + * The program is distributed under terms of BSD license. + * You can obtain the copy of the license by visiting: + * + * http://www.opensource.org/licenses/bsd-license.php + */ + +#ifndef _LIBP_HLIST_H_ +#define _LIBP_HLIST_H_ + +#include +#include "macros.h" /* static_inline */ + +/* + * weak double-linked list w/ tail sentinel + */ +typedef struct hlist_head hlist_head_t; +typedef struct hlist_item hlist_item_t; + +/* + * + */ +struct hlist_head +{ + hlist_item_t * next; +}; + +struct hlist_item +{ + hlist_item_t * next; + hlist_item_t ** prev; +}; + +/* + * shared tail sentinel + */ +struct hlist_item hlist_null; + +/* + * + */ +#define __hlist_init(h) { &hlist_null } +#define __hlist_init_item(i) { &hlist_null, &(i).next } + +static_inline void hlist_init(hlist_head_t * h); +static_inline void hlist_init_item(hlist_item_t * i); + +/* static_inline void hlist_purge(hlist_head_t * h); */ + +/* static_inline bool_t hlist_empty(const hlist_head_t * h); */ + +/* static_inline hlist_item_t * hlist_head(const hlist_head_t * h); */ + +/* static_inline hlist_item_t * hlist_next(const hlist_item_t * i); */ +/* static_inline hlist_item_t * hlist_prev(const hlist_item_t * i, + const hlist_head_t * h); */ + +static_inline void hlist_add(hlist_head_t * h, hlist_item_t * i); + +/* static_inline void hlist_add_prev(hlist_item_t * l, hlist_item_t * i); */ +/* static_inline void hlist_add_next(hlist_item_t * l, hlist_item_t * i); */ + +static_inline void hlist_del(hlist_item_t * i); + +static_inline void hlist_relink(hlist_item_t * i); +static_inline void hlist_relink_head(hlist_head_t * h); + +#define hlist_for_each(i, h) \ + for (i = (h)->next; i != &hlist_null; i = i->next) + +#define hlist_for_each_safe(i, tmp, h) \ + for (i = (h)->next, tmp = i->next; \ + i!= &hlist_null; \ + i = tmp, tmp = i->next) + +/* + * static + */ +static_inline void hlist_init(hlist_head_t * h) +{ + assert(h); + h->next = &hlist_null; +} + +static_inline void hlist_init_item(hlist_item_t * i) +{ + assert(i); + i->prev = &i->next; + i->next = &hlist_null; +} + +static_inline void hlist_add(hlist_head_t * h, hlist_item_t * i) +{ + hlist_item_t * next; + assert(h && i); + + next = i->next = h->next; + next->prev = &i->next; + h->next = i; + i->prev = &h->next; +} + +static_inline void hlist_del(hlist_item_t * i) +{ + hlist_item_t * next; + assert(i); + + next = i->next; + next->prev = i->prev; + *i->prev = next; + + hlist_init_item(i); +} + +static_inline void hlist_relink(hlist_item_t * i) +{ + assert(i); + *i->prev = i; + i->next->prev = &i->next; +} + +static_inline void hlist_relink_head(hlist_head_t * h) +{ + assert(h); + h->next->prev = &h->next; +} + +#endif + diff --git a/src/halloc/src/macros.h b/src/halloc/src/macros.h new file mode 100644 index 0000000..c36b516 --- /dev/null +++ b/src/halloc/src/macros.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2004-2010 Alex Pankratov. All rights reserved. + * + * Hierarchical memory allocator, 1.2.1 + * http://swapped.cc/halloc + */ + +/* + * The program is distributed under terms of BSD license. + * You can obtain the copy of the license by visiting: + * + * http://www.opensource.org/licenses/bsd-license.php + */ + +#ifndef _LIBP_MACROS_H_ +#define _LIBP_MACROS_H_ + +#include /* offsetof */ + +/* + restore pointer to the structure by a pointer to its field + */ +#define structof(p,t,f) ((t*)(- offsetof(t,f) + (char*)(p))) + +/* + * redefine for the target compiler + */ +#ifdef _WIN32 +#define static_inline static __inline +#else +#define static_inline static __inline__ +#endif + + +#endif + diff --git a/src/nestegg.c b/src/nestegg.c new file mode 100644 index 0000000..2ee4199 --- /dev/null +++ b/src/nestegg.c @@ -0,0 +1,1938 @@ +/* + * Copyright © 2010 Matthew Gregan + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#include +#include +#include + +#include "halloc.h" +#include "nestegg.h" + +/* EBML Elements */ +#define ID_EBML 0x1a45dfa3 +#define ID_EBML_VERSION 0x4286 +#define ID_EBML_READ_VERSION 0x42f7 +#define ID_EBML_MAX_ID_LENGTH 0x42f2 +#define ID_EBML_MAX_SIZE_LENGTH 0x42f3 +#define ID_DOCTYPE 0x4282 +#define ID_DOCTYPE_VERSION 0x4287 +#define ID_DOCTYPE_READ_VERSION 0x4285 + +/* Global Elements */ +#define ID_VOID 0xec +#define ID_CRC32 0xbf + +/* WebMedia Elements */ +#define ID_SEGMENT 0x18538067 + +/* Seek Head Elements */ +#define ID_SEEK_HEAD 0x114d9b74 +#define ID_SEEK 0x4dbb +#define ID_SEEK_ID 0x53ab +#define ID_SEEK_POSITION 0x53ac + +/* Info Elements */ +#define ID_INFO 0x1549a966 +#define ID_TIMECODE_SCALE 0x2ad7b1 +#define ID_DURATION 0x4489 + +/* Cluster Elements */ +#define ID_CLUSTER 0x1f43b675 +#define ID_TIMECODE 0xe7 +#define ID_BLOCK_GROUP 0xa0 +#define ID_SIMPLE_BLOCK 0xa3 + +/* BlockGroup Elements */ +#define ID_BLOCK 0xa1 +#define ID_BLOCK_DURATION 0x9b +#define ID_REFERENCE_BLOCK 0xfb + +/* Tracks Elements */ +#define ID_TRACKS 0x1654ae6b +#define ID_TRACK_ENTRY 0xae +#define ID_TRACK_NUMBER 0xd7 +#define ID_TRACK_UID 0x73c5 +#define ID_TRACK_TYPE 0x83 +#define ID_FLAG_ENABLED 0xb9 +#define ID_FLAG_DEFAULT 0x88 +#define ID_FLAG_LACING 0x9c +#define ID_TRACK_TIMECODE_SCALE 0x23314f +#define ID_LANGUAGE 0x22b59c +#define ID_CODEC_ID 0x86 +#define ID_CODEC_PRIVATE 0x63a2 + +/* Video Elements */ +#define ID_VIDEO 0xe0 +#define ID_PIXEL_WIDTH 0xb0 +#define ID_PIXEL_HEIGHT 0xba +#define ID_PIXEL_CROP_BOTTOM 0x54aa +#define ID_PIXEL_CROP_TOP 0x54bb +#define ID_PIXEL_CROP_LEFT 0x54cc +#define ID_PIXEL_CROP_RIGHT 0x54dd +#define ID_DISPLAY_WIDTH 0x54b0 +#define ID_DISPLAY_HEIGHT 0x54ba + +/* Audio Elements */ +#define ID_AUDIO 0xe1 +#define ID_SAMPLING_FREQUENCY 0xb5 +#define ID_CHANNELS 0x9f +#define ID_BIT_DEPTH 0x6264 + +/* Cues Elements */ +#define ID_CUES 0x1c53bb6b +#define ID_CUE_POINT 0xbb +#define ID_CUE_TIME 0xb3 +#define ID_CUE_TRACK_POSITIONS 0xb7 +#define ID_CUE_TRACK 0xf7 +#define ID_CUE_CLUSTER_POSITION 0xf1 +#define ID_CUE_BLOCK_NUMBER 0x5378 + +/* EBML Types */ +enum ebml_type_enum { + TYPE_UNKNOWN, + TYPE_MASTER, + TYPE_UINT, + TYPE_FLOAT, + TYPE_INT, + TYPE_STRING, + TYPE_BINARY +}; + +#define LIMIT_STRING (1 << 20) +#define LIMIT_BINARY (1 << 24) +#define LIMIT_BLOCK (1 << 30) +#define LIMIT_FRAME (1 << 28) + +/* Field Flags */ +#define DESC_FLAG_NONE 0 +#define DESC_FLAG_MULTI (1 << 0) +#define DESC_FLAG_SUSPEND (1 << 1) +#define DESC_FLAG_OFFSET (1 << 2) + +/* Lacing Constants */ +#define LACING_NONE 0 +#define LACING_XIPH 1 +#define LACING_FIXED 2 +#define LACING_EBML 3 + +enum vint_mask { + MASK_NONE, + MASK_FIRST_BIT +}; + +struct ebml_binary { + unsigned char * data; + size_t length; +}; + +struct ebml_list_node { + struct ebml_list_node * next; + uint64_t id; + void * data; +}; + +struct ebml_list { + struct ebml_list_node * head; + struct ebml_list_node * tail; +}; + +struct ebml_type { + union ebml_value { + uint64_t u; + double f; + int64_t i; + char * s; + struct ebml_binary b; + } v; + enum ebml_type_enum type; + int read; +}; + +/* EBML Definitions */ +struct ebml { + struct ebml_type ebml_version; + struct ebml_type ebml_read_version; + struct ebml_type ebml_max_id_length; + struct ebml_type ebml_max_size_length; + struct ebml_type doctype; + struct ebml_type doctype_version; + struct ebml_type doctype_read_version; +}; + +/* Matroksa Definitions */ +struct seek { + struct ebml_type id; + struct ebml_type position; +}; + +struct seek_head { + struct ebml_list seek; +}; + +struct info { + struct ebml_type timecode_scale; + struct ebml_type duration; +}; + +struct block_group { + struct ebml_type duration; + struct ebml_type reference_block; +}; + +struct cluster { + struct ebml_type timecode; + struct ebml_list block_group; +}; + +struct video { + struct ebml_type pixel_width; + struct ebml_type pixel_height; + struct ebml_type pixel_crop_bottom; + struct ebml_type pixel_crop_top; + struct ebml_type pixel_crop_left; + struct ebml_type pixel_crop_right; + struct ebml_type display_width; + struct ebml_type display_height; +}; + +struct audio { + struct ebml_type sampling_frequency; + struct ebml_type channels; + struct ebml_type bit_depth; +}; + +struct track_entry { + struct ebml_type number; + struct ebml_type uid; + struct ebml_type type; + struct ebml_type flag_enabled; + struct ebml_type flag_default; + struct ebml_type flag_lacing; + struct ebml_type track_timecode_scale; + struct ebml_type language; + struct ebml_type codec_id; + struct ebml_type codec_private; + struct video video; + struct audio audio; +}; + +struct tracks { + struct ebml_list track_entry; +}; + +struct cue_track_positions { + struct ebml_type track; + struct ebml_type cluster_position; + struct ebml_type block_number; +}; + +struct cue_point { + struct ebml_type time; + struct ebml_list cue_track_positions; +}; + +struct cues { + struct ebml_list cue_point; +}; + +struct segment { + struct ebml_list seek_head; + struct info info; + struct ebml_list cluster; + struct tracks tracks; + struct cues cues; +}; + +/* Misc. */ +struct pool_ctx { + char dummy; +}; + +struct list_node { + struct list_node * previous; + struct ebml_element_desc * node; + unsigned char * data; +}; + +struct saved_state { + int64_t stream_offset; + struct list_node * ancestor; + uint64_t last_id; + uint64_t last_size; +}; + +struct frame { + unsigned char * data; + size_t length; + struct frame * next; +}; + +/* Public (opaque) Structures */ +struct nestegg { + nestegg_io * io; + nestegg_log log; + struct pool_ctx * alloc_pool; + uint64_t last_id; + uint64_t last_size; + struct list_node * ancestor; + struct ebml ebml; + struct segment segment; + int64_t segment_offset; + unsigned int track_count; +}; + +struct nestegg_packet { + uint64_t track; + uint64_t timecode; + struct frame * frame; +}; + +/* Element Descriptor */ +struct ebml_element_desc { + char const * name; + uint64_t id; + enum ebml_type_enum type; + size_t offset; + unsigned int flags; + struct ebml_element_desc * children; + size_t size; + size_t data_offset; +}; + +#define E_FIELD(ID, TYPE, STRUCT, FIELD) \ + { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, NULL, 0, 0 } +#define E_MASTER(ID, TYPE, STRUCT, FIELD) \ + { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_MULTI, FIELD ## _elements, \ + sizeof(struct FIELD), 0 } +#define E_SINGLE_MASTER_O(ID, TYPE, STRUCT, FIELD) \ + { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_OFFSET, FIELD ## _elements, 0, \ + offsetof(STRUCT, FIELD ## _offset) } +#define E_SINGLE_MASTER(ID, TYPE, STRUCT, FIELD) \ + { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, FIELD ## _elements, 0, 0 } +#define E_SUSPEND(ID, TYPE) \ + { #ID, ID, TYPE, 0, DESC_FLAG_SUSPEND, NULL, 0, 0 } +#define E_LAST \ + { NULL, 0, 0, 0, DESC_FLAG_NONE, NULL, 0, 0 } + +/* EBML Element Lists */ +static struct ebml_element_desc ebml_elements[] = { + E_FIELD(ID_EBML_VERSION, TYPE_UINT, struct ebml, ebml_version), + E_FIELD(ID_EBML_READ_VERSION, TYPE_UINT, struct ebml, ebml_read_version), + E_FIELD(ID_EBML_MAX_ID_LENGTH, TYPE_UINT, struct ebml, ebml_max_id_length), + E_FIELD(ID_EBML_MAX_SIZE_LENGTH, TYPE_UINT, struct ebml, ebml_max_size_length), + E_FIELD(ID_DOCTYPE, TYPE_STRING, struct ebml, doctype), + E_FIELD(ID_DOCTYPE_VERSION, TYPE_UINT, struct ebml, doctype_version), + E_FIELD(ID_DOCTYPE_READ_VERSION, TYPE_UINT, struct ebml, doctype_read_version), + E_LAST +}; + +/* WebMedia Element Lists */ +static struct ebml_element_desc seek_elements[] = { + E_FIELD(ID_SEEK_ID, TYPE_BINARY, struct seek, id), + E_FIELD(ID_SEEK_POSITION, TYPE_UINT, struct seek, position), + E_LAST +}; + +static struct ebml_element_desc seek_head_elements[] = { + E_MASTER(ID_SEEK, TYPE_MASTER, struct seek_head, seek), + E_LAST +}; + +static struct ebml_element_desc info_elements[] = { + E_FIELD(ID_TIMECODE_SCALE, TYPE_UINT, struct info, timecode_scale), + E_FIELD(ID_DURATION, TYPE_FLOAT, struct info, duration), + E_LAST +}; + +static struct ebml_element_desc block_group_elements[] = { + E_SUSPEND(ID_BLOCK, TYPE_BINARY), + E_FIELD(ID_BLOCK_DURATION, TYPE_UINT, struct block_group, duration), + E_FIELD(ID_REFERENCE_BLOCK, TYPE_INT, struct block_group, reference_block), + E_LAST +}; + +static struct ebml_element_desc cluster_elements[] = { + E_FIELD(ID_TIMECODE, TYPE_UINT, struct cluster, timecode), + E_MASTER(ID_BLOCK_GROUP, TYPE_MASTER, struct cluster, block_group), + E_SUSPEND(ID_SIMPLE_BLOCK, TYPE_BINARY), + E_LAST +}; + +static struct ebml_element_desc video_elements[] = { + E_FIELD(ID_PIXEL_WIDTH, TYPE_UINT, struct video, pixel_width), + E_FIELD(ID_PIXEL_HEIGHT, TYPE_UINT, struct video, pixel_height), + E_FIELD(ID_PIXEL_CROP_BOTTOM, TYPE_UINT, struct video, pixel_crop_bottom), + E_FIELD(ID_PIXEL_CROP_TOP, TYPE_UINT, struct video, pixel_crop_top), + E_FIELD(ID_PIXEL_CROP_LEFT, TYPE_UINT, struct video, pixel_crop_left), + E_FIELD(ID_PIXEL_CROP_RIGHT, TYPE_UINT, struct video, pixel_crop_right), + E_FIELD(ID_DISPLAY_WIDTH, TYPE_UINT, struct video, display_width), + E_FIELD(ID_DISPLAY_HEIGHT, TYPE_UINT, struct video, display_height), + E_LAST +}; + +static struct ebml_element_desc audio_elements[] = { + E_FIELD(ID_SAMPLING_FREQUENCY, TYPE_FLOAT, struct audio, sampling_frequency), + E_FIELD(ID_CHANNELS, TYPE_UINT, struct audio, channels), + E_FIELD(ID_BIT_DEPTH, TYPE_UINT, struct audio, bit_depth), + E_LAST +}; + +static struct ebml_element_desc track_entry_elements[] = { + E_FIELD(ID_TRACK_NUMBER, TYPE_UINT, struct track_entry, number), + E_FIELD(ID_TRACK_UID, TYPE_UINT, struct track_entry, uid), + E_FIELD(ID_TRACK_TYPE, TYPE_UINT, struct track_entry, type), + E_FIELD(ID_FLAG_ENABLED, TYPE_UINT, struct track_entry, flag_enabled), + E_FIELD(ID_FLAG_DEFAULT, TYPE_UINT, struct track_entry, flag_default), + E_FIELD(ID_FLAG_LACING, TYPE_UINT, struct track_entry, flag_lacing), + E_FIELD(ID_TRACK_TIMECODE_SCALE, TYPE_FLOAT, struct track_entry, track_timecode_scale), + E_FIELD(ID_LANGUAGE, TYPE_STRING, struct track_entry, language), + E_FIELD(ID_CODEC_ID, TYPE_STRING, struct track_entry, codec_id), + E_FIELD(ID_CODEC_PRIVATE, TYPE_BINARY, struct track_entry, codec_private), + E_SINGLE_MASTER(ID_VIDEO, TYPE_MASTER, struct track_entry, video), + E_SINGLE_MASTER(ID_AUDIO, TYPE_MASTER, struct track_entry, audio), + E_LAST +}; + +static struct ebml_element_desc tracks_elements[] = { + E_MASTER(ID_TRACK_ENTRY, TYPE_MASTER, struct tracks, track_entry), + E_LAST +}; + +static struct ebml_element_desc cue_track_positions_elements[] = { + E_FIELD(ID_CUE_TRACK, TYPE_UINT, struct cue_track_positions, track), + E_FIELD(ID_CUE_CLUSTER_POSITION, TYPE_UINT, struct cue_track_positions, cluster_position), + E_FIELD(ID_CUE_BLOCK_NUMBER, TYPE_UINT, struct cue_track_positions, block_number), + E_LAST +}; + +static struct ebml_element_desc cue_point_elements[] = { + E_FIELD(ID_CUE_TIME, TYPE_UINT, struct cue_point, time), + E_MASTER(ID_CUE_TRACK_POSITIONS, TYPE_MASTER, struct cue_point, cue_track_positions), + E_LAST +}; + +static struct ebml_element_desc cues_elements[] = { + E_MASTER(ID_CUE_POINT, TYPE_MASTER, struct cues, cue_point), + E_LAST +}; + +static struct ebml_element_desc segment_elements[] = { + E_MASTER(ID_SEEK_HEAD, TYPE_MASTER, struct segment, seek_head), + E_SINGLE_MASTER(ID_INFO, TYPE_MASTER, struct segment, info), + E_MASTER(ID_CLUSTER, TYPE_MASTER, struct segment, cluster), + E_SINGLE_MASTER(ID_TRACKS, TYPE_MASTER, struct segment, tracks), + E_SINGLE_MASTER(ID_CUES, TYPE_MASTER, struct segment, cues), + E_LAST +}; + +static struct ebml_element_desc top_level_elements[] = { + E_SINGLE_MASTER(ID_EBML, TYPE_MASTER, nestegg, ebml), + E_SINGLE_MASTER_O(ID_SEGMENT, TYPE_MASTER, nestegg, segment), + E_LAST +}; + +#undef E_FIELD +#undef E_MASTER +#undef E_SINGLE_MASTER_O +#undef E_SINGLE_MASTER +#undef E_SUSPEND +#undef E_LAST + +static struct pool_ctx * +pool_init(void) +{ + struct pool_ctx * pool; + + pool = h_malloc(sizeof(*pool)); + if (!pool) + abort(); + return pool; +} + +static void +pool_destroy(struct pool_ctx * pool) +{ + h_free(pool); +} + +static void * +pool_alloc(size_t size, struct pool_ctx * pool) +{ + void * p; + + p = h_malloc(size); + if (!p) + abort(); + hattach(p, pool); + memset(p, 0, size); + return p; +} + +#if 0 +static void +pool_free(void * p) +{ + h_free(p); +} +#endif + +static void * +alloc(size_t size) +{ + void * p; + + p = calloc(1, size); + if (!p) + abort(); + return p; +} + +static int +io_read(nestegg_io * io, void * buffer, size_t length) +{ + return io->read(buffer, length, io->userdata); +} + +static int +io_seek(nestegg_io * io, int64_t offset, int whence) +{ + return io->seek(offset, whence, io->userdata); +} + +static int64_t +io_tell(nestegg_io * io) +{ + return io->tell(io->userdata); +} + +static int +bare_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length, enum vint_mask maskflag) +{ + int r; + unsigned char b; + size_t maxlen = 8; + unsigned int count = 1, mask = 1 << 7; + + r = io_read(io, &b, 1); + if (r != 1) + return r; + + while (count < maxlen) { + if ((b & mask) != 0) + break; + mask >>= 1; + count += 1; + } + + if (length) + *length = count; + *value = b; + + if (maskflag == MASK_FIRST_BIT) { + *value = b & ~mask; + } + + while (--count) { + r = io_read(io, &b, 1); + if (r != 1) + return r; + *value <<= 8; + *value |= b; + } + + return 1; +} + +static int +read_id(nestegg_io * io, uint64_t * value, uint64_t * length) +{ + return bare_read_vint(io, value, length, MASK_NONE); +} + +static int +read_vint(nestegg_io * io, uint64_t * value, uint64_t * length) +{ + return bare_read_vint(io, value, length, MASK_FIRST_BIT); +} + +static int +read_svint(nestegg_io * io, int64_t * value, uint64_t * length) +{ + int r; + uint64_t uvalue; + uint64_t ulength; + int64_t svint_subtr[] = { + 0x3f, 0x1fff, + 0xfffff, 0x7ffffff, + 0x3ffffffffLL, 0x1ffffffffffLL, + 0xffffffffffffLL, 0x7fffffffffffffLL + }; + + r = bare_read_vint(io, &uvalue, &ulength, MASK_FIRST_BIT); + if (r != 1) + return r; + *value = uvalue - svint_subtr[ulength - 1]; + if (length) + *length = ulength; + return r; +} + +static int +read_uint(nestegg_io * io, uint64_t * val, uint64_t length) +{ + unsigned char b; + int r; + + if (length == 0 || length > 8) + return -1; + r = io_read(io, &b, 1); + if (r != 1) + return r; + *val = b; + while (--length) { + r = io_read(io, &b, 1); + if (r != 1) + return r; + *val <<= 8; + *val |= b; + } + return 1; +} + +static int +read_int(nestegg_io * io, int64_t * val, uint64_t length) +{ + int r; + uint64_t uval, base; + + r = read_uint(io, &uval, length); + if (r != 1) + return r; + + if (length < sizeof(int64_t)) { + base = 1; + base <<= length * 8 - 1; + if (uval >= base) { + base = 1; + base <<= length * 8; + } else { + base = 0; + } + *val = uval - base; + } else { + *val = (int64_t) uval; + } + + return 1; +} + +static int +read_float(nestegg_io * io, double * val, uint64_t length) +{ + union { + uint64_t u; + float f; + double d; + } value; + int r; + + /* length == 10 not implemented */ + if (length != 4 && length != 8) + return -1; + r = read_uint(io, &value.u, length); + if (r != 1) + return r; + if (length == 4) + *val = value.f; + else + *val = value.d; + return 1; +} + +static int +read_string(nestegg * ctx, char ** val, uint64_t length) +{ + char * str; + int r; + + if (length == 0 || length > LIMIT_STRING) + return -1; + str = pool_alloc(length + 1, ctx->alloc_pool); + r = io_read(ctx->io, (unsigned char *) str, length); + if (r != 1) + return r; + str[length] = '\0'; + *val = str; + return 1; +} + +static int +read_binary(nestegg * ctx, struct ebml_binary * val, uint64_t length) +{ + if (length == 0 || length > LIMIT_BINARY) + return -1; + val->data = pool_alloc(length, ctx->alloc_pool); + val->length = length; + return io_read(ctx->io, val->data, length); +} + +static int +get_uint(struct ebml_type type, uint64_t * value) +{ + if (!type.read) + return -1; + + assert(type.type == TYPE_UINT); + + *value = type.v.u; + + return 0; +} + +static int +get_float(struct ebml_type type, double * value) +{ + if (!type.read) + return -1; + + assert(type.type == TYPE_FLOAT); + + *value = type.v.f; + + return 0; +} + +#if 0 +static int +get_int(struct ebml_type type, int64_t * value) +{ + if (!type.read) + return -1; + + assert(type.type == TYPE_INT); + + *value = type.v.i; + + return 0; +} +#endif + +static int +get_string(struct ebml_type type, char ** value) +{ + if (!type.read) + return -1; + + assert(type.type == TYPE_STRING); + + *value = type.v.s; + + return 0; +} + +static int +get_binary(struct ebml_type type, struct ebml_binary * value) +{ + if (!type.read) + return -1; + + assert(type.type == TYPE_BINARY); + + *value = type.v.b; + + return 0; +} + +static int +is_ancestor_element(uint64_t id, struct list_node * ancestor) +{ + struct ebml_element_desc * element; + + for (; ancestor; ancestor = ancestor->previous) + for (element = ancestor->node; element->id; ++element) + if (element->id == id) + return 1; + + return 0; +} + +static struct ebml_element_desc * +find_element(uint64_t id, struct ebml_element_desc * elements) +{ + struct ebml_element_desc * element; + + for (element = elements; element->id; ++element) + if (element->id == id) + return element; + + return NULL; +} + +static void +ctx_push(nestegg * ctx, struct ebml_element_desc * ancestor, void * data) +{ + struct list_node * item; + + item = alloc(sizeof(*item)); + item->previous = ctx->ancestor; + item->node = ancestor; + item->data = data; + ctx->ancestor = item; +} + +static void +ctx_pop(nestegg * ctx) +{ + struct list_node * item; + + item = ctx->ancestor; + ctx->ancestor = item->previous; + free(item); +} + +static int +ctx_save(nestegg * ctx, struct saved_state * s) +{ + s->stream_offset = io_tell(ctx->io); + if (s->stream_offset < 0) + return -1; + s->ancestor = ctx->ancestor; + s->last_id = ctx->last_id; + s->last_size = ctx->last_size; + return 0; +} + +static int +ctx_restore(nestegg * ctx, struct saved_state * s) +{ + int r; + + r = io_seek(ctx->io, s->stream_offset, NESTEGG_SEEK_SET); + if (r != 0) + return -1; + ctx->ancestor = s->ancestor; + ctx->last_id = s->last_id; + ctx->last_size = s->last_size; + return 0; +} + +static int +peek_element(nestegg * ctx, uint64_t * id, uint64_t * size) +{ + int r; + + if (ctx->last_id && ctx->last_size) { + if (id) + *id = ctx->last_id; + if (size) + *size = ctx->last_size; + return 1; + } + + r = read_id(ctx->io, &ctx->last_id, NULL); + if (r != 1) + return r; + + r = read_vint(ctx->io, &ctx->last_size, NULL); + if (r != 1) + return r; + + if (id) + *id = ctx->last_id; + if (size) + *size = ctx->last_size; + + return 1; +} + +static int +read_element(nestegg * ctx, uint64_t * id, uint64_t * size) +{ + int r; + + r = peek_element(ctx, id, size); + if (r != 1) + return r; + + ctx->last_id = 0; + ctx->last_size = 0; + + return 1; +} + +static int +read_master(nestegg * ctx, struct ebml_element_desc * desc) +{ + struct ebml_list * list; + struct ebml_list_node * node, * oldtail; + + assert(desc->type == TYPE_MASTER && desc->flags & DESC_FLAG_MULTI); + + ctx->log(ctx, NESTEGG_LOG_DEBUG, "multi master element %llx (%s)", + desc->id, desc->name); + + list = (struct ebml_list *) (ctx->ancestor->data + desc->offset); + + node = pool_alloc(sizeof(*node), ctx->alloc_pool); + node->id = desc->id; + node->data = pool_alloc(desc->size, ctx->alloc_pool); + + oldtail = list->tail; + if (oldtail) + oldtail->next = node; + list->tail = node; + if (!list->head) + list->head = node; + + ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p", node->data); + + ctx_push(ctx, desc->children, node->data); + + return 1; +} + +static int +read_single_master(nestegg * ctx, struct ebml_element_desc * desc) +{ + assert(desc->type == TYPE_MASTER && !(desc->flags & DESC_FLAG_MULTI)); + + ctx->log(ctx, NESTEGG_LOG_DEBUG, "single master element %llx (%s)", + desc->id, desc->name); + ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p (%u)", + ctx->ancestor->data + desc->offset, desc->offset); + + ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); + + return 1; +} + +static int +read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) +{ + struct ebml_type * storage; + int r; + + storage = (struct ebml_type *) (ctx->ancestor->data + desc->offset); + + if (storage->read) + return -1; + + storage->type = desc->type; + + ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) -> %p (%u)", + desc->id, desc->name, storage, desc->offset); + + r = -1; + + switch (desc->type) { + case TYPE_UINT: + r = read_uint(ctx->io, &storage->v.u, length); + break; + case TYPE_FLOAT: + r = read_float(ctx->io, &storage->v.f, length); + break; + case TYPE_INT: + r = read_int(ctx->io, &storage->v.i, length); + break; + case TYPE_STRING: + r = read_string(ctx, &storage->v.s, length); + break; + case TYPE_BINARY: + r = read_binary(ctx, &storage->v.b, length); + break; + case TYPE_MASTER: + case TYPE_UNKNOWN: + assert(0); + break; + } + + if (r == 1) + storage->read = 1; + + return r; +} + +static int +parse(nestegg * ctx, struct ebml_element_desc * top_level) +{ + int r; + int64_t * data_offset; + uint64_t id, size; + struct ebml_element_desc * element; + + /* loop until we need to return: + - hit suspend point + - parse complete + - error occurred */ + + /* loop over elements at current level reading them if sublevel found, + push ctx onto stack and continue if sublevel ended, pop ctx off stack + and continue */ + + for (;;) { + r = peek_element(ctx, &id, &size); + if (r != 1) + break; + + element = find_element(id, ctx->ancestor->node); + if (element) { + if (element->flags & DESC_FLAG_SUSPEND) { + assert(element->type == TYPE_BINARY); + ctx->log(ctx, NESTEGG_LOG_DEBUG, "suspend parse at %llx", id); + r = 1; + break; + } + + r = read_element(ctx, &id, &size); + if (r != 1) + break; + + if (element->flags & DESC_FLAG_OFFSET) { + data_offset = (int64_t *) (ctx->ancestor->data + element->data_offset); + *data_offset = io_tell(ctx->io); + if (*data_offset < 0) + return -1; + } + + if (element->type == TYPE_MASTER) { + if (element->flags & DESC_FLAG_MULTI) { + r = read_master(ctx, element); + } else { + r = read_single_master(ctx, element); + } + continue; + } else { + r = read_simple(ctx, element, size); + } + } else if (is_ancestor_element(id, ctx->ancestor->previous)) { + ctx->log(ctx, NESTEGG_LOG_DEBUG, "parent element %llx", id); + if (top_level && ctx->ancestor->node == top_level) { + ctx->log(ctx, NESTEGG_LOG_DEBUG, "*** parse about to back up past top_level"); + r = 1; + break; + } + ctx_pop(ctx); + } else { + r = read_element(ctx, &id, &size); + if (r != 1) + break; + + if (id != ID_VOID && id != ID_CRC32) + ctx->log(ctx, NESTEGG_LOG_DEBUG, "unknown element %llx", id); + r = io_seek(ctx->io, size, NESTEGG_SEEK_CUR); + if (r != 0) + return -1; + } + } + + if (r != 1) { + while(ctx->ancestor) + ctx_pop(ctx); + } + + return r; +} + +static uint64_t +xiph_lace_value(unsigned char ** np) +{ + uint64_t lace; + uint64_t value; + unsigned char * p = *np; + + lace = *p++; + value = lace; + while (lace == 255) { + lace = *p++; + value += lace; + } + + *np = p; + + return value; +} + +static int +read_xiph_lace_value(nestegg_io * io, uint64_t * value, size_t * consumed) +{ + int r; + uint64_t lace; + + r = read_uint(io, &lace, 1); + if (r != 1) + return r; + *consumed += 1; + + *value = lace; + while (lace == 255) { + r = read_uint(io, &lace, 1); + if (r != 1) + return r; + *consumed += 1; + *value += lace; + } + + return 1; +} + +static int +read_xiph_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) +{ + int r; + size_t i = 0; + uint64_t sum = 0; + + while (--n) { + r = read_xiph_lace_value(io, &sizes[i], read); + if (r != 1) + return r; + sum += sizes[i]; + i += 1; + } + + if (*read + sum > block) + return -1; + + /* last frame is the remainder of the block */ + sizes[i] = block - *read - sum; + return 1; +} + +static int +read_ebml_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) +{ + int r; + uint64_t lace, sum, length; + int64_t slace; + size_t i = 0; + + r = read_vint(io, &lace, &length); + if (r != 1) + return r; + *read += length; + + sizes[i] = lace; + sum = sizes[i]; + + i += 1; + n -= 1; + + while (--n) { + r = read_svint(io, &slace, &length); + if (r != 1) + return r; + *read += length; + sizes[i] = sizes[i - 1] + slace; + sum += sizes[i]; + i += 1; + } + + if (*read + sum > block) + return -1; + + /* last frame is the remainder of the block */ + sizes[i] = block - *read - sum; + return 1; +} + +static uint64_t +get_timecode_scale(nestegg * ctx) +{ + uint64_t scale; + + if (get_uint(ctx->segment.info.timecode_scale, &scale) != 0) + scale = 1000000; + + return scale; +} + +static struct track_entry * +find_track_entry(nestegg * ctx, unsigned int track) +{ + struct ebml_list_node * node; + unsigned int tracks = 0; + + node = ctx->segment.tracks.track_entry.head; + while (node) { + assert(node->id == ID_TRACK_ENTRY); + if (track == tracks) { + return node->data; + } + tracks += 1; + node = node->next; + } + + return NULL; +} + +static int +read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_packet ** data) +{ + int r; + int64_t timecode, abs_timecode; + nestegg_packet * pkt; + struct cluster * cluster; + struct frame * f, * last; + struct track_entry * entry; + double track_scale; + uint64_t track, length, frame_sizes[256], cluster_tc, flags, frames, tc_scale, total; + unsigned int i, lacing; + size_t consumed = 0; + + *data = NULL; + + if (block_size > LIMIT_BLOCK) + return -1; + + r = read_vint(ctx->io, &track, &length); + if (r != 1) + return r; + + if (track == 0 || track > ctx->track_count) + return -1; + + consumed += length; + + r = read_int(ctx->io, &timecode, 2); + if (r != 1) + return r; + + consumed += 2; + + r = read_uint(ctx->io, &flags, 1); + if (r != 1) + return r; + + consumed += 1; + + frames = 0; + + /* flags are different between block and simpleblock, but lacing is + encoded the same way */ + lacing = (flags & 0x6) >> 1; + + switch (lacing) { + case LACING_NONE: + frames = 1; + break; + case LACING_XIPH: + case LACING_FIXED: + case LACING_EBML: + r = read_uint(ctx->io, &frames, 1); + if (r != 1) + return r; + consumed += 1; + frames += 1; + } + + if (frames > 256) + return -1; + + switch (lacing) { + case LACING_NONE: + frame_sizes[0] = block_size - consumed; + break; + case LACING_XIPH: + if (frames == 1) + return -1; + r = read_xiph_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); + if (r != 1) + return r; + break; + case LACING_FIXED: + if ((block_size - consumed) % frames) + return -1; + for (i = 0; i < frames; ++i) { + frame_sizes[i] = (block_size - consumed) / frames; + } + break; + case LACING_EBML: + if (frames == 1) + return -1; + r = read_ebml_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); + if (r != 1) + return r; + break; + } + + /* sanity check unlaced frame sizes against total block size. */ + total = consumed; + for (i = 0; i < frames; ++i) { + total += frame_sizes[i]; + } + if (total > block_size) + return -1; + + entry = find_track_entry(ctx, track - 1); + if (!entry) + return -1; + + track_scale = 1.0; + get_float(entry->track_timecode_scale, &track_scale); + + tc_scale = get_timecode_scale(ctx); + + assert(ctx->segment.cluster.tail->id == ID_CLUSTER); + cluster = ctx->segment.cluster.tail->data; + if (get_uint(cluster->timecode, &cluster_tc) != 0) + return -1; + + abs_timecode = timecode + cluster_tc; + if (abs_timecode < 0) + return -1; + + pkt = alloc(sizeof(*pkt)); + pkt->track = track - 1; + pkt->timecode = abs_timecode * tc_scale * track_scale; + + ctx->log(ctx, NESTEGG_LOG_DEBUG, "%sblock t %lld pts %f f %llx frames: %llu", + block_id == ID_BLOCK ? "" : "simple", pkt->track, pkt->timecode / 1e9, flags, frames); + + last = NULL; + for (i = 0; i < frames; ++i) { + if (frame_sizes[i] > LIMIT_FRAME) { + nestegg_free_packet(pkt); + return -1; + } + f = alloc(sizeof(*f)); + f->data = alloc(frame_sizes[i]); + f->length = frame_sizes[i]; + r = io_read(ctx->io, f->data, frame_sizes[i]); + if (r != 1) { + free(f->data); + free(f); + nestegg_free_packet(pkt); + return -1; + } + + if (!last) { + pkt->frame = f; + } else { + last->next = f; + } + last = f; + } + + *data = pkt; + + return 1; +} + +static uint64_t +buf_read_id(unsigned char const * p, size_t length) +{ + uint64_t id = 0; + + while (length--) { + id <<= 8; + id |= *p++; + } + + return id; +} + +static struct seek * +find_seek_for_id(struct ebml_list_node * seek_head, uint64_t id) +{ + struct ebml_list * head; + struct ebml_list_node * seek; + struct ebml_binary binary_id; + struct seek * s; + + while (seek_head) { + assert(seek_head->id == ID_SEEK_HEAD); + head = seek_head->data; + seek = head->head; + + while (seek) { + assert(seek->id == ID_SEEK); + s = seek->data; + + if (get_binary(s->id, &binary_id) == 0 && + buf_read_id(binary_id.data, binary_id.length) == id) { + return s; + } + + seek = seek->next; + } + + seek_head = seek_head->next; + } + + return NULL; +} + +static struct cue_point * +find_cue_point_for_tstamp(struct ebml_list_node * cue_point, uint64_t scale, uint64_t tstamp) +{ + uint64_t time; + struct cue_point *c, * prev = NULL; + + while (cue_point) { + assert(cue_point->id == ID_CUE_POINT); + c = cue_point->data; + + if (!prev) + prev = c; + + if (get_uint(c->time, &time) == 0 && + (time * scale > tstamp)) + return prev; + + prev = cue_point->data; + cue_point = cue_point->next; + } + + return NULL; +} + +static int +is_suspend_element(uint64_t id) +{ + /* this could search the tree of elements for DESC_FLAG_SUSPEND */ + if (id == ID_SIMPLE_BLOCK || id == ID_BLOCK) + return 1; + return 0; +} + +static void +null_log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ...) +{ + if (ctx && severity && fmt) + return; +} + +int +nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback) +{ + int r; + uint64_t id, version; + struct ebml_list_node * track; + char * doctype; + nestegg * ctx = NULL; + + if (!(io.read && io.seek && io.tell)) + return -1; + + ctx = alloc(sizeof(*ctx)); + + ctx->io = alloc(sizeof(*ctx->io)); + *ctx->io = io; + ctx->log = callback; + ctx->alloc_pool = pool_init(); + + if (!ctx->log) + ctx->log = null_log_callback; + + r = peek_element(ctx, &id, NULL); + if (r != 1) { + nestegg_destroy(ctx); + return -1; + } + + if (id != ID_EBML) { + nestegg_destroy(ctx); + return -1; + } + + ctx->log(ctx, NESTEGG_LOG_DEBUG, "ctx %p", ctx); + + ctx_push(ctx, top_level_elements, ctx); + + r = parse(ctx, NULL); + + if (r != 1) { + nestegg_destroy(ctx); + return -1; + } + + /* XXX youtube hack: accept webm and matroska for now */ + if (get_string(ctx->ebml.doctype, &doctype) == 0 && + (strcmp(doctype, "webm") == 0 || + strcmp(doctype, "matroska") == 0) && + get_uint(ctx->ebml.doctype_read_version, &version) == 0 && version <= 2 && + get_uint(ctx->ebml.ebml_read_version, &version) == 0 && version <= 1 && + !ctx->segment.tracks.track_entry.head) { + nestegg_destroy(ctx); + return -1; + } + + track = ctx->segment.tracks.track_entry.head; + ctx->track_count = 0; + + while (track) { + ctx->track_count += 1; + track = track->next; + } + + *context = ctx; + + return 0; +} + +void +nestegg_destroy(nestegg * ctx) +{ + while (ctx->ancestor) + ctx_pop(ctx); + pool_destroy(ctx->alloc_pool); + free(ctx->io); + free(ctx); +} + +int +nestegg_duration(nestegg * ctx, uint64_t * duration) +{ + uint64_t tc_scale; + double unscaled_duration; + + if (get_float(ctx->segment.info.duration, &unscaled_duration) != 0) + return -1; + + tc_scale = get_timecode_scale(ctx); + + *duration = (uint64_t) (unscaled_duration * tc_scale); + return 0; +} + +int +nestegg_track_count(nestegg * ctx, unsigned int * tracks) +{ + *tracks = ctx->track_count; + return 0; +} + +int +nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp) +{ + int r; + struct cue_point * cue_point; + struct cue_track_positions * pos; + struct saved_state state; + struct seek * found; + uint64_t seek_pos, tc_scale, t, id; + struct ebml_list_node * node = ctx->segment.cues.cue_point.head; + + /* If there are no cues loaded, check for cues element in the seek head + and load it. */ + if (!node) { + found = find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); + if (!found) + return -1; + + if (get_uint(found->position, &seek_pos) != 0) + return -1; + + /* Save old parser state. */ + r = ctx_save(ctx, &state); + if (r != 0) + return -1; + + /* Seek and set up parser state for segment-level element (Cues). */ + r = io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); + if (r != 0) + return -1; + ctx->last_id = 0; + ctx->last_size = 0; + + r = read_element(ctx, &id, NULL); + if (r != 1) + return -1; + + if (id != ID_CUES) + return -1; + + ctx->ancestor = NULL; + ctx_push(ctx, top_level_elements, ctx); + ctx_push(ctx, segment_elements, &ctx->segment); + ctx_push(ctx, cues_elements, &ctx->segment.cues); + /* parser will run until end of cues element. */ + ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements"); + r = parse(ctx, cues_elements); + while (ctx->ancestor) + ctx_pop(ctx); + + /* Reset parser state to original state and seek back to old position. */ + r = ctx_restore(ctx, &state); + if (r != 0) + return -1; + } + + tc_scale = get_timecode_scale(ctx); + + cue_point = find_cue_point_for_tstamp(ctx->segment.cues.cue_point.head, tc_scale, tstamp); + if (!cue_point) + return -1; + + node = cue_point->cue_track_positions.head; + + seek_pos = 0; + + while (node) { + assert(node->id == ID_CUE_TRACK_POSITIONS); + pos = node->data; + if (get_uint(pos->track, &t) == 0 && t - 1 == track) { + if (get_uint(pos->cluster_position, &seek_pos) != 0) + return -1; + break; + } + node = node->next; + } + + /* Seek and set up parser state for segment-level element (Cluster). */ + r = io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); + if (r != 0) + return -1; + ctx->last_id = 0; + ctx->last_size = 0; + + while (ctx->ancestor) + ctx_pop(ctx); + + ctx_push(ctx, top_level_elements, ctx); + ctx_push(ctx, segment_elements, &ctx->segment); + ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cluster elements"); + r = parse(ctx, NULL); + if (r != 1) + return -1; + + if (!is_suspend_element(ctx->last_id)) + return -1; + + return 0; +} + +int +nestegg_track_type(nestegg * ctx, unsigned int track) +{ + struct track_entry * entry; + uint64_t type; + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (get_uint(entry->type, &type) != 0) + return -1; + + if (type & 0x1) + return NESTEGG_TRACK_VIDEO; + + if (type & 0x2) + return NESTEGG_TRACK_AUDIO; + + return -1; +} + +struct bitmapinfoheader { + int size; + int width; + int height; + short planes; + short bit_count; + unsigned int compression; + int size_image; + int x_pels_per_meter; + int y_pels_per_meter; + int clr_used; + int clr_important; +}; + +int +nestegg_track_codec_id(nestegg * ctx, unsigned int track) +{ + char * codec_id; + struct ebml_binary codec_private; + struct track_entry * entry; + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (get_string(entry->codec_id, &codec_id) != 0) + return -1; + + if (strcmp(codec_id, "V_VP8") == 0) + return NESTEGG_CODEC_VP8; + + if (strcmp(codec_id, "A_VORBIS") == 0) + return NESTEGG_CODEC_VORBIS; + + /* XXX youtube hack: accept VFW codec id for now */ + if (strcmp(codec_id, "V_MS/VFW/FOURCC") == 0 && + get_binary(entry->codec_private, &codec_private) == 0 && + codec_private.length >= 40) { + struct bitmapinfoheader * bih = (struct bitmapinfoheader *) codec_private.data; + if (bih->compression == 0x30385056) + return NESTEGG_CODEC_VP8; + } + + return -1; +} + +int +nestegg_track_codec_data_count(nestegg * ctx, unsigned int track, + unsigned int * count) +{ + struct track_entry * entry; + struct ebml_binary codec_private; + unsigned char * p; + + *count = 0; + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) + return -1; + + if (get_binary(entry->codec_private, &codec_private) != 0) + return -1; + + if (codec_private.length < 1) + return -1; + + p = codec_private.data; + *count = *p + 1; + + if (*count > 3) + return -1; + + return 0; +} + +int +nestegg_track_codec_data(nestegg * ctx, unsigned int track, unsigned int item, + unsigned char ** data, size_t * length) +{ + struct track_entry * entry; + struct ebml_binary codec_private; + uint64_t sizes[3], total; + unsigned char * p; + unsigned int count, i; + + *data = NULL; + *length = 0; + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) + return -1; + + if (get_binary(entry->codec_private, &codec_private) != 0) + return -1; + + p = codec_private.data; + count = *p++ + 1; + + if (count > 3) + return -1; + + i = 0; + total = 0; + while (--count) { + sizes[i] = xiph_lace_value(&p); + total += sizes[i]; + i += 1; + } + sizes[i] = codec_private.length - total - (p - codec_private.data); + + for (i = 0; i < item; ++i) { + if (sizes[i] > LIMIT_FRAME) + return -1; + p += sizes[i]; + } + *data = p; + *length = sizes[item]; + + return 0; +} + +int +nestegg_track_video_params(nestegg * ctx, unsigned int track, + nestegg_video_params * params) +{ + struct track_entry * entry; + uint64_t value; + + memset(params, 0, sizeof(*params)); + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_VIDEO) + return -1; + + if (get_uint(entry->video.pixel_width, &value) != 0) + return -1; + params->width = value; + + if (get_uint(entry->video.pixel_height, &value) != 0) + return -1; + params->height = value; + + value = 0; + get_uint(entry->video.pixel_crop_bottom, &value); + params->crop_bottom = value; + + value = 0; + get_uint(entry->video.pixel_crop_top, &value); + params->crop_top = value; + + value = 0; + get_uint(entry->video.pixel_crop_left, &value); + params->crop_left = value; + + value = 0; + get_uint(entry->video.pixel_crop_right, &value); + params->crop_right = value; + + value = params->width; + get_uint(entry->video.display_width, &value); + params->display_width = value; + + value = params->height; + get_uint(entry->video.display_height, &value); + params->display_height = value; + + return 0; +} + +int +nestegg_track_audio_params(nestegg * ctx, unsigned int track, + nestegg_audio_params * params) +{ + struct track_entry * entry; + uint64_t value; + + memset(params, 0, sizeof(*params)); + + entry = find_track_entry(ctx, track); + if (!entry) + return -1; + + if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_AUDIO) + return -1; + + params->rate = 8000; + get_float(entry->audio.sampling_frequency, ¶ms->rate); + + value = 1; + get_uint(entry->audio.channels, &value); + params->channels = value; + + value = 16; + get_uint(entry->audio.bit_depth, &value); + params->depth = value; + + return 0; +} + +int +nestegg_read_packet(nestegg * ctx, nestegg_packet ** pkt) +{ + int r; + uint64_t id, size; + + *pkt = NULL; + + for (;;) { + r = peek_element(ctx, &id, &size); + if (r != 1) { + return r; + } + + /* any suspend fields must be handled here */ + if (is_suspend_element(id)) { + r = read_element(ctx, &id, &size); + if (r != 1) { + return r; + } + + /* the only suspend fields are blocks and simple blocks, which we + handle directly. */ + r = read_block(ctx, id, size, pkt); + return r; + } + + r = parse(ctx, NULL); + if (r != 1) + return r; + } + + return 1; +} + +void +nestegg_free_packet(nestegg_packet * pkt) +{ + struct frame * frame; + + while (pkt->frame) { + frame = pkt->frame; + pkt->frame = frame->next; + free(frame->data); + free(frame); + } + + free(pkt); +} + +int +nestegg_packet_track(nestegg_packet * pkt, unsigned int * track) +{ + *track = pkt->track; + return 0; +} + +int +nestegg_packet_tstamp(nestegg_packet * pkt, uint64_t * tstamp) +{ + *tstamp = pkt->timecode; + return 0; +} + +int +nestegg_packet_count(nestegg_packet * pkt, unsigned int * count) +{ + struct frame * f = pkt->frame; + + *count = 0; + + while (f) { + *count += 1; + f = f->next; + } + + return 0; +} + +int +nestegg_packet_data(nestegg_packet * pkt, unsigned int item, + unsigned char ** data, size_t * length) +{ + struct frame * f = pkt->frame; + unsigned int count = 0; + + *data = NULL; + *length = 0; + + while (f) { + if (count == item) { + *data = f->data; + *length = f->length; + return 0; + } + count += 1; + f = f->next; + } + + return -1; +} diff --git a/src/nestegg.h b/src/nestegg.h new file mode 100644 index 0000000..c9ade75 --- /dev/null +++ b/src/nestegg.h @@ -0,0 +1,288 @@ +/* + * Copyright © 2010 Matthew Gregan + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#ifndef NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 +#define NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 + +#ifdef _WIN32 +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** @mainpage + + @section intro Introduction + + This is the documentation fot the libnestegg C API. + libnestegg is a demultiplexing library for Matroska and WebMedia media files. + + @section example Example code + + @code + nestegg * demux_ctx; + nestegg_init(&demux_ctx, io, NULL); + + nestegg_packet * pkt; + while ((r = nestegg_read_packet(demux_ctx, &pkt)) > 0) { + unsigned int track; + + nestegg_packet_track(pkt, &track); + + // This example decodes the first track only. + if (track == 0) { + unsigned int chunk, chunks; + + nestegg_packet_count(pkt, &chunks); + + // Decode each chunk of data. + for (chunk = 0; chunk < chunks; ++chunk) { + unsigned char * data; + size_t data_size; + + nestegg_packet_data(pkt, chunk, &data, &data_size); + + example_codec_decode(codec_ctx, data, data_size); + } + } + + nestegg_free_packet(pkt); + } + + nestegg_destroy(demux_ctx); + @endcode +*/ + + +/** @file + The libnestegg C API. */ + +#define NESTEGG_TRACK_VIDEO 0 /**< Track is of type video. */ +#define NESTEGG_TRACK_AUDIO 1 /**< Track is of type audio. */ + +#define NESTEGG_CODEC_VP8 0 /**< Track uses Google On2 VP8 codec. */ +#define NESTEGG_CODEC_VORBIS 1 /**< Track uses Xiph Vorbis codec. */ + +#define NESTEGG_SEEK_SET 0 /**< Seek offset relative to beginning of stream. */ +#define NESTEGG_SEEK_CUR 1 /**< Seek offset relative to current position in stream. */ +#define NESTEGG_SEEK_END 2 /**< Seek offset relative to end of stream. */ + +#define NESTEGG_LOG_DEBUG 1 /**< Debug level log message. */ +#define NESTEGG_LOG_INFO 10 /**< Informational level log message. */ +#define NESTEGG_LOG_WARNING 100 /**< Warning level log message. */ +#define NESTEGG_LOG_ERROR 1000 /**< Error level log message. */ +#define NESTEGG_LOG_CRITICAL 10000 /**< Critical level log message. */ + +typedef struct nestegg nestegg; /**< Opaque handle referencing the stream state. */ +typedef struct nestegg_packet nestegg_packet; /**< Opaque handle referencing a packet of data. */ + +/** User supplied IO context. */ +typedef struct { + /** User supplied read callback. + @param buffer Buffer to read data into. + @param length Length of supplied buffer in bytes. + @param userptr The #userdata supplied by the user. + @retval 1 Read succeeded. + @retval 0 End of stream. + @retval -1 Error. */ + int (* read)(void * buffer, size_t length, void * userdata); + + /** User supplied seek callback. + @param offset Offset within the stream to seek to. + @param whence Seek direction. One of #NESTEGG_SEEK_SET, + #NESTEGG_SEEK_CUR, or #NESTEGG_SEEK_END. + @param userdata The #userdata supplied by the user. + @retval 0 Seek succeeded. + @retval -1 Error. */ + int (* seek)(int64_t offset, int whence, void * userdata); + + /** User supplied tell callback. + @param userdata The #userdata supplied by the user. + @returns Current position within the stream. + @retval -1 Error. */ + int64_t (* tell)(void * userdata); + + /** User supplied pointer to be passed to the IO callbacks. */ + void * userdata; +} nestegg_io; + +/** Parameters specific to a video track. */ +typedef struct { + unsigned int width; /**< Width of the video frame in pixels. */ + unsigned int height; /**< Height of the video frame in pixels. */ + unsigned int display_width; /**< Display width of the video frame in pixels. */ + unsigned int display_height; /**< Display height of the video frame in pixels. */ + unsigned int crop_bottom; /**< Pixels to crop from the bottom of the frame. */ + unsigned int crop_top; /**< Pixels to crop from the top of the frame. */ + unsigned int crop_left; /**< Pixels to crop from the left of the frame. */ + unsigned int crop_right; /**< Pixels to crop from the right of the frame. */ +} nestegg_video_params; + +/** Parameters specific to an audio track. */ +typedef struct { + double rate; /**< Sampling rate in Hz. */ + unsigned int channels; /**< Number of audio channels. */ + unsigned int depth; /**< Bits per sample. */ +} nestegg_audio_params; + +/** Logging callback function pointer. */ +typedef void (* nestegg_log)(nestegg * context, unsigned int severity, char const * format, ...); + +/** Initialize a nestegg context. During initialization the parser will + read forward in the stream processing all elements until the first + block of media is reached. All track metadata has been processed at this point. + @param context Storage for the new nestegg context. @see nestegg_destroy + @param io User supplied IO context. + @param callback Optional logging callback function pointer. May be NULL. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback); + +/** Destroy a nestegg context and free associated memory. + @param context #nestegg context to be freed. @see nestegg_init */ +void nestegg_destroy(nestegg * context); + +/** Query the duration of the media stream in nanoseconds. + @param context Stream context initialized by #nestegg_init. + @param duration Storage for the queried duration. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_duration(nestegg * context, uint64_t * duration); + +/** Query the number of tracks in the media stream. + @param context Stream context initialized by #nestegg_init. + @param tracks Storage for the queried track count. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_count(nestegg * context, unsigned int * tracks); + +/** Seek @a track to @a tstamp. Stream seek will terminate at the earliest + key point in the stream at or before @a tstamp. Other tracks in the + stream will output packets with unspecified but nearby timestamps. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param tstamp Absolute timestamp in nanoseconds. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_seek(nestegg * context, unsigned int track, uint64_t tstamp); + +/** Query the type specified by @a track. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @retval #NESTEGG_TRACK_VIDEO Track type is video. + @retval #NESTEGG_TRACK_VIDEO Track type is audio. + @retval -1 Error. */ +int nestegg_track_type(nestegg * context, unsigned int track); + +/** Query the codec ID specified by @a track. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @retval #NESTEGG_CODEC_VP8 Track codec is VP8. + @retval #NESTEGG_CODEC_VORBIS Track codec is Vorbis. + @retval -1 Error. */ +int nestegg_track_codec_id(nestegg * context, unsigned int track); + +/** Query the number of codec initialization chunks for @a track. Each + chunk of data should be passed to the codec initialization functions in + the order returned. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param count Storage for the queried chunk count. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_codec_data_count(nestegg * context, unsigned int track, + unsigned int * count); + +/** Get a pointer to chunk number @a item of codec initialization data for + @a track. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param item Zero based chunk item number. + @param data Storage for the queried data pointer. + The data is owned by the #nestegg context. + @param length Storage for the queried data size. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_codec_data(nestegg * context, unsigned int track, unsigned int item, + unsigned char ** data, size_t * length); + +/** Query the video parameters specified by @a track. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param params Storage for the queried video parameters. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_video_params(nestegg * context, unsigned int track, + nestegg_video_params * params); + +/** Query the audio parameters specified by @a track. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param params Storage for the queried audio parameters. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_audio_params(nestegg * context, unsigned int track, + nestegg_audio_params * params); + +/** Read a packet of media data. A packet consists of one or more chunks of + data associated with a single track. nestegg_read_packet should be + called in a loop while the return value is 1 to drive the stream parser + forward. @see nestegg_free_packet + @param context Context returned by #nestegg_init. + @param packet Storage for the returned nestegg_packet. + @retval 1 Additional packets may be read in subsequent calls. + @retval 0 End of stream. + @retval -1 Error. */ +int nestegg_read_packet(nestegg * context, nestegg_packet ** packet); + +/** Destroy a nestegg_packet and free associated memory. + @param packet #nestegg_packet to be freed. @see nestegg_read_packet */ +void nestegg_free_packet(nestegg_packet * packet); + +/** Query the track number of @a packet. + @param packet Packet initialized by #nestegg_read_packet. + @param track Storage for the queried zero based track index. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_track(nestegg_packet * packet, unsigned int * track); + +/** Query the time stamp in nanoseconds of @a packet. + @param packet Packet initialized by #nestegg_read_packet. + @param tstamp Storage for the queried timestamp in nanoseconds. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_tstamp(nestegg_packet * packet, uint64_t * tstamp); + +/** Query the number of data chunks contained in @a packet. + @param packet Packet initialized by #nestegg_read_packet. + @param count Storage for the queried timestamp in nanoseconds. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_count(nestegg_packet * packet, unsigned int * count); + +/** Get a pointer to chunk number @a item of packet data. + @param packet Packet initialized by #nestegg_read_packet. + @param item Zero based chunk item number. + @param data Storage for the queried data pointer. + The data is owned by the #nestegg_packet packet. + @param length Storage for the queried data size. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_data(nestegg_packet * packet, unsigned int item, + unsigned char ** data, size_t * length); + +#ifdef __cplusplus +} +#endif + +#endif /* NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 */