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 */