diff --git a/Makefile b/Makefile index a091ca9..dfa262c 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,10 @@ FLAGS += -I. -Isrc -Isrc/halloc INSTALL = install -INCLUDEFLAGS ?= `pkg-config --cflags oggplay` `imlib2-config --cflags` +INCLUDEFLAGS ?= `pkg-config --cflags oggplay vpx` `imlib2-config --cflags` LINKFLAGS += -L${PREFIX}/lib LINKFLAGS += `imlib2-config --libs` -LINKFLAGS += `pkg-config --libs oggplay` -lvpx +LINKFLAGS += `pkg-config --libs oggplay vpx` LINKFLAGS += ${LDFLAGS} LINKFLAGS += -lm diff --git a/src/nestegg.c b/src/nestegg.c index b0e0497..54dc330 100644 --- a/src/nestegg.c +++ b/src/nestegg.c @@ -47,8 +47,17 @@ /* BlockGroup Elements */ #define ID_BLOCK 0xa1 +#define ID_BLOCK_ADDITIONS 0x75a1 #define ID_BLOCK_DURATION 0x9b #define ID_REFERENCE_BLOCK 0xfb +#define ID_DISCARD_PADDING 0x75a2 + +/* BlockAdditions Elements */ +#define ID_BLOCK_MORE 0xa6 + +/* BlockMore Elements */ +#define ID_BLOCK_ADD_ID 0xee +#define ID_BLOCK_ADDITIONAL 0xa5 /* Tracks Elements */ #define ID_TRACKS 0x1654ae6b @@ -63,10 +72,14 @@ #define ID_LANGUAGE 0x22b59c #define ID_CODEC_ID 0x86 #define ID_CODEC_PRIVATE 0x63a2 +#define ID_CODEC_DELAY 0x56aa +#define ID_SEEK_PREROLL 0x56bb +#define ID_DEFAULT_DURATION 0x23e383 /* Video Elements */ #define ID_VIDEO 0xe0 #define ID_STEREO_MODE 0x53b8 +#define ID_ALPHA_MODE 0x53C0 #define ID_PIXEL_WIDTH 0xb0 #define ID_PIXEL_HEIGHT 0xba #define ID_PIXEL_CROP_BOTTOM 0x54aa @@ -128,7 +141,9 @@ enum ebml_type_enum { /* Track IDs */ #define TRACK_ID_VP8 "V_VP8" +#define TRACK_ID_VP9 "V_VP9" #define TRACK_ID_VORBIS "A_VORBIS" +#define TRACK_ID_OPUS "A_OPUS" enum vint_mask { MASK_NONE, @@ -189,9 +204,20 @@ struct info { struct ebml_type duration; }; +struct block_more { + struct ebml_type block_add_id; + struct ebml_type block_additional; +}; + +struct block_additions { + struct ebml_list block_more; +}; + struct block_group { + struct ebml_type block_additions; struct ebml_type duration; struct ebml_type reference_block; + struct ebml_type discard_padding; }; struct cluster { @@ -201,6 +227,7 @@ struct cluster { struct video { struct ebml_type stereo_mode; + struct ebml_type alpha_mode; struct ebml_type pixel_width; struct ebml_type pixel_height; struct ebml_type pixel_crop_bottom; @@ -228,6 +255,9 @@ struct track_entry { struct ebml_type language; struct ebml_type codec_id; struct ebml_type codec_private; + struct ebml_type codec_delay; + struct ebml_type seek_preroll; + struct ebml_type default_duration; struct video video; struct audio audio; }; @@ -275,6 +305,7 @@ struct saved_state { struct list_node * ancestor; uint64_t last_id; uint64_t last_size; + int last_valid; }; struct frame { @@ -283,6 +314,13 @@ struct frame { struct frame * next; }; +struct block_additional { + unsigned int id; + unsigned char * data; + size_t length; + struct block_additional * next; +}; + /* Public (opaque) Structures */ struct nestegg { nestegg_io * io; @@ -290,6 +328,7 @@ struct nestegg { struct pool_ctx * alloc_pool; uint64_t last_id; uint64_t last_size; + int last_valid; struct list_node * ancestor; struct ebml ebml; struct segment segment; @@ -300,7 +339,10 @@ struct nestegg { struct nestegg_packet { uint64_t track; uint64_t timecode; + uint64_t duration; struct frame * frame; + struct block_additional * block_additional; + int64_t discard_padding; }; /* Element Descriptor */ @@ -360,10 +402,23 @@ static struct ebml_element_desc ne_info_elements[] = { E_LAST }; +static struct ebml_element_desc ne_block_more_elements[] = { + E_FIELD(ID_BLOCK_ADD_ID, TYPE_UINT, struct block_more, block_add_id), + E_FIELD(ID_BLOCK_ADDITIONAL, TYPE_BINARY, struct block_more, block_additional), + E_LAST +}; + +static struct ebml_element_desc ne_block_additions_elements[] = { + E_MASTER(ID_BLOCK_MORE, TYPE_MASTER, struct block_additions, block_more), + E_LAST +}; + static struct ebml_element_desc ne_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_FIELD(ID_DISCARD_PADDING, TYPE_INT, struct block_group, discard_padding), + E_SINGLE_MASTER(ID_BLOCK_ADDITIONS, TYPE_MASTER, struct block_group, block_additions), E_LAST }; @@ -376,6 +431,7 @@ static struct ebml_element_desc ne_cluster_elements[] = { static struct ebml_element_desc ne_video_elements[] = { E_FIELD(ID_STEREO_MODE, TYPE_UINT, struct video, stereo_mode), + E_FIELD(ID_ALPHA_MODE, TYPE_UINT, struct video, alpha_mode), 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), @@ -405,6 +461,9 @@ static struct ebml_element_desc ne_track_entry_elements[] = { 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_FIELD(ID_CODEC_DELAY, TYPE_UINT, struct track_entry, codec_delay), + E_FIELD(ID_SEEK_PREROLL, TYPE_UINT, struct track_entry, seek_preroll), + E_FIELD(ID_DEFAULT_DURATION, TYPE_UINT, struct track_entry, default_duration), E_SINGLE_MASTER(ID_VIDEO, TYPE_MASTER, struct track_entry, video), E_SINGLE_MASTER(ID_AUDIO, TYPE_MASTER, struct track_entry, audio), E_LAST @@ -458,12 +517,7 @@ static struct ebml_element_desc ne_top_level_elements[] = { static struct pool_ctx * ne_pool_init(void) { - struct pool_ctx * pool; - - pool = h_malloc(sizeof(*pool)); - if (!pool) - abort(); - return pool; + return h_malloc(sizeof(struct pool_ctx)); } static void @@ -479,7 +533,7 @@ ne_pool_alloc(size_t size, struct pool_ctx * pool) p = h_malloc(size); if (!p) - abort(); + return NULL; hattach(p, pool); memset(p, 0, size); return p; @@ -488,12 +542,7 @@ ne_pool_alloc(size_t size, struct pool_ctx * pool) static void * ne_alloc(size_t size) { - void * p; - - p = calloc(1, size); - if (!p) - abort(); - return p; + return calloc(1, size); } static int @@ -684,6 +733,8 @@ ne_read_string(nestegg * ctx, char ** val, uint64_t length) if (length == 0 || length > LIMIT_STRING) return -1; str = ne_pool_alloc(length + 1, ctx->alloc_pool); + if (!str) + return -1; r = ne_io_read(ctx->io, (unsigned char *) str, length); if (r != 1) return r; @@ -698,6 +749,8 @@ ne_read_binary(nestegg * ctx, struct ebml_binary * val, uint64_t length) if (length == 0 || length > LIMIT_BINARY) return -1; val->data = ne_pool_alloc(length, ctx->alloc_pool); + if (!val->data) + return -1; val->length = length; return ne_io_read(ctx->io, val->data, length); } @@ -779,16 +832,19 @@ ne_find_element(uint64_t id, struct ebml_element_desc * elements) return NULL; } -static void +static int ne_ctx_push(nestegg * ctx, struct ebml_element_desc * ancestor, void * data) { struct list_node * item; item = ne_alloc(sizeof(*item)); + if (!item) + return -1; item->previous = ctx->ancestor; item->node = ancestor; item->data = data; ctx->ancestor = item; + return 0; } static void @@ -810,6 +866,7 @@ ne_ctx_save(nestegg * ctx, struct saved_state * s) s->ancestor = ctx->ancestor; s->last_id = ctx->last_id; s->last_size = ctx->last_size; + s->last_valid = ctx->last_valid; return 0; } @@ -824,6 +881,7 @@ ne_ctx_restore(nestegg * ctx, struct saved_state * s) ctx->ancestor = s->ancestor; ctx->last_id = s->last_id; ctx->last_size = s->last_size; + ctx->last_valid = s->last_valid; return 0; } @@ -832,7 +890,7 @@ ne_peek_element(nestegg * ctx, uint64_t * id, uint64_t * size) { int r; - if (ctx->last_id && ctx->last_size) { + if (ctx->last_valid) { if (id) *id = ctx->last_id; if (size) @@ -853,6 +911,8 @@ ne_peek_element(nestegg * ctx, uint64_t * id, uint64_t * size) if (size) *size = ctx->last_size; + ctx->last_valid = 1; + return 1; } @@ -865,13 +925,12 @@ ne_read_element(nestegg * ctx, uint64_t * id, uint64_t * size) if (r != 1) return r; - ctx->last_id = 0; - ctx->last_size = 0; + ctx->last_valid = 0; return 1; } -static void +static int ne_read_master(nestegg * ctx, struct ebml_element_desc * desc) { struct ebml_list * list; @@ -885,8 +944,12 @@ ne_read_master(nestegg * ctx, struct ebml_element_desc * desc) list = (struct ebml_list *) (ctx->ancestor->data + desc->offset); node = ne_pool_alloc(sizeof(*node), ctx->alloc_pool); + if (!node) + return -1; node->id = desc->id; node->data = ne_pool_alloc(desc->size, ctx->alloc_pool); + if (!node->data) + return -1; oldtail = list->tail; if (oldtail) @@ -897,10 +960,13 @@ ne_read_master(nestegg * ctx, struct ebml_element_desc * desc) ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p", node->data); - ne_ctx_push(ctx, desc->children, node->data); + if (ne_ctx_push(ctx, desc->children, node->data) < 0) + return -1; + + return 0; } -static void +static int ne_read_single_master(nestegg * ctx, struct ebml_element_desc * desc) { assert(desc->type == TYPE_MASTER && !(desc->flags & DESC_FLAG_MULTI)); @@ -910,7 +976,7 @@ ne_read_single_master(nestegg * ctx, struct ebml_element_desc * desc) ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p (%u)", ctx->ancestor->data + desc->offset, desc->offset); - ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); + return ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); } static int @@ -932,8 +998,6 @@ ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) 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 = ne_read_uint(ctx->io, &storage->v.u, length); @@ -953,6 +1017,7 @@ ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) case TYPE_MASTER: case TYPE_UNKNOWN: assert(0); + r = 0; break; } @@ -963,20 +1028,26 @@ ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) } static int -ne_parse(nestegg * ctx, struct ebml_element_desc * top_level) +ne_parse(nestegg * ctx, struct ebml_element_desc * top_level, int64_t max_offset) { int r; int64_t * data_offset; - uint64_t id, size; + uint64_t id, size, peeked_id; struct ebml_element_desc * element; if (!ctx->ancestor) return -1; for (;;) { + if (max_offset > 0 && ne_io_tell(ctx->io) >= max_offset) { + /* Reached end of offset allowed for parsing - return gracefully */ + r = 1; + break; + } r = ne_peek_element(ctx, &id, &size); if (r != 1) break; + peeked_id = id; element = ne_find_element(id, ctx->ancestor->node); if (element) { @@ -990,6 +1061,7 @@ ne_parse(nestegg * ctx, struct ebml_element_desc * top_level) r = ne_read_element(ctx, &id, &size); if (r != 1) break; + assert(id == peeked_id); if (element->flags & DESC_FLAG_OFFSET) { data_offset = (int64_t *) (ctx->ancestor->data + element->data_offset); @@ -1001,10 +1073,13 @@ ne_parse(nestegg * ctx, struct ebml_element_desc * top_level) } if (element->type == TYPE_MASTER) { - if (element->flags & DESC_FLAG_MULTI) - ne_read_master(ctx, element); - else - ne_read_single_master(ctx, element); + if (element->flags & DESC_FLAG_MULTI) { + if (ne_read_master(ctx, element) < 0) + break; + } else { + if (ne_read_single_master(ctx, element) < 0) + break; + } continue; } else { r = ne_read_simple(ctx, element, size); @@ -1152,6 +1227,37 @@ ne_get_timecode_scale(nestegg * ctx) return scale; } +static int +ne_map_track_number_to_index(nestegg * ctx, + unsigned int track_number, + unsigned int * track_index) +{ + struct ebml_list_node * node; + struct track_entry * t_entry; + uint64_t t_number = 0; + + if (!track_index) + return -1; + *track_index = 0; + + if (track_number == 0) + return -1; + + node = ctx->segment.tracks.track_entry.head; + while (node) { + assert(node->id == ID_TRACK_ENTRY); + t_entry = node->data; + if (ne_get_uint(t_entry->number, &t_number) != 0) + return -1; + if (t_number == track_number) + return 0; + *track_index += 1; + node = node->next; + } + + return -1; +} + static struct track_entry * ne_find_track_entry(nestegg * ctx, unsigned int track) { @@ -1180,8 +1286,8 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac 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; + uint64_t track_number, length, frame_sizes[256], cluster_tc, flags, frames, tc_scale, total; + unsigned int i, lacing, track; size_t consumed = 0; *data = NULL; @@ -1189,11 +1295,11 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac if (block_size > LIMIT_BLOCK) return -1; - r = ne_read_vint(ctx->io, &track, &length); + r = ne_read_vint(ctx->io, &track_number, &length); if (r != 1) return r; - if (track == 0 || track > ctx->track_count) + if (track_number == 0) return -1; consumed += length; @@ -1266,7 +1372,10 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac if (total > block_size) return -1; - entry = ne_find_track_entry(ctx, track - 1); + if (ne_map_track_number_to_index(ctx, track_number, &track) != 0) + return -1; + + entry = ne_find_track_entry(ctx, track); if (!entry) return -1; @@ -1284,7 +1393,9 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac return -1; pkt = ne_alloc(sizeof(*pkt)); - pkt->track = track - 1; + if (!pkt) + return -1; + pkt->track = track; pkt->timecode = abs_timecode * tc_scale * track_scale; ctx->log(ctx, NESTEGG_LOG_DEBUG, "%sblock t %lld pts %f f %llx frames: %llu", @@ -1297,7 +1408,16 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac return -1; } f = ne_alloc(sizeof(*f)); + if (!f) { + nestegg_free_packet(pkt); + return -1; + } f->data = ne_alloc(frame_sizes[i]); + if (!f->data) { + free(f); + nestegg_free_packet(pkt); + return -1; + } f->length = frame_sizes[i]; r = ne_io_read(ctx->io, f->data, frame_sizes[i]); if (r != 1) { @@ -1319,6 +1439,173 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac return 1; } +static int +ne_read_block_duration(nestegg * ctx, nestegg_packet * pkt) +{ + int r; + uint64_t id, size; + struct ebml_element_desc * element; + struct ebml_type * storage; + + r = ne_peek_element(ctx, &id, &size); + if (r != 1) + return r; + + if (id != ID_BLOCK_DURATION) + return 1; + + element = ne_find_element(id, ctx->ancestor->node); + if (!element) + return 1; + + r = ne_read_simple(ctx, element, size); + if (r != 1) + return r; + storage = (struct ebml_type *) (ctx->ancestor->data + element->offset); + pkt->duration = storage->v.i * ne_get_timecode_scale(ctx); + + return 1; +} + +static int +ne_read_discard_padding(nestegg * ctx, nestegg_packet * pkt) +{ + int r; + uint64_t id, size; + struct ebml_element_desc * element; + struct ebml_type * storage; + + r = ne_peek_element(ctx, &id, &size); + if (r != 1) + return r; + + if (id != ID_DISCARD_PADDING) + return 1; + + element = ne_find_element(id, ctx->ancestor->node); + if (!element) + return 1; + + r = ne_read_simple(ctx, element, size); + if (r != 1) + return r; + storage = (struct ebml_type *) (ctx->ancestor->data + element->offset); + pkt->discard_padding = storage->v.i; + + return 1; +} + +static int +ne_read_block_additions(nestegg * ctx, nestegg_packet * pkt) +{ + int r; + uint64_t id, size, data_size; + int64_t block_additions_end, block_more_end; + void * data; + int has_data; + struct block_additional * block_additional; + uint64_t add_id; + + assert(pkt != NULL); + assert(pkt->block_additional == NULL); + + r = ne_peek_element(ctx, &id, &size); + if (r != 1) + return r; + + if (id != ID_BLOCK_ADDITIONS) + return 1; + + /* This makes ne_read_element read the next element instead of returning + information about the already "peeked" one. */ + ctx->last_valid = 0; + + block_additions_end = ne_io_tell(ctx->io) + size; + + while (ne_io_tell(ctx->io) < block_additions_end) { + add_id = 1; + data = NULL; + has_data = 0; + r = ne_read_element(ctx, &id, &size); + if (r != 1) + return -1; + + if (id != ID_BLOCK_MORE) { + /* We don't know what this element is, so skip over it */ + if (id != ID_VOID && id != ID_CRC32) + ctx->log(ctx, NESTEGG_LOG_DEBUG, + "unknown element %llx in BlockAdditions", id); + ne_io_read_skip(ctx->io, size); + continue; + } + + block_more_end = ne_io_tell(ctx->io) + size; + + while (ne_io_tell(ctx->io) < block_more_end) { + r = ne_read_element(ctx, &id, &size); + if (r != 1) { + free(data); + return r; + } + + if (id == ID_BLOCK_ADD_ID) { + r = ne_read_uint(ctx->io, &add_id, size); + if (r != 1) { + free(data); + return r; + } + + if (add_id == 0) { + ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed BlockAddId 0 used"); + free(data); + return -1; + } + } else if (id == ID_BLOCK_ADDITIONAL) { + if (has_data) { + /* BlockAdditional is supposed to only occur once in a + BlockMore. */ + ctx->log(ctx, NESTEGG_LOG_ERROR, + "Multiple BlockAdditional elements in a BlockMore"); + free(data); + return -1; + } + + has_data = 1; + data_size = size; + if (size != 0) { + data = ne_alloc(size); + r = ne_io_read(ctx->io, data, size); + if (r != 1) { + free(data); + return r; + } + } + } else { + /* We don't know what this element is, so skip over it */ + if (id != ID_VOID && id != ID_CRC32) + ctx->log(ctx, NESTEGG_LOG_DEBUG, + "unknown element %llx in BlockMore", id); + ne_io_read_skip(ctx->io, size); + } + } + + if (has_data == 0) { + ctx->log(ctx, NESTEGG_LOG_ERROR, + "No BlockAdditional element in a BlockMore"); + return -1; + } + + block_additional = ne_alloc(sizeof(*block_additional)); + block_additional->next = pkt->block_additional; + block_additional->id = add_id; + block_additional->data = data; + block_additional->length = data_size; + pkt->block_additional = block_additional; + } + + return 1; +} + static uint64_t ne_buf_read_id(unsigned char const * p, size_t length) { @@ -1362,8 +1649,33 @@ ne_find_seek_for_id(struct ebml_list_node * seek_head, uint64_t id) return NULL; } +static struct cue_track_positions * +ne_find_cue_position_for_track(nestegg * ctx, struct ebml_list_node * node, unsigned int track) +{ + struct cue_track_positions * pos = NULL; + uint64_t track_number; + unsigned int t; + + while (node) { + assert(node->id == ID_CUE_TRACK_POSITIONS); + pos = node->data; + if (ne_get_uint(pos->track, &track_number) != 0) + return NULL; + + if (ne_map_track_number_to_index(ctx, track_number, &t) != 0) + return NULL; + + if (t == track) + return pos; + + node = node->next; + } + + return NULL; +} + static struct cue_point * -ne_find_cue_point_for_tstamp(struct ebml_list_node * cue_point, uint64_t scale, uint64_t tstamp) +ne_find_cue_point_for_tstamp(nestegg * ctx, struct ebml_list_node * cue_point, unsigned int track, uint64_t scale, uint64_t tstamp) { uint64_t time; struct cue_point * c, * prev = NULL; @@ -1378,7 +1690,9 @@ ne_find_cue_point_for_tstamp(struct ebml_list_node * cue_point, uint64_t scale, if (ne_get_uint(c->time, &time) == 0 && time * scale > tstamp) break; - prev = cue_point->data; + if (ne_find_cue_position_for_track(ctx, c->cue_track_positions.head, track) != NULL) + prev = c; + cue_point = cue_point->next; } @@ -1400,8 +1714,187 @@ ne_null_log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ... return; } +static int +ne_init_cue_points(nestegg * ctx, int64_t max_offset) +{ + int r; + struct ebml_list_node * node = ctx->segment.cues.cue_point.head; + struct seek * found; + uint64_t seek_pos, id; + struct saved_state state; + + /* If there are no cues loaded, check for cues element in the seek head + and load it. */ + if (!node) { + found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); + if (!found) + return -1; + + if (ne_get_uint(found->position, &seek_pos) != 0) + return -1; + + /* Save old parser state. */ + r = ne_ctx_save(ctx, &state); + if (r != 0) + return -1; + + /* Seek and set up parser state for segment-level element (Cues). */ + r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); + if (r != 0) + return -1; + ctx->last_valid = 0; + + r = ne_read_element(ctx, &id, NULL); + if (r != 1) + return -1; + + if (id != ID_CUES) + return -1; + + ctx->ancestor = NULL; + if (ne_ctx_push(ctx, ne_top_level_elements, ctx) < 0) + return -1; + if (ne_ctx_push(ctx, ne_segment_elements, &ctx->segment) < 0) + return -1; + if (ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues) < 0) + return -1; + /* parser will run until end of cues element. */ + ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements"); + r = ne_parse(ctx, ne_cues_elements, max_offset); + while (ctx->ancestor) + ne_ctx_pop(ctx); + + /* Reset parser state to original state and seek back to old position. */ + if (ne_ctx_restore(ctx, &state) != 0) + return -1; + + if (r < 0) + return -1; + + node = ctx->segment.cues.cue_point.head; + if (!node) + return -1; + } + + return 0; +} + +/* Three functions that implement the nestegg_io interface, operating on a + * sniff_buffer. */ +struct sniff_buffer { + unsigned char const * buffer; + size_t length; + int64_t offset; +}; + +static int +ne_buffer_read(void * buffer, size_t length, void * user_data) +{ + struct sniff_buffer * sb = user_data; + + int rv = 1; + size_t available = sb->length - sb->offset; + + if (available < length) + return 0; + + memcpy(buffer, sb->buffer + sb->offset, length); + sb->offset += length; + + return rv; +} + +static int +ne_buffer_seek(int64_t offset, int whence, void * user_data) +{ + struct sniff_buffer * sb = user_data; + int64_t o = sb->offset; + + switch(whence) { + case NESTEGG_SEEK_SET: + o = offset; + break; + case NESTEGG_SEEK_CUR: + o += offset; + break; + case NESTEGG_SEEK_END: + o = sb->length + offset; + break; + } + + if (o < 0 || o > (int64_t) sb->length) + return -1; + + sb->offset = o; + return 0; +} + +static int64_t +ne_buffer_tell(void * user_data) +{ + struct sniff_buffer * sb = user_data; + return sb->offset; +} + +static int +ne_match_webm(nestegg_io io, int64_t max_offset) +{ + int r; + uint64_t id; + char * doctype; + nestegg * ctx; + + if (!(io.read && io.seek && io.tell)) + return -1; + + ctx = ne_alloc(sizeof(*ctx)); + if (!ctx) + return -1; + + ctx->io = ne_alloc(sizeof(*ctx->io)); + if (!ctx->io) { + nestegg_destroy(ctx); + return -1; + } + *ctx->io = io; + ctx->alloc_pool = ne_pool_init(); + if (!ctx->alloc_pool) { + nestegg_destroy(ctx); + return -1; + } + ctx->log = ne_null_log_callback; + + r = ne_peek_element(ctx, &id, NULL); + if (r != 1) { + nestegg_destroy(ctx); + return 0; + } + + if (id != ID_EBML) { + nestegg_destroy(ctx); + return 0; + } + + ne_ctx_push(ctx, ne_top_level_elements, ctx); + + /* we don't check the return value of ne_parse, that might fail because + max_offset is not on a valid element end point. We only want to check + the EBML ID and that the doctype is "webm". */ + ne_parse(ctx, NULL, max_offset); + + if (ne_get_string(ctx->ebml.doctype, &doctype) != 0 || + strcmp(doctype, "webm") != 0) { + nestegg_destroy(ctx); + return 0; + } + + nestegg_destroy(ctx); + + return 1; +} + int -nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback) +nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset) { int r; uint64_t id, version, docversion; @@ -1413,11 +1906,21 @@ nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback) return -1; ctx = ne_alloc(sizeof(*ctx)); + if (!ctx) + return -1; ctx->io = ne_alloc(sizeof(*ctx->io)); + if (!ctx->io) { + nestegg_destroy(ctx); + return -1; + } *ctx->io = io; ctx->log = callback; ctx->alloc_pool = ne_pool_init(); + if (!ctx->alloc_pool) { + nestegg_destroy(ctx); + return -1; + } if (!ctx->log) ctx->log = ne_null_log_callback; @@ -1437,7 +1940,7 @@ nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback) ne_ctx_push(ctx, ne_top_level_elements, ctx); - r = ne_parse(ctx, NULL); + r = ne_parse(ctx, NULL, max_offset); if (r != 1) { nestegg_destroy(ctx); @@ -1523,101 +2026,134 @@ nestegg_track_count(nestegg * ctx, unsigned int * tracks) } int -nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp) +nestegg_get_cue_point(nestegg * ctx, unsigned int cluster_num, int64_t max_offset, + int64_t * start_pos, int64_t * end_pos, uint64_t * tstamp) { - int r; + int range_obtained = 0; + unsigned int cluster_count = 0; 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; + uint64_t seek_pos, track_number, tc_scale, time; + struct ebml_list_node * cues_node = ctx->segment.cues.cue_point.head; + struct ebml_list_node * cue_pos_node = NULL; + unsigned int track = 0, track_count = 0, track_index; - /* If there are no cues loaded, check for cues element in the seek head - and load it. */ - if (!node) { - found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); - if (!found) - return -1; + if (!start_pos || !end_pos || !tstamp) + return -1; - if (ne_get_uint(found->position, &seek_pos) != 0) - return -1; + /* Initialise return values */ + *start_pos = -1; + *end_pos = -1; + *tstamp = 0; - /* Save old parser state. */ - r = ne_ctx_save(ctx, &state); - if (r != 0) - return -1; - - /* Seek and set up parser state for segment-level element (Cues). */ - r = ne_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 = ne_read_element(ctx, &id, NULL); - if (r != 1) - return -1; - - if (id != ID_CUES) - return -1; - - ctx->ancestor = NULL; - ne_ctx_push(ctx, ne_top_level_elements, ctx); - ne_ctx_push(ctx, ne_segment_elements, &ctx->segment); - ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues); - /* Parser will run until end of cues element. */ - ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements"); - r = ne_parse(ctx, ne_cues_elements); - while (ctx->ancestor) - ne_ctx_pop(ctx); - - /* Reset parser state to original state and seek back to old position. */ - if (ne_ctx_restore(ctx, &state) != 0) - return -1; - - if (r < 0) + if (!cues_node) { + ne_init_cue_points(ctx, max_offset); + cues_node = ctx->segment.cues.cue_point.head; + /* Verify cues have been added to context. */ + if (!cues_node) return -1; } + nestegg_track_count(ctx, &track_count); + tc_scale = ne_get_timecode_scale(ctx); - cue_point = ne_find_cue_point_for_tstamp(ctx->segment.cues.cue_point.head, tc_scale, tstamp); - if (!cue_point) - return -1; + while (cues_node && !range_obtained) { + assert(cues_node->id == ID_CUE_POINT); + cue_point = cues_node->data; + cue_pos_node = cue_point->cue_track_positions.head; + while (cue_pos_node) { + assert(cue_pos_node->id == ID_CUE_TRACK_POSITIONS); + pos = cue_pos_node->data; + for (track = 0; track < track_count; track++) { + if (ne_get_uint(pos->track, &track_number) != 0) + return -1; - node = cue_point->cue_track_positions.head; + if (ne_map_track_number_to_index(ctx, track_number, &track_index) != 0) + return -1; - seek_pos = 0; - - while (node) { - assert(node->id == ID_CUE_TRACK_POSITIONS); - pos = node->data; - if (ne_get_uint(pos->track, &t) == 0 && t - 1 == track) { - if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) - return -1; - break; + if (track_index == track) { + if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) + return -1; + if (cluster_count == cluster_num) { + *start_pos = ctx->segment_offset+seek_pos; + if (ne_get_uint(cue_point->time, &time) != 0) + return -1; + *tstamp = time * tc_scale; + } else if (cluster_count == cluster_num+1) { + *end_pos = (ctx->segment_offset+seek_pos)-1; + range_obtained = 1; + break; + } + cluster_count++; + } + } + cue_pos_node = cue_pos_node->next; } - node = node->next; + cues_node = cues_node->next; } + return 0; +} + +int +nestegg_offset_seek(nestegg * ctx, uint64_t offset) +{ + int r; + /* Seek and set up parser state for segment-level element (Cluster). */ - r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); + r = ne_io_seek(ctx->io, offset, NESTEGG_SEEK_SET); if (r != 0) return -1; - ctx->last_id = 0; - ctx->last_size = 0; + ctx->last_valid = 0; while (ctx->ancestor) ne_ctx_pop(ctx); ne_ctx_push(ctx, ne_top_level_elements, ctx); ne_ctx_push(ctx, ne_segment_elements, &ctx->segment); + ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cluster elements"); - r = ne_parse(ctx, NULL); + r = ne_parse(ctx, NULL, -1); if (r != 1) return -1; + 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; + uint64_t seek_pos, tc_scale; + + /* If there are no cues loaded, check for cues element in the seek head + and load it. */ + if (!ctx->segment.cues.cue_point.head) { + r = ne_init_cue_points(ctx, -1); + if (r != 0) + return -1; + } + + tc_scale = ne_get_timecode_scale(ctx); + + cue_point = ne_find_cue_point_for_tstamp(ctx, ctx->segment.cues.cue_point.head, + track, tc_scale, tstamp); + if (!cue_point) + return -1; + + pos = ne_find_cue_position_for_track(ctx, cue_point->cue_track_positions.head, track); + if (pos == NULL) + return -1; + + if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) + return -1; + + /* Seek and set up parser state for segment-level element (Cluster). */ + r = nestegg_offset_seek(ctx, ctx->segment_offset + seek_pos); + if (!ne_is_suspend_element(ctx->last_id)) return -1; @@ -1662,9 +2198,15 @@ nestegg_track_codec_id(nestegg * ctx, unsigned int track) if (strcmp(codec_id, TRACK_ID_VP8) == 0) return NESTEGG_CODEC_VP8; + if (strcmp(codec_id, TRACK_ID_VP9) == 0) + return NESTEGG_CODEC_VP9; + if (strcmp(codec_id, TRACK_ID_VORBIS) == 0) return NESTEGG_CODEC_VORBIS; + if (strcmp(codec_id, TRACK_ID_OPUS) == 0) + return NESTEGG_CODEC_OPUS; + return -1; } @@ -1717,34 +2259,40 @@ nestegg_track_codec_data(nestegg * ctx, unsigned int track, unsigned int item, if (!entry) return -1; - if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) + if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS + && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS) return -1; if (ne_get_binary(entry->codec_private, &codec_private) != 0) return -1; - p = codec_private.data; - count = *p++ + 1; + if (nestegg_track_codec_id(ctx, track) == NESTEGG_CODEC_VORBIS) { + p = codec_private.data; + count = *p++ + 1; - if (count > 3) - return -1; + if (count > 3) + return -1; - i = 0; - total = 0; - while (--count) { - sizes[i] = ne_xiph_lace_value(&p); - total += sizes[i]; - i += 1; + i = 0; + total = 0; + while (--count) { + sizes[i] = ne_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]; + } else { + *data = codec_private.data; + *length = codec_private.length; } - 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; } @@ -1771,6 +2319,10 @@ nestegg_track_video_params(nestegg * ctx, unsigned int track, value == NESTEGG_VIDEO_STEREO_RIGHT_LEFT) params->stereo_mode = value; + value = 0; + ne_get_uint(entry->video.alpha_mode, &value); + params->alpha_mode = value; + if (ne_get_uint(entry->video.pixel_width, &value) != 0) return -1; params->width = value; @@ -1833,6 +2385,32 @@ nestegg_track_audio_params(nestegg * ctx, unsigned int track, ne_get_uint(entry->audio.bit_depth, &value); params->depth = value; + value = 0; + ne_get_uint(entry->codec_delay, &value); + params->codec_delay = value; + + value = 0; + ne_get_uint(entry->seek_preroll, &value); + params->seek_preroll = value; + + return 0; +} + +int +nestegg_track_default_duration(nestegg * ctx, unsigned int track, + uint64_t * duration) +{ + struct track_entry * entry; + uint64_t value; + + entry = ne_find_track_entry(ctx, track); + if (!entry) + return -1; + + if (ne_get_uint(entry->default_duration, &value) != 0) + return -1; + *duration = value; + return 0; } @@ -1858,10 +2436,25 @@ nestegg_read_packet(nestegg * ctx, nestegg_packet ** pkt) /* The only DESC_FLAG_SUSPEND fields are Blocks and SimpleBlocks, which we handle directly. */ r = ne_read_block(ctx, id, size, pkt); + if (r != 1) + return r; + + r = ne_read_block_duration(ctx, *pkt); + if (r != 1) + return r; + + r = ne_read_discard_padding(ctx, *pkt); + if (r != 1) + return r; + + r = ne_read_block_additions(ctx, *pkt); + if (r != 1) + return r; + return r; } - r = ne_parse(ctx, NULL); + r = ne_parse(ctx, NULL, -1); if (r != 1) return r; } @@ -1873,6 +2466,7 @@ void nestegg_free_packet(nestegg_packet * pkt) { struct frame * frame; + struct block_additional * block_additional; while (pkt->frame) { frame = pkt->frame; @@ -1881,6 +2475,13 @@ nestegg_free_packet(nestegg_packet * pkt) free(frame); } + while (pkt->block_additional) { + block_additional = pkt->block_additional; + pkt->block_additional = block_additional->next; + free(block_additional->data); + free(block_additional); + } + free(pkt); } @@ -1898,6 +2499,20 @@ nestegg_packet_tstamp(nestegg_packet * pkt, uint64_t * tstamp) return 0; } +int +nestegg_packet_duration(nestegg_packet * pkt, uint64_t * duration) +{ + *duration = pkt->duration; + return 0; +} + +int +nestegg_packet_discard_padding(nestegg_packet * pkt, int64_t * discard_padding) +{ + *discard_padding = pkt->discard_padding; + return 0; +} + int nestegg_packet_count(nestegg_packet * pkt, unsigned int * count) { @@ -1935,3 +2550,54 @@ nestegg_packet_data(nestegg_packet * pkt, unsigned int item, return -1; } + +int +nestegg_packet_additional_data(nestegg_packet * pkt, unsigned int id, + unsigned char ** data, size_t * length) +{ + struct block_additional * a = pkt->block_additional; + + *data = NULL; + *length = 0; + + while (a) { + if (a->id == id) { + *data = a->data; + *length = a->length; + return 0; + } + a = a->next; + } + + return -1; +} + +int +nestegg_has_cues(nestegg * ctx) +{ + return ctx->segment.cues.cue_point.head || + ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); +} + +int +nestegg_sniff(unsigned char const * buffer, size_t length) +{ + nestegg_io io; + struct sniff_buffer user_data; + + user_data.buffer = buffer; + user_data.length = length; + user_data.offset = 0; + + io.read = ne_buffer_read; + io.seek = ne_buffer_seek; + io.tell = ne_buffer_tell; + io.userdata = &user_data; + return ne_match_webm(io, length); +} + +void +nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t)) +{ + halloc_allocator = realloc_func; +} diff --git a/src/nestegg.h b/src/nestegg.h index 12ed7f3..589ef4d 100644 --- a/src/nestegg.h +++ b/src/nestegg.h @@ -4,12 +4,12 @@ * 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 +#if !defined(NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79) +#define NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 #include -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { #endif @@ -67,6 +67,8 @@ extern "C" { #define NESTEGG_CODEC_VP8 0 /**< Track uses Google On2 VP8 codec. */ #define NESTEGG_CODEC_VORBIS 1 /**< Track uses Xiph Vorbis codec. */ +#define NESTEGG_CODEC_VP9 2 /**< Track uses Google On2 VP9 codec. */ +#define NESTEGG_CODEC_OPUS 3 /**< Track uses Xiph Opus codec. */ #define NESTEGG_VIDEO_MONO 0 /**< Track is mono video. */ #define NESTEGG_VIDEO_STEREO_LEFT_RIGHT 1 /**< Track is side-by-side stereo video. Left first. */ @@ -131,6 +133,7 @@ typedef struct { 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. */ + unsigned int alpha_mode; /**< 1 if an additional opacity stream is available, otherwise 0. */ } nestegg_video_params; /** Parameters specific to an audio track. */ @@ -138,6 +141,8 @@ typedef struct { double rate; /**< Sampling rate in Hz. */ unsigned int channels; /**< Number of audio channels. */ unsigned int depth; /**< Bits per sample. */ + uint64_t codec_delay; /**< Nanoseconds that must be discarded from the start. */ + uint64_t seek_preroll;/**< Nanoseconds that must be discarded after a seek. */ } nestegg_audio_params; /** Logging callback function pointer. */ @@ -149,9 +154,10 @@ typedef void (* nestegg_log)(nestegg * context, unsigned int severity, char cons @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. + @param max_offset Optional maximum offset to be read. Set -1 to ignore. @retval 0 Success. @retval -1 Error. */ -int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback); +int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset); /** Destroy a nestegg context and free associated memory. @param context #nestegg context to be freed. @see nestegg_init */ @@ -180,6 +186,29 @@ int nestegg_tstamp_scale(nestegg * context, uint64_t * scale); @retval -1 Error. */ int nestegg_track_count(nestegg * context, unsigned int * tracks); +/** Query the start and end offset for a particular cluster. + @param context Stream context initialized by #nestegg_init. + @param cluster_num Zero-based cluster number; order they appear in cues. + @param max_offset Optional maximum offset to be read. Set -1 to ignore. + @param start_pos Starting offset of the cluster. -1 means non-existant. + @param end_pos Starting offset of the cluster. -1 means non-existant or + final cluster. + @param tstamp Starting timestamp of the cluster. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_get_cue_point(nestegg * context, unsigned int cluster_num, + int64_t max_offset, int64_t * start_pos, + int64_t * end_pos, uint64_t * tstamp); + +/** Seek to @a offset. Stream will seek directly to offset. + Should be used to seek to the start of a resync point, i.e. cluster; the + parser will not be able to understand other offsets. + @param context Stream context initialized by #nestegg_init. + @param offset Absolute offset in bytes. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_offset_seek(nestegg * context, uint64_t offset); + /** 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. @@ -248,6 +277,16 @@ int nestegg_track_video_params(nestegg * context, unsigned int track, int nestegg_track_audio_params(nestegg * context, unsigned int track, nestegg_audio_params * params); +/** Query the default frame duration for @a track. For a video track, this + is typically the inverse of the video frame rate. + @param context Stream context initialized by #nestegg_init. + @param track Zero based track number. + @param duration Storage for the default duration in nanoseconds. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_track_default_duration(nestegg * context, unsigned int track, + uint64_t * duration); + /** 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 @@ -277,6 +316,13 @@ int nestegg_packet_track(nestegg_packet * packet, unsigned int * track); @retval -1 Error. */ int nestegg_packet_tstamp(nestegg_packet * packet, uint64_t * tstamp); +/** Query the duration in nanoseconds of @a packet. + @param packet Packet initialized by #nestegg_read_packet. + @param duration Storage for the queried duration in nanoseconds. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_duration(nestegg_packet * packet, uint64_t * duration); + /** 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. @@ -295,7 +341,50 @@ int nestegg_packet_count(nestegg_packet * packet, unsigned int * count); int nestegg_packet_data(nestegg_packet * packet, unsigned int item, unsigned char ** data, size_t * length); -#ifdef __cplusplus +/** Get a pointer to additional data with identifier @a id of additional packet + data. If @a id isn't present in the packet, returns -1. + @param packet Packet initialized by #nestegg_read_packet. + @param id Codec specific identifer. For VP8, use 1 to get a VP8 encoded + frame containing an alpha channel in its Y plane. + @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_additional_data(nestegg_packet * packet, unsigned int id, + unsigned char ** data, size_t * length); + +/** Returns discard_padding for given packet + @param packet Packet initialized by #nestegg_read_packet. + @param discard_padding pointer to store discard padding in. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_discard_padding(nestegg_packet * packet, + int64_t * discard_padding); + +/** Query the presence of cues. + @param context Stream context initialized by #nestegg_init. + @retval 0 The media has no cues. + @retval 1 The media has cues. */ +int nestegg_has_cues(nestegg * context); + +/** + * Try to determine if the buffer looks like the beginning of a WebM file. + * + * @param buffer A buffer containing the beginning of a media file. + * @param length The size of the buffer. + * @retval 0 The file is not a WebM file. + * @retval 1 The file is a WebM file. */ +int nestegg_sniff(unsigned char const * buffer, size_t length); + +/** + * Set the underlying allocation function for library allocations. + * + * @param realloc_func The desired function. + */ +void nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t)); + +#if defined(__cplusplus) } #endif diff --git a/src/oxframe.c b/src/oxframe.c index 643a47a..56d25a5 100644 --- a/src/oxframe.c +++ b/src/oxframe.c @@ -41,8 +41,6 @@ #include "vpx/vpx_decoder.h" #include "vpx/vp8dx.h" -#define interface (&vpx_codec_vp8_dx_algo) - #include #include @@ -361,9 +359,11 @@ int extract_frame_webm(oxstate *state) { } io.userdata = fp; ctx = NULL; - r = nestegg_init(&ctx, io, log_callback); - if (r != 0) - return EXIT_FAILURE; + r = nestegg_init(&ctx, io, log_callback, -1); + if (r != 0) { + fprintf (stderr, "nestegg_init failed %d\n", r); + exit(EXIT_FAILURE); + } nestegg_track_count(ctx, &tracks); nestegg_duration(ctx, &duration); @@ -373,15 +373,25 @@ int extract_frame_webm(oxstate *state) { 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) { + if (type == NESTEGG_TRACK_VIDEO) { + /* Initialize codec */ + if (codec_id == NESTEGG_CODEC_VP8) { + if(vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, NULL, flags)) + die_codec(&codec, "Failed to initialize decoder"); +#ifdef HAVE_VP9 + } else if (codec_id == NESTEGG_CODEC_VP9) { + if(vpx_codec_dec_init(&codec, &vpx_codec_vp9_dx_algo, NULL, flags)) + die_codec(&codec, "Failed to initialize decoder"); +#endif + } else { + fprintf(stderr, "unsupported video codec %d\n", codec_id); + continue; + } nestegg_track_video_params(ctx, i, &vparams); if(!nestegg_track_seek(ctx, i, seek_tstamp)) {