| /* |
| * Copyright © 2010 Mozilla Foundation |
| * |
| * This program is made available under an ISC-style license. See the |
| * accompanying file LICENSE for details. |
| */ |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "nestegg/halloc/halloc.h" |
| #include "nestegg/include/nestegg/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) |
| |
| /* Block Header Flags */ |
| #define BLOCK_FLAGS_LACING 6 |
| |
| /* Lacing Constants */ |
| #define LACING_NONE 0 |
| #define LACING_XIPH 1 |
| #define LACING_FIXED 2 |
| #define LACING_EBML 3 |
| |
| /* Track Types */ |
| #define TRACK_TYPE_VIDEO 1 |
| #define TRACK_TYPE_AUDIO 2 |
| |
| /* Track IDs */ |
| #define TRACK_ID_VP8 "V_VP8" |
| #define TRACK_ID_VORBIS "A_VORBIS" |
| |
| 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, ne_ ## FIELD ## _elements, \ |
| sizeof(struct FIELD), 0 } |
| #define E_SINGLE_MASTER_O(ID, TYPE, STRUCT, FIELD) \ |
| { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_OFFSET, ne_ ## FIELD ## _elements, 0, \ |
| offsetof(STRUCT, FIELD ## _offset) } |
| #define E_SINGLE_MASTER(ID, TYPE, STRUCT, FIELD) \ |
| { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, ne_ ## 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 ne_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 ne_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 ne_seek_head_elements[] = { |
| E_MASTER(ID_SEEK, TYPE_MASTER, struct seek_head, seek), |
| E_LAST |
| }; |
| |
| static struct ebml_element_desc ne_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 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_LAST |
| }; |
| |
| static struct ebml_element_desc ne_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 ne_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 ne_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 ne_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 ne_tracks_elements[] = { |
| E_MASTER(ID_TRACK_ENTRY, TYPE_MASTER, struct tracks, track_entry), |
| E_LAST |
| }; |
| |
| static struct ebml_element_desc ne_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 ne_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 ne_cues_elements[] = { |
| E_MASTER(ID_CUE_POINT, TYPE_MASTER, struct cues, cue_point), |
| E_LAST |
| }; |
| |
| static struct ebml_element_desc ne_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 ne_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 * |
| ne_pool_init(void) |
| { |
| struct pool_ctx * pool; |
| |
| pool = h_malloc(sizeof(*pool)); |
| if (!pool) |
| abort(); |
| return pool; |
| } |
| |
| static void |
| ne_pool_destroy(struct pool_ctx * pool) |
| { |
| h_free(pool); |
| } |
| |
| static void * |
| ne_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; |
| } |
| |
| static void * |
| ne_alloc(size_t size) |
| { |
| void * p; |
| |
| p = calloc(1, size); |
| if (!p) |
| abort(); |
| return p; |
| } |
| |
| static int |
| ne_io_read(nestegg_io * io, void * buffer, size_t length) |
| { |
| return io->read(buffer, length, io->userdata); |
| } |
| |
| static int |
| ne_io_seek(nestegg_io * io, int64_t offset, int whence) |
| { |
| return io->seek(offset, whence, io->userdata); |
| } |
| |
| static int |
| ne_io_read_skip(nestegg_io * io, size_t length) |
| { |
| size_t get; |
| unsigned char buf[8192]; |
| int r = 1; |
| |
| while (length > 0) { |
| get = length < sizeof(buf) ? length : sizeof(buf); |
| r = ne_io_read(io, buf, get); |
| if (r != 1) |
| break; |
| length -= get; |
| } |
| |
| return r; |
| } |
| |
| static int64_t |
| ne_io_tell(nestegg_io * io) |
| { |
| return io->tell(io->userdata); |
| } |
| |
| static int |
| ne_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 = ne_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 = ne_io_read(io, &b, 1); |
| if (r != 1) |
| return r; |
| *value <<= 8; |
| *value |= b; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ne_read_id(nestegg_io * io, uint64_t * value, uint64_t * length) |
| { |
| return ne_bare_read_vint(io, value, length, MASK_NONE); |
| } |
| |
| static int |
| ne_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length) |
| { |
| return ne_bare_read_vint(io, value, length, MASK_FIRST_BIT); |
| } |
| |
| static int |
| ne_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 = ne_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 |
| ne_read_uint(nestegg_io * io, uint64_t * val, uint64_t length) |
| { |
| unsigned char b; |
| int r; |
| |
| if (length == 0 || length > 8) |
| return -1; |
| r = ne_io_read(io, &b, 1); |
| if (r != 1) |
| return r; |
| *val = b; |
| while (--length) { |
| r = ne_io_read(io, &b, 1); |
| if (r != 1) |
| return r; |
| *val <<= 8; |
| *val |= b; |
| } |
| return 1; |
| } |
| |
| static int |
| ne_read_int(nestegg_io * io, int64_t * val, uint64_t length) |
| { |
| int r; |
| uint64_t uval, base; |
| |
| r = ne_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 |
| ne_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 = ne_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 |
| ne_read_string(nestegg * ctx, char ** val, uint64_t length) |
| { |
| char * str; |
| int r; |
| |
| if (length == 0 || length > LIMIT_STRING) |
| return -1; |
| str = ne_pool_alloc(length + 1, ctx->alloc_pool); |
| r = ne_io_read(ctx->io, (unsigned char *) str, length); |
| if (r != 1) |
| return r; |
| str[length] = '\0'; |
| *val = str; |
| return 1; |
| } |
| |
| static int |
| 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); |
| val->length = length; |
| return ne_io_read(ctx->io, val->data, length); |
| } |
| |
| static int |
| ne_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 |
| ne_get_float(struct ebml_type type, double * value) |
| { |
| if (!type.read) |
| return -1; |
| |
| assert(type.type == TYPE_FLOAT); |
| |
| *value = type.v.f; |
| |
| return 0; |
| } |
| |
| static int |
| ne_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 |
| ne_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 |
| ne_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 * |
| ne_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 |
| ne_ctx_push(nestegg * ctx, struct ebml_element_desc * ancestor, void * data) |
| { |
| struct list_node * item; |
| |
| item = ne_alloc(sizeof(*item)); |
| item->previous = ctx->ancestor; |
| item->node = ancestor; |
| item->data = data; |
| ctx->ancestor = item; |
| } |
| |
| static void |
| ne_ctx_pop(nestegg * ctx) |
| { |
| struct list_node * item; |
| |
| item = ctx->ancestor; |
| ctx->ancestor = item->previous; |
| free(item); |
| } |
| |
| static int |
| ne_ctx_save(nestegg * ctx, struct saved_state * s) |
| { |
| s->stream_offset = ne_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 |
| ne_ctx_restore(nestegg * ctx, struct saved_state * s) |
| { |
| int r; |
| |
| r = ne_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 |
| ne_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 = ne_read_id(ctx->io, &ctx->last_id, NULL); |
| if (r != 1) |
| return r; |
| |
| r = ne_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 |
| ne_read_element(nestegg * ctx, uint64_t * id, uint64_t * size) |
| { |
| int r; |
| |
| r = ne_peek_element(ctx, id, size); |
| if (r != 1) |
| return r; |
| |
| ctx->last_id = 0; |
| ctx->last_size = 0; |
| |
| return 1; |
| } |
| |
| static void |
| ne_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 = ne_pool_alloc(sizeof(*node), ctx->alloc_pool); |
| node->id = desc->id; |
| node->data = ne_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); |
| |
| ne_ctx_push(ctx, desc->children, node->data); |
| } |
| |
| static void |
| ne_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); |
| |
| ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); |
| } |
| |
| static int |
| ne_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) { |
| ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) already read, skipping", |
| desc->id, desc->name); |
| return 0; |
| } |
| |
| 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 = ne_read_uint(ctx->io, &storage->v.u, length); |
| break; |
| case TYPE_FLOAT: |
| r = ne_read_float(ctx->io, &storage->v.f, length); |
| break; |
| case TYPE_INT: |
| r = ne_read_int(ctx->io, &storage->v.i, length); |
| break; |
| case TYPE_STRING: |
| r = ne_read_string(ctx, &storage->v.s, length); |
| break; |
| case TYPE_BINARY: |
| r = ne_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 |
| ne_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 */ |
| |
| if (!ctx->ancestor) |
| return -1; |
| |
| for (;;) { |
| r = ne_peek_element(ctx, &id, &size); |
| if (r != 1) |
| break; |
| |
| element = ne_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 = ne_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 = ne_io_tell(ctx->io); |
| if (*data_offset < 0) { |
| r = -1; |
| break; |
| } |
| } |
| |
| if (element->type == TYPE_MASTER) { |
| if (element->flags & DESC_FLAG_MULTI) |
| ne_read_master(ctx, element); |
| else |
| ne_read_single_master(ctx, element); |
| continue; |
| } else { |
| r = ne_read_simple(ctx, element, size); |
| if (r < 0) |
| break; |
| } |
| } else if (ne_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; |
| } |
| ne_ctx_pop(ctx); |
| } else { |
| r = ne_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 = ne_io_read_skip(ctx->io, size); |
| if (r != 1) |
| break; |
| } |
| } |
| |
| if (r != 1) |
| while (ctx->ancestor) |
| ne_ctx_pop(ctx); |
| |
| return r; |
| } |
| |
| static uint64_t |
| ne_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 |
| ne_read_xiph_lace_value(nestegg_io * io, uint64_t * value, size_t * consumed) |
| { |
| int r; |
| uint64_t lace; |
| |
| r = ne_read_uint(io, &lace, 1); |
| if (r != 1) |
| return r; |
| *consumed += 1; |
| |
| *value = lace; |
| while (lace == 255) { |
| r = ne_read_uint(io, &lace, 1); |
| if (r != 1) |
| return r; |
| *consumed += 1; |
| *value += lace; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ne_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 = ne_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 |
| ne_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 = ne_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 = ne_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 |
| ne_get_timecode_scale(nestegg * ctx) |
| { |
| uint64_t scale; |
| |
| if (ne_get_uint(ctx->segment.info.timecode_scale, &scale) != 0) |
| scale = 1000000; |
| |
| return scale; |
| } |
| |
| static struct track_entry * |
| ne_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 |
| ne_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 = ne_read_vint(ctx->io, &track, &length); |
| if (r != 1) |
| return r; |
| |
| if (track == 0 || track > ctx->track_count) |
| return -1; |
| |
| consumed += length; |
| |
| r = ne_read_int(ctx->io, &timecode, 2); |
| if (r != 1) |
| return r; |
| |
| consumed += 2; |
| |
| r = ne_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 & BLOCK_FLAGS_LACING) >> 1; |
| |
| switch (lacing) { |
| case LACING_NONE: |
| frames = 1; |
| break; |
| case LACING_XIPH: |
| case LACING_FIXED: |
| case LACING_EBML: |
| r = ne_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 = ne_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 = ne_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 = ne_find_track_entry(ctx, track - 1); |
| if (!entry) |
| return -1; |
| |
| track_scale = 1.0; |
| |
| tc_scale = ne_get_timecode_scale(ctx); |
| |
| assert(ctx->segment.cluster.tail->id == ID_CLUSTER); |
| cluster = ctx->segment.cluster.tail->data; |
| if (ne_get_uint(cluster->timecode, &cluster_tc) != 0) |
| return -1; |
| |
| abs_timecode = timecode + cluster_tc; |
| if (abs_timecode < 0) |
| return -1; |
| |
| pkt = ne_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 = ne_alloc(sizeof(*f)); |
| f->data = ne_alloc(frame_sizes[i]); |
| f->length = frame_sizes[i]; |
| r = ne_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 |
| ne_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 * |
| ne_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 (ne_get_binary(s->id, &binary_id) == 0 && |
| ne_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 * |
| ne_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 (ne_get_uint(c->time, &time) == 0 && time * scale > tstamp) |
| break; |
| |
| prev = cue_point->data; |
| cue_point = cue_point->next; |
| } |
| |
| return prev; |
| } |
| |
| static int |
| ne_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 |
| ne_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, docversion; |
| struct ebml_list_node * track; |
| char * doctype; |
| nestegg * ctx = NULL; |
| |
| if (!(io.read && io.seek && io.tell)) |
| return -1; |
| |
| ctx = ne_alloc(sizeof(*ctx)); |
| |
| ctx->io = ne_alloc(sizeof(*ctx->io)); |
| *ctx->io = io; |
| ctx->log = callback; |
| ctx->alloc_pool = ne_pool_init(); |
| |
| if (!ctx->log) |
| ctx->log = ne_null_log_callback; |
| |
| r = ne_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); |
| |
| ne_ctx_push(ctx, ne_top_level_elements, ctx); |
| |
| r = ne_parse(ctx, NULL); |
| |
| if (r != 1) { |
| nestegg_destroy(ctx); |
| return -1; |
| } |
| |
| if (ne_get_uint(ctx->ebml.ebml_read_version, &version) != 0) |
| version = 1; |
| if (version != 1) { |
| nestegg_destroy(ctx); |
| return -1; |
| } |
| |
| if (ne_get_string(ctx->ebml.doctype, &doctype) != 0) |
| doctype = "matroska"; |
| if (strcmp(doctype, "webm") != 0) { |
| nestegg_destroy(ctx); |
| return -1; |
| } |
| |
| if (ne_get_uint(ctx->ebml.doctype_read_version, &docversion) != 0) |
| docversion = 1; |
| if (docversion < 1 || docversion > 2) { |
| nestegg_destroy(ctx); |
| return -1; |
| } |
| |
| if (!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) |
| ne_ctx_pop(ctx); |
| ne_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 (ne_get_float(ctx->segment.info.duration, &unscaled_duration) != 0) |
| return -1; |
| |
| tc_scale = ne_get_timecode_scale(ctx); |
| |
| *duration = (uint64_t) (unscaled_duration * tc_scale); |
| return 0; |
| } |
| |
| int |
| nestegg_tstamp_scale(nestegg * ctx, uint64_t * scale) |
| { |
| *scale = ne_get_timecode_scale(ctx); |
| 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 = 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_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) |
| return -1; |
| } |
| |
| 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; |
| |
| node = cue_point->cue_track_positions.head; |
| |
| 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; |
| } |
| node = node->next; |
| } |
| |
| /* 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); |
| if (r != 0) |
| return -1; |
| ctx->last_id = 0; |
| ctx->last_size = 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); |
| if (r != 1) |
| return -1; |
| |
| if (!ne_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 = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (ne_get_uint(entry->type, &type) != 0) |
| return -1; |
| |
| if (type & TRACK_TYPE_VIDEO) |
| return NESTEGG_TRACK_VIDEO; |
| |
| if (type & TRACK_TYPE_AUDIO) |
| return NESTEGG_TRACK_AUDIO; |
| |
| return -1; |
| } |
| |
| int |
| nestegg_track_codec_id(nestegg * ctx, unsigned int track) |
| { |
| char * codec_id; |
| struct track_entry * entry; |
| |
| entry = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (ne_get_string(entry->codec_id, &codec_id) != 0) |
| return -1; |
| |
| if (strcmp(codec_id, TRACK_ID_VP8) == 0) |
| return NESTEGG_CODEC_VP8; |
| |
| if (strcmp(codec_id, TRACK_ID_VORBIS) == 0) |
| return NESTEGG_CODEC_VORBIS; |
| |
| 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 = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) |
| return -1; |
| |
| if (ne_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 = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) |
| return -1; |
| |
| if (ne_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] = 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]; |
| |
| 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 = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_VIDEO) |
| return -1; |
| |
| if (ne_get_uint(entry->video.pixel_width, &value) != 0) |
| return -1; |
| params->width = value; |
| |
| if (ne_get_uint(entry->video.pixel_height, &value) != 0) |
| return -1; |
| params->height = value; |
| |
| value = 0; |
| ne_get_uint(entry->video.pixel_crop_bottom, &value); |
| params->crop_bottom = value; |
| |
| value = 0; |
| ne_get_uint(entry->video.pixel_crop_top, &value); |
| params->crop_top = value; |
| |
| value = 0; |
| ne_get_uint(entry->video.pixel_crop_left, &value); |
| params->crop_left = value; |
| |
| value = 0; |
| ne_get_uint(entry->video.pixel_crop_right, &value); |
| params->crop_right = value; |
| |
| value = params->width; |
| ne_get_uint(entry->video.display_width, &value); |
| params->display_width = value; |
| |
| value = params->height; |
| ne_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 = ne_find_track_entry(ctx, track); |
| if (!entry) |
| return -1; |
| |
| if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_AUDIO) |
| return -1; |
| |
| params->rate = 8000; |
| ne_get_float(entry->audio.sampling_frequency, ¶ms->rate); |
| |
| value = 1; |
| ne_get_uint(entry->audio.channels, &value); |
| params->channels = value; |
| |
| value = 16; |
| ne_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 = ne_peek_element(ctx, &id, &size); |
| if (r != 1) |
| return r; |
| |
| /* any suspend fields must be handled here */ |
| if (ne_is_suspend_element(id)) { |
| r = ne_read_element(ctx, &id, &size); |
| if (r != 1) |
| return r; |
| |
| /* the only suspend fields are blocks and simple blocks, which we |
| handle directly. */ |
| r = ne_read_block(ctx, id, size, pkt); |
| return r; |
| } |
| |
| r = ne_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; |
| } |