michael@0: /* michael@0: * Copyright © 2010 Mozilla Foundation michael@0: * michael@0: * This program is made available under an ISC-style license. See the michael@0: * accompanying file LICENSE for details. michael@0: */ michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "halloc.h" michael@0: #include "nestegg/nestegg.h" michael@0: michael@0: /* EBML Elements */ michael@0: #define ID_EBML 0x1a45dfa3 michael@0: #define ID_EBML_VERSION 0x4286 michael@0: #define ID_EBML_READ_VERSION 0x42f7 michael@0: #define ID_EBML_MAX_ID_LENGTH 0x42f2 michael@0: #define ID_EBML_MAX_SIZE_LENGTH 0x42f3 michael@0: #define ID_DOCTYPE 0x4282 michael@0: #define ID_DOCTYPE_VERSION 0x4287 michael@0: #define ID_DOCTYPE_READ_VERSION 0x4285 michael@0: michael@0: /* Global Elements */ michael@0: #define ID_VOID 0xec michael@0: #define ID_CRC32 0xbf michael@0: michael@0: /* WebM Elements */ michael@0: #define ID_SEGMENT 0x18538067 michael@0: michael@0: /* Seek Head Elements */ michael@0: #define ID_SEEK_HEAD 0x114d9b74 michael@0: #define ID_SEEK 0x4dbb michael@0: #define ID_SEEK_ID 0x53ab michael@0: #define ID_SEEK_POSITION 0x53ac michael@0: michael@0: /* Info Elements */ michael@0: #define ID_INFO 0x1549a966 michael@0: #define ID_TIMECODE_SCALE 0x2ad7b1 michael@0: #define ID_DURATION 0x4489 michael@0: michael@0: /* Cluster Elements */ michael@0: #define ID_CLUSTER 0x1f43b675 michael@0: #define ID_TIMECODE 0xe7 michael@0: #define ID_BLOCK_GROUP 0xa0 michael@0: #define ID_SIMPLE_BLOCK 0xa3 michael@0: michael@0: /* BlockGroup Elements */ michael@0: #define ID_BLOCK 0xa1 michael@0: #define ID_BLOCK_DURATION 0x9b michael@0: #define ID_REFERENCE_BLOCK 0xfb michael@0: #define ID_DISCARD_PADDING 0x75a2 michael@0: michael@0: /* Tracks Elements */ michael@0: #define ID_TRACKS 0x1654ae6b michael@0: #define ID_TRACK_ENTRY 0xae michael@0: #define ID_TRACK_NUMBER 0xd7 michael@0: #define ID_TRACK_UID 0x73c5 michael@0: #define ID_TRACK_TYPE 0x83 michael@0: #define ID_FLAG_ENABLED 0xb9 michael@0: #define ID_FLAG_DEFAULT 0x88 michael@0: #define ID_FLAG_LACING 0x9c michael@0: #define ID_TRACK_TIMECODE_SCALE 0x23314f michael@0: #define ID_LANGUAGE 0x22b59c michael@0: #define ID_CODEC_ID 0x86 michael@0: #define ID_CODEC_PRIVATE 0x63a2 michael@0: #define ID_CODEC_DELAY 0x56aa michael@0: #define ID_SEEK_PREROLL 0x56bb michael@0: #define ID_DEFAULT_DURATION 0x23e383 michael@0: michael@0: /* Video Elements */ michael@0: #define ID_VIDEO 0xe0 michael@0: #define ID_STEREO_MODE 0x53b8 michael@0: #define ID_PIXEL_WIDTH 0xb0 michael@0: #define ID_PIXEL_HEIGHT 0xba michael@0: #define ID_PIXEL_CROP_BOTTOM 0x54aa michael@0: #define ID_PIXEL_CROP_TOP 0x54bb michael@0: #define ID_PIXEL_CROP_LEFT 0x54cc michael@0: #define ID_PIXEL_CROP_RIGHT 0x54dd michael@0: #define ID_DISPLAY_WIDTH 0x54b0 michael@0: #define ID_DISPLAY_HEIGHT 0x54ba michael@0: michael@0: /* Audio Elements */ michael@0: #define ID_AUDIO 0xe1 michael@0: #define ID_SAMPLING_FREQUENCY 0xb5 michael@0: #define ID_CHANNELS 0x9f michael@0: #define ID_BIT_DEPTH 0x6264 michael@0: michael@0: /* Cues Elements */ michael@0: #define ID_CUES 0x1c53bb6b michael@0: #define ID_CUE_POINT 0xbb michael@0: #define ID_CUE_TIME 0xb3 michael@0: #define ID_CUE_TRACK_POSITIONS 0xb7 michael@0: #define ID_CUE_TRACK 0xf7 michael@0: #define ID_CUE_CLUSTER_POSITION 0xf1 michael@0: #define ID_CUE_BLOCK_NUMBER 0x5378 michael@0: michael@0: /* EBML Types */ michael@0: enum ebml_type_enum { michael@0: TYPE_UNKNOWN, michael@0: TYPE_MASTER, michael@0: TYPE_UINT, michael@0: TYPE_FLOAT, michael@0: TYPE_INT, michael@0: TYPE_STRING, michael@0: TYPE_BINARY michael@0: }; michael@0: michael@0: #define LIMIT_STRING (1 << 20) michael@0: #define LIMIT_BINARY (1 << 24) michael@0: #define LIMIT_BLOCK (1 << 30) michael@0: #define LIMIT_FRAME (1 << 28) michael@0: michael@0: /* Field Flags */ michael@0: #define DESC_FLAG_NONE 0 michael@0: #define DESC_FLAG_MULTI (1 << 0) michael@0: #define DESC_FLAG_SUSPEND (1 << 1) michael@0: #define DESC_FLAG_OFFSET (1 << 2) michael@0: michael@0: /* Block Header Flags */ michael@0: #define BLOCK_FLAGS_LACING 6 michael@0: michael@0: /* Lacing Constants */ michael@0: #define LACING_NONE 0 michael@0: #define LACING_XIPH 1 michael@0: #define LACING_FIXED 2 michael@0: #define LACING_EBML 3 michael@0: michael@0: /* Track Types */ michael@0: #define TRACK_TYPE_VIDEO 1 michael@0: #define TRACK_TYPE_AUDIO 2 michael@0: michael@0: /* Track IDs */ michael@0: #define TRACK_ID_VP8 "V_VP8" michael@0: #define TRACK_ID_VP9 "V_VP9" michael@0: #define TRACK_ID_VORBIS "A_VORBIS" michael@0: #define TRACK_ID_OPUS "A_OPUS" michael@0: michael@0: enum vint_mask { michael@0: MASK_NONE, michael@0: MASK_FIRST_BIT michael@0: }; michael@0: michael@0: struct ebml_binary { michael@0: unsigned char * data; michael@0: size_t length; michael@0: }; michael@0: michael@0: struct ebml_list_node { michael@0: struct ebml_list_node * next; michael@0: uint64_t id; michael@0: void * data; michael@0: }; michael@0: michael@0: struct ebml_list { michael@0: struct ebml_list_node * head; michael@0: struct ebml_list_node * tail; michael@0: }; michael@0: michael@0: struct ebml_type { michael@0: union ebml_value { michael@0: uint64_t u; michael@0: double f; michael@0: int64_t i; michael@0: char * s; michael@0: struct ebml_binary b; michael@0: } v; michael@0: enum ebml_type_enum type; michael@0: int read; michael@0: }; michael@0: michael@0: /* EBML Definitions */ michael@0: struct ebml { michael@0: struct ebml_type ebml_version; michael@0: struct ebml_type ebml_read_version; michael@0: struct ebml_type ebml_max_id_length; michael@0: struct ebml_type ebml_max_size_length; michael@0: struct ebml_type doctype; michael@0: struct ebml_type doctype_version; michael@0: struct ebml_type doctype_read_version; michael@0: }; michael@0: michael@0: /* Matroksa Definitions */ michael@0: struct seek { michael@0: struct ebml_type id; michael@0: struct ebml_type position; michael@0: }; michael@0: michael@0: struct seek_head { michael@0: struct ebml_list seek; michael@0: }; michael@0: michael@0: struct info { michael@0: struct ebml_type timecode_scale; michael@0: struct ebml_type duration; michael@0: }; michael@0: michael@0: struct block_group { michael@0: struct ebml_type duration; michael@0: struct ebml_type reference_block; michael@0: struct ebml_type discard_padding; michael@0: }; michael@0: michael@0: struct cluster { michael@0: struct ebml_type timecode; michael@0: struct ebml_list block_group; michael@0: }; michael@0: michael@0: struct video { michael@0: struct ebml_type stereo_mode; michael@0: struct ebml_type pixel_width; michael@0: struct ebml_type pixel_height; michael@0: struct ebml_type pixel_crop_bottom; michael@0: struct ebml_type pixel_crop_top; michael@0: struct ebml_type pixel_crop_left; michael@0: struct ebml_type pixel_crop_right; michael@0: struct ebml_type display_width; michael@0: struct ebml_type display_height; michael@0: }; michael@0: michael@0: struct audio { michael@0: struct ebml_type sampling_frequency; michael@0: struct ebml_type channels; michael@0: struct ebml_type bit_depth; michael@0: }; michael@0: michael@0: struct track_entry { michael@0: struct ebml_type number; michael@0: struct ebml_type uid; michael@0: struct ebml_type type; michael@0: struct ebml_type flag_enabled; michael@0: struct ebml_type flag_default; michael@0: struct ebml_type flag_lacing; michael@0: struct ebml_type track_timecode_scale; michael@0: struct ebml_type language; michael@0: struct ebml_type codec_id; michael@0: struct ebml_type codec_private; michael@0: struct ebml_type codec_delay; michael@0: struct ebml_type seek_preroll; michael@0: struct ebml_type default_duration; michael@0: struct video video; michael@0: struct audio audio; michael@0: }; michael@0: michael@0: struct tracks { michael@0: struct ebml_list track_entry; michael@0: }; michael@0: michael@0: struct cue_track_positions { michael@0: struct ebml_type track; michael@0: struct ebml_type cluster_position; michael@0: struct ebml_type block_number; michael@0: }; michael@0: michael@0: struct cue_point { michael@0: struct ebml_type time; michael@0: struct ebml_list cue_track_positions; michael@0: }; michael@0: michael@0: struct cues { michael@0: struct ebml_list cue_point; michael@0: }; michael@0: michael@0: struct segment { michael@0: struct ebml_list seek_head; michael@0: struct info info; michael@0: struct ebml_list cluster; michael@0: struct tracks tracks; michael@0: struct cues cues; michael@0: }; michael@0: michael@0: /* Misc. */ michael@0: struct pool_ctx { michael@0: char dummy; michael@0: }; michael@0: michael@0: struct list_node { michael@0: struct list_node * previous; michael@0: struct ebml_element_desc * node; michael@0: unsigned char * data; michael@0: }; michael@0: michael@0: struct saved_state { michael@0: int64_t stream_offset; michael@0: struct list_node * ancestor; michael@0: uint64_t last_id; michael@0: uint64_t last_size; michael@0: int last_valid; michael@0: }; michael@0: michael@0: struct frame { michael@0: unsigned char * data; michael@0: size_t length; michael@0: struct frame * next; michael@0: }; michael@0: michael@0: /* Public (opaque) Structures */ michael@0: struct nestegg { michael@0: nestegg_io * io; michael@0: nestegg_log log; michael@0: struct pool_ctx * alloc_pool; michael@0: uint64_t last_id; michael@0: uint64_t last_size; michael@0: int last_valid; michael@0: struct list_node * ancestor; michael@0: struct ebml ebml; michael@0: struct segment segment; michael@0: int64_t segment_offset; michael@0: unsigned int track_count; michael@0: }; michael@0: michael@0: struct nestegg_packet { michael@0: uint64_t track; michael@0: uint64_t timecode; michael@0: uint64_t duration; michael@0: struct frame * frame; michael@0: int64_t discard_padding; michael@0: }; michael@0: michael@0: /* Element Descriptor */ michael@0: struct ebml_element_desc { michael@0: char const * name; michael@0: uint64_t id; michael@0: enum ebml_type_enum type; michael@0: size_t offset; michael@0: unsigned int flags; michael@0: struct ebml_element_desc * children; michael@0: size_t size; michael@0: size_t data_offset; michael@0: }; michael@0: michael@0: #define E_FIELD(ID, TYPE, STRUCT, FIELD) \ michael@0: { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, NULL, 0, 0 } michael@0: #define E_MASTER(ID, TYPE, STRUCT, FIELD) \ michael@0: { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_MULTI, ne_ ## FIELD ## _elements, \ michael@0: sizeof(struct FIELD), 0 } michael@0: #define E_SINGLE_MASTER_O(ID, TYPE, STRUCT, FIELD) \ michael@0: { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_OFFSET, ne_ ## FIELD ## _elements, 0, \ michael@0: offsetof(STRUCT, FIELD ## _offset) } michael@0: #define E_SINGLE_MASTER(ID, TYPE, STRUCT, FIELD) \ michael@0: { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, ne_ ## FIELD ## _elements, 0, 0 } michael@0: #define E_SUSPEND(ID, TYPE) \ michael@0: { #ID, ID, TYPE, 0, DESC_FLAG_SUSPEND, NULL, 0, 0 } michael@0: #define E_LAST \ michael@0: { NULL, 0, 0, 0, DESC_FLAG_NONE, NULL, 0, 0 } michael@0: michael@0: /* EBML Element Lists */ michael@0: static struct ebml_element_desc ne_ebml_elements[] = { michael@0: E_FIELD(ID_EBML_VERSION, TYPE_UINT, struct ebml, ebml_version), michael@0: E_FIELD(ID_EBML_READ_VERSION, TYPE_UINT, struct ebml, ebml_read_version), michael@0: E_FIELD(ID_EBML_MAX_ID_LENGTH, TYPE_UINT, struct ebml, ebml_max_id_length), michael@0: E_FIELD(ID_EBML_MAX_SIZE_LENGTH, TYPE_UINT, struct ebml, ebml_max_size_length), michael@0: E_FIELD(ID_DOCTYPE, TYPE_STRING, struct ebml, doctype), michael@0: E_FIELD(ID_DOCTYPE_VERSION, TYPE_UINT, struct ebml, doctype_version), michael@0: E_FIELD(ID_DOCTYPE_READ_VERSION, TYPE_UINT, struct ebml, doctype_read_version), michael@0: E_LAST michael@0: }; michael@0: michael@0: /* WebM Element Lists */ michael@0: static struct ebml_element_desc ne_seek_elements[] = { michael@0: E_FIELD(ID_SEEK_ID, TYPE_BINARY, struct seek, id), michael@0: E_FIELD(ID_SEEK_POSITION, TYPE_UINT, struct seek, position), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_seek_head_elements[] = { michael@0: E_MASTER(ID_SEEK, TYPE_MASTER, struct seek_head, seek), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_info_elements[] = { michael@0: E_FIELD(ID_TIMECODE_SCALE, TYPE_UINT, struct info, timecode_scale), michael@0: E_FIELD(ID_DURATION, TYPE_FLOAT, struct info, duration), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_block_group_elements[] = { michael@0: E_SUSPEND(ID_BLOCK, TYPE_BINARY), michael@0: E_FIELD(ID_BLOCK_DURATION, TYPE_UINT, struct block_group, duration), michael@0: E_FIELD(ID_REFERENCE_BLOCK, TYPE_INT, struct block_group, reference_block), michael@0: E_FIELD(ID_DISCARD_PADDING, TYPE_INT, struct block_group, discard_padding), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_cluster_elements[] = { michael@0: E_FIELD(ID_TIMECODE, TYPE_UINT, struct cluster, timecode), michael@0: E_MASTER(ID_BLOCK_GROUP, TYPE_MASTER, struct cluster, block_group), michael@0: E_SUSPEND(ID_SIMPLE_BLOCK, TYPE_BINARY), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_video_elements[] = { michael@0: E_FIELD(ID_STEREO_MODE, TYPE_UINT, struct video, stereo_mode), michael@0: E_FIELD(ID_PIXEL_WIDTH, TYPE_UINT, struct video, pixel_width), michael@0: E_FIELD(ID_PIXEL_HEIGHT, TYPE_UINT, struct video, pixel_height), michael@0: E_FIELD(ID_PIXEL_CROP_BOTTOM, TYPE_UINT, struct video, pixel_crop_bottom), michael@0: E_FIELD(ID_PIXEL_CROP_TOP, TYPE_UINT, struct video, pixel_crop_top), michael@0: E_FIELD(ID_PIXEL_CROP_LEFT, TYPE_UINT, struct video, pixel_crop_left), michael@0: E_FIELD(ID_PIXEL_CROP_RIGHT, TYPE_UINT, struct video, pixel_crop_right), michael@0: E_FIELD(ID_DISPLAY_WIDTH, TYPE_UINT, struct video, display_width), michael@0: E_FIELD(ID_DISPLAY_HEIGHT, TYPE_UINT, struct video, display_height), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_audio_elements[] = { michael@0: E_FIELD(ID_SAMPLING_FREQUENCY, TYPE_FLOAT, struct audio, sampling_frequency), michael@0: E_FIELD(ID_CHANNELS, TYPE_UINT, struct audio, channels), michael@0: E_FIELD(ID_BIT_DEPTH, TYPE_UINT, struct audio, bit_depth), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_track_entry_elements[] = { michael@0: E_FIELD(ID_TRACK_NUMBER, TYPE_UINT, struct track_entry, number), michael@0: E_FIELD(ID_TRACK_UID, TYPE_UINT, struct track_entry, uid), michael@0: E_FIELD(ID_TRACK_TYPE, TYPE_UINT, struct track_entry, type), michael@0: E_FIELD(ID_FLAG_ENABLED, TYPE_UINT, struct track_entry, flag_enabled), michael@0: E_FIELD(ID_FLAG_DEFAULT, TYPE_UINT, struct track_entry, flag_default), michael@0: E_FIELD(ID_FLAG_LACING, TYPE_UINT, struct track_entry, flag_lacing), michael@0: E_FIELD(ID_TRACK_TIMECODE_SCALE, TYPE_FLOAT, struct track_entry, track_timecode_scale), michael@0: E_FIELD(ID_LANGUAGE, TYPE_STRING, struct track_entry, language), michael@0: E_FIELD(ID_CODEC_ID, TYPE_STRING, struct track_entry, codec_id), michael@0: E_FIELD(ID_CODEC_PRIVATE, TYPE_BINARY, struct track_entry, codec_private), michael@0: E_FIELD(ID_CODEC_DELAY, TYPE_UINT, struct track_entry, codec_delay), michael@0: E_FIELD(ID_SEEK_PREROLL, TYPE_UINT, struct track_entry, seek_preroll), michael@0: E_FIELD(ID_DEFAULT_DURATION, TYPE_UINT, struct track_entry, default_duration), michael@0: E_SINGLE_MASTER(ID_VIDEO, TYPE_MASTER, struct track_entry, video), michael@0: E_SINGLE_MASTER(ID_AUDIO, TYPE_MASTER, struct track_entry, audio), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_tracks_elements[] = { michael@0: E_MASTER(ID_TRACK_ENTRY, TYPE_MASTER, struct tracks, track_entry), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_cue_track_positions_elements[] = { michael@0: E_FIELD(ID_CUE_TRACK, TYPE_UINT, struct cue_track_positions, track), michael@0: E_FIELD(ID_CUE_CLUSTER_POSITION, TYPE_UINT, struct cue_track_positions, cluster_position), michael@0: E_FIELD(ID_CUE_BLOCK_NUMBER, TYPE_UINT, struct cue_track_positions, block_number), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_cue_point_elements[] = { michael@0: E_FIELD(ID_CUE_TIME, TYPE_UINT, struct cue_point, time), michael@0: E_MASTER(ID_CUE_TRACK_POSITIONS, TYPE_MASTER, struct cue_point, cue_track_positions), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_cues_elements[] = { michael@0: E_MASTER(ID_CUE_POINT, TYPE_MASTER, struct cues, cue_point), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_segment_elements[] = { michael@0: E_MASTER(ID_SEEK_HEAD, TYPE_MASTER, struct segment, seek_head), michael@0: E_SINGLE_MASTER(ID_INFO, TYPE_MASTER, struct segment, info), michael@0: E_MASTER(ID_CLUSTER, TYPE_MASTER, struct segment, cluster), michael@0: E_SINGLE_MASTER(ID_TRACKS, TYPE_MASTER, struct segment, tracks), michael@0: E_SINGLE_MASTER(ID_CUES, TYPE_MASTER, struct segment, cues), michael@0: E_LAST michael@0: }; michael@0: michael@0: static struct ebml_element_desc ne_top_level_elements[] = { michael@0: E_SINGLE_MASTER(ID_EBML, TYPE_MASTER, nestegg, ebml), michael@0: E_SINGLE_MASTER_O(ID_SEGMENT, TYPE_MASTER, nestegg, segment), michael@0: E_LAST michael@0: }; michael@0: michael@0: #undef E_FIELD michael@0: #undef E_MASTER michael@0: #undef E_SINGLE_MASTER_O michael@0: #undef E_SINGLE_MASTER michael@0: #undef E_SUSPEND michael@0: #undef E_LAST michael@0: michael@0: static struct pool_ctx * michael@0: ne_pool_init(void) michael@0: { michael@0: return h_malloc(sizeof(struct pool_ctx)); michael@0: } michael@0: michael@0: static void michael@0: ne_pool_destroy(struct pool_ctx * pool) michael@0: { michael@0: h_free(pool); michael@0: } michael@0: michael@0: static void * michael@0: ne_pool_alloc(size_t size, struct pool_ctx * pool) michael@0: { michael@0: void * p; michael@0: michael@0: p = h_malloc(size); michael@0: if (!p) michael@0: return NULL; michael@0: hattach(p, pool); michael@0: memset(p, 0, size); michael@0: return p; michael@0: } michael@0: michael@0: static void * michael@0: ne_alloc(size_t size) michael@0: { michael@0: return calloc(1, size); michael@0: } michael@0: michael@0: static int michael@0: ne_io_read(nestegg_io * io, void * buffer, size_t length) michael@0: { michael@0: return io->read(buffer, length, io->userdata); michael@0: } michael@0: michael@0: static int michael@0: ne_io_seek(nestegg_io * io, int64_t offset, int whence) michael@0: { michael@0: return io->seek(offset, whence, io->userdata); michael@0: } michael@0: michael@0: static int michael@0: ne_io_read_skip(nestegg_io * io, size_t length) michael@0: { michael@0: size_t get; michael@0: unsigned char buf[8192]; michael@0: int r = 1; michael@0: michael@0: while (length > 0) { michael@0: get = length < sizeof(buf) ? length : sizeof(buf); michael@0: r = ne_io_read(io, buf, get); michael@0: if (r != 1) michael@0: break; michael@0: length -= get; michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: static int64_t michael@0: ne_io_tell(nestegg_io * io) michael@0: { michael@0: return io->tell(io->userdata); michael@0: } michael@0: michael@0: static int michael@0: ne_bare_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length, enum vint_mask maskflag) michael@0: { michael@0: int r; michael@0: unsigned char b; michael@0: size_t maxlen = 8; michael@0: unsigned int count = 1, mask = 1 << 7; michael@0: michael@0: r = ne_io_read(io, &b, 1); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: while (count < maxlen) { michael@0: if ((b & mask) != 0) michael@0: break; michael@0: mask >>= 1; michael@0: count += 1; michael@0: } michael@0: michael@0: if (length) michael@0: *length = count; michael@0: *value = b; michael@0: michael@0: if (maskflag == MASK_FIRST_BIT) michael@0: *value = b & ~mask; michael@0: michael@0: while (--count) { michael@0: r = ne_io_read(io, &b, 1); michael@0: if (r != 1) michael@0: return r; michael@0: *value <<= 8; michael@0: *value |= b; michael@0: } michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_id(nestegg_io * io, uint64_t * value, uint64_t * length) michael@0: { michael@0: return ne_bare_read_vint(io, value, length, MASK_NONE); michael@0: } michael@0: michael@0: static int michael@0: ne_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length) michael@0: { michael@0: return ne_bare_read_vint(io, value, length, MASK_FIRST_BIT); michael@0: } michael@0: michael@0: static int michael@0: ne_read_svint(nestegg_io * io, int64_t * value, uint64_t * length) michael@0: { michael@0: int r; michael@0: uint64_t uvalue; michael@0: uint64_t ulength; michael@0: int64_t svint_subtr[] = { michael@0: 0x3f, 0x1fff, michael@0: 0xfffff, 0x7ffffff, michael@0: 0x3ffffffffLL, 0x1ffffffffffLL, michael@0: 0xffffffffffffLL, 0x7fffffffffffffLL michael@0: }; michael@0: michael@0: r = ne_bare_read_vint(io, &uvalue, &ulength, MASK_FIRST_BIT); michael@0: if (r != 1) michael@0: return r; michael@0: *value = uvalue - svint_subtr[ulength - 1]; michael@0: if (length) michael@0: *length = ulength; michael@0: return r; michael@0: } michael@0: michael@0: static int michael@0: ne_read_uint(nestegg_io * io, uint64_t * val, uint64_t length) michael@0: { michael@0: unsigned char b; michael@0: int r; michael@0: michael@0: if (length == 0 || length > 8) michael@0: return -1; michael@0: r = ne_io_read(io, &b, 1); michael@0: if (r != 1) michael@0: return r; michael@0: *val = b; michael@0: while (--length) { michael@0: r = ne_io_read(io, &b, 1); michael@0: if (r != 1) michael@0: return r; michael@0: *val <<= 8; michael@0: *val |= b; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_int(nestegg_io * io, int64_t * val, uint64_t length) michael@0: { michael@0: int r; michael@0: uint64_t uval, base; michael@0: michael@0: r = ne_read_uint(io, &uval, length); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: if (length < sizeof(int64_t)) { michael@0: base = 1; michael@0: base <<= length * 8 - 1; michael@0: if (uval >= base) { michael@0: base = 1; michael@0: base <<= length * 8; michael@0: } else { michael@0: base = 0; michael@0: } michael@0: *val = uval - base; michael@0: } else { michael@0: *val = (int64_t) uval; michael@0: } michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_float(nestegg_io * io, double * val, uint64_t length) michael@0: { michael@0: union { michael@0: uint64_t u; michael@0: float f; michael@0: double d; michael@0: } value; michael@0: int r; michael@0: michael@0: /* Length == 10 not implemented. */ michael@0: if (length != 4 && length != 8) michael@0: return -1; michael@0: r = ne_read_uint(io, &value.u, length); michael@0: if (r != 1) michael@0: return r; michael@0: if (length == 4) michael@0: *val = value.f; michael@0: else michael@0: *val = value.d; michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_string(nestegg * ctx, char ** val, uint64_t length) michael@0: { michael@0: char * str; michael@0: int r; michael@0: michael@0: if (length == 0 || length > LIMIT_STRING) michael@0: return -1; michael@0: str = ne_pool_alloc(length + 1, ctx->alloc_pool); michael@0: if (!str) michael@0: return -1; michael@0: r = ne_io_read(ctx->io, (unsigned char *) str, length); michael@0: if (r != 1) michael@0: return r; michael@0: str[length] = '\0'; michael@0: *val = str; michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_binary(nestegg * ctx, struct ebml_binary * val, uint64_t length) michael@0: { michael@0: if (length == 0 || length > LIMIT_BINARY) michael@0: return -1; michael@0: val->data = ne_pool_alloc(length, ctx->alloc_pool); michael@0: if (!val->data) michael@0: return -1; michael@0: val->length = length; michael@0: return ne_io_read(ctx->io, val->data, length); michael@0: } michael@0: michael@0: static int michael@0: ne_get_uint(struct ebml_type type, uint64_t * value) michael@0: { michael@0: if (!type.read) michael@0: return -1; michael@0: michael@0: assert(type.type == TYPE_UINT); michael@0: michael@0: *value = type.v.u; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_get_float(struct ebml_type type, double * value) michael@0: { michael@0: if (!type.read) michael@0: return -1; michael@0: michael@0: assert(type.type == TYPE_FLOAT); michael@0: michael@0: *value = type.v.f; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_get_string(struct ebml_type type, char ** value) michael@0: { michael@0: if (!type.read) michael@0: return -1; michael@0: michael@0: assert(type.type == TYPE_STRING); michael@0: michael@0: *value = type.v.s; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_get_binary(struct ebml_type type, struct ebml_binary * value) michael@0: { michael@0: if (!type.read) michael@0: return -1; michael@0: michael@0: assert(type.type == TYPE_BINARY); michael@0: michael@0: *value = type.v.b; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_is_ancestor_element(uint64_t id, struct list_node * ancestor) michael@0: { michael@0: struct ebml_element_desc * element; michael@0: michael@0: for (; ancestor; ancestor = ancestor->previous) michael@0: for (element = ancestor->node; element->id; ++element) michael@0: if (element->id == id) michael@0: return 1; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static struct ebml_element_desc * michael@0: ne_find_element(uint64_t id, struct ebml_element_desc * elements) michael@0: { michael@0: struct ebml_element_desc * element; michael@0: michael@0: for (element = elements; element->id; ++element) michael@0: if (element->id == id) michael@0: return element; michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: static int michael@0: ne_ctx_push(nestegg * ctx, struct ebml_element_desc * ancestor, void * data) michael@0: { michael@0: struct list_node * item; michael@0: michael@0: item = ne_alloc(sizeof(*item)); michael@0: if (!item) michael@0: return -1; michael@0: item->previous = ctx->ancestor; michael@0: item->node = ancestor; michael@0: item->data = data; michael@0: ctx->ancestor = item; michael@0: return 0; michael@0: } michael@0: michael@0: static void michael@0: ne_ctx_pop(nestegg * ctx) michael@0: { michael@0: struct list_node * item; michael@0: michael@0: item = ctx->ancestor; michael@0: ctx->ancestor = item->previous; michael@0: free(item); michael@0: } michael@0: michael@0: static int michael@0: ne_ctx_save(nestegg * ctx, struct saved_state * s) michael@0: { michael@0: s->stream_offset = ne_io_tell(ctx->io); michael@0: if (s->stream_offset < 0) michael@0: return -1; michael@0: s->ancestor = ctx->ancestor; michael@0: s->last_id = ctx->last_id; michael@0: s->last_size = ctx->last_size; michael@0: s->last_valid = ctx->last_valid; michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_ctx_restore(nestegg * ctx, struct saved_state * s) michael@0: { michael@0: int r; michael@0: michael@0: r = ne_io_seek(ctx->io, s->stream_offset, NESTEGG_SEEK_SET); michael@0: if (r != 0) michael@0: return -1; michael@0: ctx->ancestor = s->ancestor; michael@0: ctx->last_id = s->last_id; michael@0: ctx->last_size = s->last_size; michael@0: ctx->last_valid = s->last_valid; michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_peek_element(nestegg * ctx, uint64_t * id, uint64_t * size) michael@0: { michael@0: int r; michael@0: michael@0: if (ctx->last_valid) { michael@0: if (id) michael@0: *id = ctx->last_id; michael@0: if (size) michael@0: *size = ctx->last_size; michael@0: return 1; michael@0: } michael@0: michael@0: r = ne_read_id(ctx->io, &ctx->last_id, NULL); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: r = ne_read_vint(ctx->io, &ctx->last_size, NULL); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: if (id) michael@0: *id = ctx->last_id; michael@0: if (size) michael@0: *size = ctx->last_size; michael@0: michael@0: ctx->last_valid = 1; michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_element(nestegg * ctx, uint64_t * id, uint64_t * size) michael@0: { michael@0: int r; michael@0: michael@0: r = ne_peek_element(ctx, id, size); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: ctx->last_valid = 0; michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_master(nestegg * ctx, struct ebml_element_desc * desc) michael@0: { michael@0: struct ebml_list * list; michael@0: struct ebml_list_node * node, * oldtail; michael@0: michael@0: assert(desc->type == TYPE_MASTER && desc->flags & DESC_FLAG_MULTI); michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "multi master element %llx (%s)", michael@0: desc->id, desc->name); michael@0: michael@0: list = (struct ebml_list *) (ctx->ancestor->data + desc->offset); michael@0: michael@0: node = ne_pool_alloc(sizeof(*node), ctx->alloc_pool); michael@0: if (!node) michael@0: return -1; michael@0: node->id = desc->id; michael@0: node->data = ne_pool_alloc(desc->size, ctx->alloc_pool); michael@0: if (!node->data) michael@0: return -1; michael@0: michael@0: oldtail = list->tail; michael@0: if (oldtail) michael@0: oldtail->next = node; michael@0: list->tail = node; michael@0: if (!list->head) michael@0: list->head = node; michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p", node->data); michael@0: michael@0: if (ne_ctx_push(ctx, desc->children, node->data) < 0) michael@0: return -1; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: ne_read_single_master(nestegg * ctx, struct ebml_element_desc * desc) michael@0: { michael@0: assert(desc->type == TYPE_MASTER && !(desc->flags & DESC_FLAG_MULTI)); michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "single master element %llx (%s)", michael@0: desc->id, desc->name); michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p (%u)", michael@0: ctx->ancestor->data + desc->offset, desc->offset); michael@0: michael@0: return ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); michael@0: } michael@0: michael@0: static int michael@0: ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) michael@0: { michael@0: struct ebml_type * storage; michael@0: int r; michael@0: michael@0: storage = (struct ebml_type *) (ctx->ancestor->data + desc->offset); michael@0: michael@0: if (storage->read) { michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) already read, skipping", michael@0: desc->id, desc->name); michael@0: return 0; michael@0: } michael@0: michael@0: storage->type = desc->type; michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) -> %p (%u)", michael@0: desc->id, desc->name, storage, desc->offset); michael@0: michael@0: switch (desc->type) { michael@0: case TYPE_UINT: michael@0: r = ne_read_uint(ctx->io, &storage->v.u, length); michael@0: break; michael@0: case TYPE_FLOAT: michael@0: r = ne_read_float(ctx->io, &storage->v.f, length); michael@0: break; michael@0: case TYPE_INT: michael@0: r = ne_read_int(ctx->io, &storage->v.i, length); michael@0: break; michael@0: case TYPE_STRING: michael@0: r = ne_read_string(ctx, &storage->v.s, length); michael@0: break; michael@0: case TYPE_BINARY: michael@0: r = ne_read_binary(ctx, &storage->v.b, length); michael@0: break; michael@0: case TYPE_MASTER: michael@0: case TYPE_UNKNOWN: michael@0: assert(0); michael@0: r = 0; michael@0: break; michael@0: } michael@0: michael@0: if (r == 1) michael@0: storage->read = 1; michael@0: michael@0: return r; michael@0: } michael@0: michael@0: static int michael@0: ne_parse(nestegg * ctx, struct ebml_element_desc * top_level, int64_t max_offset) michael@0: { michael@0: int r; michael@0: int64_t * data_offset; michael@0: uint64_t id, size, peeked_id; michael@0: struct ebml_element_desc * element; michael@0: michael@0: if (!ctx->ancestor) michael@0: return -1; michael@0: michael@0: for (;;) { michael@0: if (max_offset > 0 && ne_io_tell(ctx->io) >= max_offset) { michael@0: /* Reached end of offset allowed for parsing - return gracefully */ michael@0: r = 1; michael@0: break; michael@0: } michael@0: r = ne_peek_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: break; michael@0: peeked_id = id; michael@0: michael@0: element = ne_find_element(id, ctx->ancestor->node); michael@0: if (element) { michael@0: if (element->flags & DESC_FLAG_SUSPEND) { michael@0: assert(element->type == TYPE_BINARY); michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "suspend parse at %llx", id); michael@0: r = 1; michael@0: break; michael@0: } michael@0: michael@0: r = ne_read_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: break; michael@0: assert(id == peeked_id); michael@0: michael@0: if (element->flags & DESC_FLAG_OFFSET) { michael@0: data_offset = (int64_t *) (ctx->ancestor->data + element->data_offset); michael@0: *data_offset = ne_io_tell(ctx->io); michael@0: if (*data_offset < 0) { michael@0: r = -1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (element->type == TYPE_MASTER) { michael@0: if (element->flags & DESC_FLAG_MULTI) { michael@0: if (ne_read_master(ctx, element) < 0) michael@0: break; michael@0: } else { michael@0: if (ne_read_single_master(ctx, element) < 0) michael@0: break; michael@0: } michael@0: continue; michael@0: } else { michael@0: r = ne_read_simple(ctx, element, size); michael@0: if (r < 0) michael@0: break; michael@0: } michael@0: } else if (ne_is_ancestor_element(id, ctx->ancestor->previous)) { michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "parent element %llx", id); michael@0: if (top_level && ctx->ancestor->node == top_level) { michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "*** parse about to back up past top_level"); michael@0: r = 1; michael@0: break; michael@0: } michael@0: ne_ctx_pop(ctx); michael@0: } else { michael@0: r = ne_read_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: break; michael@0: michael@0: if (id != ID_VOID && id != ID_CRC32) michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "unknown element %llx", id); michael@0: r = ne_io_read_skip(ctx->io, size); michael@0: if (r != 1) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (r != 1) michael@0: while (ctx->ancestor) michael@0: ne_ctx_pop(ctx); michael@0: michael@0: return r; michael@0: } michael@0: michael@0: static uint64_t michael@0: ne_xiph_lace_value(unsigned char ** np) michael@0: { michael@0: uint64_t lace; michael@0: uint64_t value; michael@0: unsigned char * p = *np; michael@0: michael@0: lace = *p++; michael@0: value = lace; michael@0: while (lace == 255) { michael@0: lace = *p++; michael@0: value += lace; michael@0: } michael@0: michael@0: *np = p; michael@0: michael@0: return value; michael@0: } michael@0: michael@0: static int michael@0: ne_read_xiph_lace_value(nestegg_io * io, uint64_t * value, size_t * consumed) michael@0: { michael@0: int r; michael@0: uint64_t lace; michael@0: michael@0: r = ne_read_uint(io, &lace, 1); michael@0: if (r != 1) michael@0: return r; michael@0: *consumed += 1; michael@0: michael@0: *value = lace; michael@0: while (lace == 255) { michael@0: r = ne_read_uint(io, &lace, 1); michael@0: if (r != 1) michael@0: return r; michael@0: *consumed += 1; michael@0: *value += lace; michael@0: } michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_xiph_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) michael@0: { michael@0: int r; michael@0: size_t i = 0; michael@0: uint64_t sum = 0; michael@0: michael@0: while (--n) { michael@0: r = ne_read_xiph_lace_value(io, &sizes[i], read); michael@0: if (r != 1) michael@0: return r; michael@0: sum += sizes[i]; michael@0: i += 1; michael@0: } michael@0: michael@0: if (*read + sum > block) michael@0: return -1; michael@0: michael@0: /* Last frame is the remainder of the block. */ michael@0: sizes[i] = block - *read - sum; michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_ebml_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) michael@0: { michael@0: int r; michael@0: uint64_t lace, sum, length; michael@0: int64_t slace; michael@0: size_t i = 0; michael@0: michael@0: r = ne_read_vint(io, &lace, &length); michael@0: if (r != 1) michael@0: return r; michael@0: *read += length; michael@0: michael@0: sizes[i] = lace; michael@0: sum = sizes[i]; michael@0: michael@0: i += 1; michael@0: n -= 1; michael@0: michael@0: while (--n) { michael@0: r = ne_read_svint(io, &slace, &length); michael@0: if (r != 1) michael@0: return r; michael@0: *read += length; michael@0: sizes[i] = sizes[i - 1] + slace; michael@0: sum += sizes[i]; michael@0: i += 1; michael@0: } michael@0: michael@0: if (*read + sum > block) michael@0: return -1; michael@0: michael@0: /* Last frame is the remainder of the block. */ michael@0: sizes[i] = block - *read - sum; michael@0: return 1; michael@0: } michael@0: michael@0: static uint64_t michael@0: ne_get_timecode_scale(nestegg * ctx) michael@0: { michael@0: uint64_t scale; michael@0: michael@0: if (ne_get_uint(ctx->segment.info.timecode_scale, &scale) != 0) michael@0: scale = 1000000; michael@0: michael@0: return scale; michael@0: } michael@0: michael@0: static int michael@0: ne_map_track_number_to_index(nestegg * ctx, michael@0: unsigned int track_number, michael@0: unsigned int * track_index) michael@0: { michael@0: struct ebml_list_node * node; michael@0: struct track_entry * t_entry; michael@0: uint64_t t_number = 0; michael@0: michael@0: if (!track_index) michael@0: return -1; michael@0: *track_index = 0; michael@0: michael@0: if (track_number == 0) michael@0: return -1; michael@0: michael@0: node = ctx->segment.tracks.track_entry.head; michael@0: while (node) { michael@0: assert(node->id == ID_TRACK_ENTRY); michael@0: t_entry = node->data; michael@0: if (ne_get_uint(t_entry->number, &t_number) != 0) michael@0: return -1; michael@0: if (t_number == track_number) michael@0: return 0; michael@0: *track_index += 1; michael@0: node = node->next; michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: static struct track_entry * michael@0: ne_find_track_entry(nestegg * ctx, unsigned int track) michael@0: { michael@0: struct ebml_list_node * node; michael@0: unsigned int tracks = 0; michael@0: michael@0: node = ctx->segment.tracks.track_entry.head; michael@0: while (node) { michael@0: assert(node->id == ID_TRACK_ENTRY); michael@0: if (track == tracks) michael@0: return node->data; michael@0: tracks += 1; michael@0: node = node->next; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: static int michael@0: ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_packet ** data) michael@0: { michael@0: int r; michael@0: int64_t timecode, abs_timecode; michael@0: nestegg_packet * pkt; michael@0: struct cluster * cluster; michael@0: struct frame * f, * last; michael@0: struct track_entry * entry; michael@0: double track_scale; michael@0: uint64_t track_number, length, frame_sizes[256], cluster_tc, flags, frames, tc_scale, total; michael@0: unsigned int i, lacing, track; michael@0: size_t consumed = 0; michael@0: michael@0: *data = NULL; michael@0: michael@0: if (block_size > LIMIT_BLOCK) michael@0: return -1; michael@0: michael@0: r = ne_read_vint(ctx->io, &track_number, &length); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: if (track_number == 0) michael@0: return -1; michael@0: michael@0: consumed += length; michael@0: michael@0: r = ne_read_int(ctx->io, &timecode, 2); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: consumed += 2; michael@0: michael@0: r = ne_read_uint(ctx->io, &flags, 1); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: consumed += 1; michael@0: michael@0: frames = 0; michael@0: michael@0: /* Flags are different between Block and SimpleBlock, but lacing is michael@0: encoded the same way. */ michael@0: lacing = (flags & BLOCK_FLAGS_LACING) >> 1; michael@0: michael@0: switch (lacing) { michael@0: case LACING_NONE: michael@0: frames = 1; michael@0: break; michael@0: case LACING_XIPH: michael@0: case LACING_FIXED: michael@0: case LACING_EBML: michael@0: r = ne_read_uint(ctx->io, &frames, 1); michael@0: if (r != 1) michael@0: return r; michael@0: consumed += 1; michael@0: frames += 1; michael@0: } michael@0: michael@0: if (frames > 256) michael@0: return -1; michael@0: michael@0: switch (lacing) { michael@0: case LACING_NONE: michael@0: frame_sizes[0] = block_size - consumed; michael@0: break; michael@0: case LACING_XIPH: michael@0: if (frames == 1) michael@0: return -1; michael@0: r = ne_read_xiph_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); michael@0: if (r != 1) michael@0: return r; michael@0: break; michael@0: case LACING_FIXED: michael@0: if ((block_size - consumed) % frames) michael@0: return -1; michael@0: for (i = 0; i < frames; ++i) michael@0: frame_sizes[i] = (block_size - consumed) / frames; michael@0: break; michael@0: case LACING_EBML: michael@0: if (frames == 1) michael@0: return -1; michael@0: r = ne_read_ebml_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); michael@0: if (r != 1) michael@0: return r; michael@0: break; michael@0: } michael@0: michael@0: /* Sanity check unlaced frame sizes against total block size. */ michael@0: total = consumed; michael@0: for (i = 0; i < frames; ++i) michael@0: total += frame_sizes[i]; michael@0: if (total > block_size) michael@0: return -1; michael@0: michael@0: if (ne_map_track_number_to_index(ctx, track_number, &track) != 0) michael@0: return -1; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: track_scale = 1.0; michael@0: michael@0: tc_scale = ne_get_timecode_scale(ctx); michael@0: michael@0: assert(ctx->segment.cluster.tail->id == ID_CLUSTER); michael@0: cluster = ctx->segment.cluster.tail->data; michael@0: if (ne_get_uint(cluster->timecode, &cluster_tc) != 0) michael@0: return -1; michael@0: michael@0: abs_timecode = timecode + cluster_tc; michael@0: if (abs_timecode < 0) michael@0: return -1; michael@0: michael@0: pkt = ne_alloc(sizeof(*pkt)); michael@0: if (!pkt) michael@0: return -1; michael@0: pkt->track = track; michael@0: pkt->timecode = abs_timecode * tc_scale * track_scale; michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "%sblock t %lld pts %f f %llx frames: %llu", michael@0: block_id == ID_BLOCK ? "" : "simple", pkt->track, pkt->timecode / 1e9, flags, frames); michael@0: michael@0: last = NULL; michael@0: for (i = 0; i < frames; ++i) { michael@0: if (frame_sizes[i] > LIMIT_FRAME) { michael@0: nestegg_free_packet(pkt); michael@0: return -1; michael@0: } michael@0: f = ne_alloc(sizeof(*f)); michael@0: if (!f) { michael@0: nestegg_free_packet(pkt); michael@0: return -1; michael@0: } michael@0: f->data = ne_alloc(frame_sizes[i]); michael@0: if (!f->data) { michael@0: free(f); michael@0: nestegg_free_packet(pkt); michael@0: return -1; michael@0: } michael@0: f->length = frame_sizes[i]; michael@0: r = ne_io_read(ctx->io, f->data, frame_sizes[i]); michael@0: if (r != 1) { michael@0: free(f->data); michael@0: free(f); michael@0: nestegg_free_packet(pkt); michael@0: return -1; michael@0: } michael@0: michael@0: if (!last) michael@0: pkt->frame = f; michael@0: else michael@0: last->next = f; michael@0: last = f; michael@0: } michael@0: michael@0: *data = pkt; michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_block_duration(nestegg * ctx, nestegg_packet * pkt) michael@0: { michael@0: int r; michael@0: uint64_t id, size; michael@0: struct ebml_element_desc * element; michael@0: struct ebml_type * storage; michael@0: michael@0: r = ne_peek_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: if (id != ID_BLOCK_DURATION) michael@0: return 1; michael@0: michael@0: element = ne_find_element(id, ctx->ancestor->node); michael@0: if (!element) michael@0: return 1; michael@0: michael@0: r = ne_read_simple(ctx, element, size); michael@0: if (r != 1) michael@0: return r; michael@0: storage = (struct ebml_type *) (ctx->ancestor->data + element->offset); michael@0: pkt->duration = storage->v.i * ne_get_timecode_scale(ctx); michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: ne_read_discard_padding(nestegg * ctx, nestegg_packet * pkt) michael@0: { michael@0: int r; michael@0: uint64_t id, size; michael@0: struct ebml_element_desc * element; michael@0: struct ebml_type * storage; michael@0: michael@0: r = ne_peek_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: if (id != ID_DISCARD_PADDING) michael@0: return 1; michael@0: michael@0: element = ne_find_element(id, ctx->ancestor->node); michael@0: if (!element) michael@0: return 1; michael@0: michael@0: r = ne_read_simple(ctx, element, size); michael@0: if (r != 1) michael@0: return r; michael@0: storage = (struct ebml_type *) (ctx->ancestor->data + element->offset); michael@0: pkt->discard_padding = storage->v.i; michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: michael@0: static uint64_t michael@0: ne_buf_read_id(unsigned char const * p, size_t length) michael@0: { michael@0: uint64_t id = 0; michael@0: michael@0: while (length--) { michael@0: id <<= 8; michael@0: id |= *p++; michael@0: } michael@0: michael@0: return id; michael@0: } michael@0: michael@0: static struct seek * michael@0: ne_find_seek_for_id(struct ebml_list_node * seek_head, uint64_t id) michael@0: { michael@0: struct ebml_list * head; michael@0: struct ebml_list_node * seek; michael@0: struct ebml_binary binary_id; michael@0: struct seek * s; michael@0: michael@0: while (seek_head) { michael@0: assert(seek_head->id == ID_SEEK_HEAD); michael@0: head = seek_head->data; michael@0: seek = head->head; michael@0: michael@0: while (seek) { michael@0: assert(seek->id == ID_SEEK); michael@0: s = seek->data; michael@0: michael@0: if (ne_get_binary(s->id, &binary_id) == 0 && michael@0: ne_buf_read_id(binary_id.data, binary_id.length) == id) michael@0: return s; michael@0: michael@0: seek = seek->next; michael@0: } michael@0: michael@0: seek_head = seek_head->next; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: static struct cue_track_positions * michael@0: ne_find_cue_position_for_track(nestegg * ctx, struct ebml_list_node * node, unsigned int track) michael@0: { michael@0: struct cue_track_positions * pos = NULL; michael@0: uint64_t track_number; michael@0: unsigned int t; michael@0: michael@0: while (node) { michael@0: assert(node->id == ID_CUE_TRACK_POSITIONS); michael@0: pos = node->data; michael@0: if (ne_get_uint(pos->track, &track_number) != 0) michael@0: return NULL; michael@0: michael@0: if (ne_map_track_number_to_index(ctx, track_number, &t) != 0) michael@0: return NULL; michael@0: michael@0: if (t == track) michael@0: return pos; michael@0: michael@0: node = node->next; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: static struct cue_point * michael@0: ne_find_cue_point_for_tstamp(nestegg * ctx, struct ebml_list_node * cue_point, unsigned int track, uint64_t scale, uint64_t tstamp) michael@0: { michael@0: uint64_t time; michael@0: struct cue_point * c, * prev = NULL; michael@0: michael@0: while (cue_point) { michael@0: assert(cue_point->id == ID_CUE_POINT); michael@0: c = cue_point->data; michael@0: michael@0: if (!prev) michael@0: prev = c; michael@0: michael@0: if (ne_get_uint(c->time, &time) == 0 && time * scale > tstamp) michael@0: break; michael@0: michael@0: if (ne_find_cue_position_for_track(ctx, c->cue_track_positions.head, track) != NULL) michael@0: prev = c; michael@0: michael@0: cue_point = cue_point->next; michael@0: } michael@0: michael@0: return prev; michael@0: } michael@0: michael@0: static int michael@0: ne_is_suspend_element(uint64_t id) michael@0: { michael@0: if (id == ID_SIMPLE_BLOCK || id == ID_BLOCK) michael@0: return 1; michael@0: return 0; michael@0: } michael@0: michael@0: static void michael@0: ne_null_log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ...) michael@0: { michael@0: if (ctx && severity && fmt) michael@0: return; michael@0: } michael@0: michael@0: static int michael@0: ne_init_cue_points(nestegg * ctx, int64_t max_offset) michael@0: { michael@0: int r; michael@0: struct ebml_list_node * node = ctx->segment.cues.cue_point.head; michael@0: struct seek * found; michael@0: uint64_t seek_pos, id; michael@0: struct saved_state state; michael@0: michael@0: /* If there are no cues loaded, check for cues element in the seek head michael@0: and load it. */ michael@0: if (!node) { michael@0: found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); michael@0: if (!found) michael@0: return -1; michael@0: michael@0: if (ne_get_uint(found->position, &seek_pos) != 0) michael@0: return -1; michael@0: michael@0: /* Save old parser state. */ michael@0: r = ne_ctx_save(ctx, &state); michael@0: if (r != 0) michael@0: return -1; michael@0: michael@0: /* Seek and set up parser state for segment-level element (Cues). */ michael@0: r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); michael@0: if (r != 0) michael@0: return -1; michael@0: ctx->last_valid = 0; michael@0: michael@0: r = ne_read_element(ctx, &id, NULL); michael@0: if (r != 1) michael@0: return -1; michael@0: michael@0: if (id != ID_CUES) michael@0: return -1; michael@0: michael@0: ctx->ancestor = NULL; michael@0: if (ne_ctx_push(ctx, ne_top_level_elements, ctx) < 0) michael@0: return -1; michael@0: if (ne_ctx_push(ctx, ne_segment_elements, &ctx->segment) < 0) michael@0: return -1; michael@0: if (ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues) < 0) michael@0: return -1; michael@0: /* parser will run until end of cues element. */ michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements"); michael@0: r = ne_parse(ctx, ne_cues_elements, max_offset); michael@0: while (ctx->ancestor) michael@0: ne_ctx_pop(ctx); michael@0: michael@0: /* Reset parser state to original state and seek back to old position. */ michael@0: if (ne_ctx_restore(ctx, &state) != 0) michael@0: return -1; michael@0: michael@0: if (r < 0) michael@0: return -1; michael@0: michael@0: node = ctx->segment.cues.cue_point.head; michael@0: if (!node) michael@0: return -1; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /* Three functions that implement the nestegg_io interface, operating on a michael@0: * sniff_buffer. */ michael@0: struct sniff_buffer { michael@0: unsigned char const * buffer; michael@0: size_t length; michael@0: int64_t offset; michael@0: }; michael@0: michael@0: static int michael@0: ne_buffer_read(void * buffer, size_t length, void * user_data) michael@0: { michael@0: struct sniff_buffer * sb = user_data; michael@0: michael@0: int rv = 1; michael@0: size_t available = sb->length - sb->offset; michael@0: michael@0: if (available < length) michael@0: return 0; michael@0: michael@0: memcpy(buffer, sb->buffer + sb->offset, length); michael@0: sb->offset += length; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static int michael@0: ne_buffer_seek(int64_t offset, int whence, void * user_data) michael@0: { michael@0: struct sniff_buffer * sb = user_data; michael@0: int64_t o = sb->offset; michael@0: michael@0: switch(whence) { michael@0: case NESTEGG_SEEK_SET: michael@0: o = offset; michael@0: break; michael@0: case NESTEGG_SEEK_CUR: michael@0: o += offset; michael@0: break; michael@0: case NESTEGG_SEEK_END: michael@0: o = sb->length + offset; michael@0: break; michael@0: } michael@0: michael@0: if (o < 0 || o > (int64_t) sb->length) michael@0: return -1; michael@0: michael@0: sb->offset = o; michael@0: return 0; michael@0: } michael@0: michael@0: static int64_t michael@0: ne_buffer_tell(void * user_data) michael@0: { michael@0: struct sniff_buffer * sb = user_data; michael@0: return sb->offset; michael@0: } michael@0: michael@0: static int michael@0: ne_match_webm(nestegg_io io, int64_t max_offset) michael@0: { michael@0: int r; michael@0: uint64_t id; michael@0: char * doctype; michael@0: nestegg * ctx; michael@0: michael@0: if (!(io.read && io.seek && io.tell)) michael@0: return -1; michael@0: michael@0: ctx = ne_alloc(sizeof(*ctx)); michael@0: if (!ctx) michael@0: return -1; michael@0: michael@0: ctx->io = ne_alloc(sizeof(*ctx->io)); michael@0: if (!ctx->io) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: *ctx->io = io; michael@0: ctx->alloc_pool = ne_pool_init(); michael@0: if (!ctx->alloc_pool) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: ctx->log = ne_null_log_callback; michael@0: michael@0: r = ne_peek_element(ctx, &id, NULL); michael@0: if (r != 1) { michael@0: nestegg_destroy(ctx); michael@0: return 0; michael@0: } michael@0: michael@0: if (id != ID_EBML) { michael@0: nestegg_destroy(ctx); michael@0: return 0; michael@0: } michael@0: michael@0: ne_ctx_push(ctx, ne_top_level_elements, ctx); michael@0: michael@0: /* we don't check the return value of ne_parse, that might fail because michael@0: * max_offset is not on a valid element end point. We only want to check michael@0: * the EBML ID and that the doctype is "webm". */ michael@0: ne_parse(ctx, NULL, max_offset); michael@0: michael@0: if (ne_get_string(ctx->ebml.doctype, &doctype) != 0 || michael@0: strcmp(doctype, "webm") != 0) { michael@0: nestegg_destroy(ctx); michael@0: return 0; michael@0: } michael@0: michael@0: nestegg_destroy(ctx); michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: int michael@0: nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset) michael@0: { michael@0: int r; michael@0: uint64_t id, version, docversion; michael@0: struct ebml_list_node * track; michael@0: char * doctype; michael@0: nestegg * ctx; michael@0: michael@0: if (!(io.read && io.seek && io.tell)) michael@0: return -1; michael@0: michael@0: ctx = ne_alloc(sizeof(*ctx)); michael@0: if (!ctx) michael@0: return -1; michael@0: michael@0: ctx->io = ne_alloc(sizeof(*ctx->io)); michael@0: if (!ctx->io) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: *ctx->io = io; michael@0: ctx->log = callback; michael@0: ctx->alloc_pool = ne_pool_init(); michael@0: if (!ctx->alloc_pool) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (!ctx->log) michael@0: ctx->log = ne_null_log_callback; michael@0: michael@0: r = ne_peek_element(ctx, &id, NULL); michael@0: if (r != 1) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (id != ID_EBML) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "ctx %p", ctx); michael@0: michael@0: ne_ctx_push(ctx, ne_top_level_elements, ctx); michael@0: michael@0: r = ne_parse(ctx, NULL, max_offset); michael@0: michael@0: if (r != 1) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (ne_get_uint(ctx->ebml.ebml_read_version, &version) != 0) michael@0: version = 1; michael@0: if (version != 1) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (ne_get_string(ctx->ebml.doctype, &doctype) != 0) michael@0: doctype = "matroska"; michael@0: if (strcmp(doctype, "webm") != 0) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (ne_get_uint(ctx->ebml.doctype_read_version, &docversion) != 0) michael@0: docversion = 1; michael@0: if (docversion < 1 || docversion > 2) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: if (!ctx->segment.tracks.track_entry.head) { michael@0: nestegg_destroy(ctx); michael@0: return -1; michael@0: } michael@0: michael@0: track = ctx->segment.tracks.track_entry.head; michael@0: ctx->track_count = 0; michael@0: michael@0: while (track) { michael@0: ctx->track_count += 1; michael@0: track = track->next; michael@0: } michael@0: michael@0: *context = ctx; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: nestegg_destroy(nestegg * ctx) michael@0: { michael@0: while (ctx->ancestor) michael@0: ne_ctx_pop(ctx); michael@0: ne_pool_destroy(ctx->alloc_pool); michael@0: free(ctx->io); michael@0: free(ctx); michael@0: } michael@0: michael@0: int michael@0: nestegg_duration(nestegg * ctx, uint64_t * duration) michael@0: { michael@0: uint64_t tc_scale; michael@0: double unscaled_duration; michael@0: michael@0: if (ne_get_float(ctx->segment.info.duration, &unscaled_duration) != 0) michael@0: return -1; michael@0: michael@0: tc_scale = ne_get_timecode_scale(ctx); michael@0: michael@0: *duration = (uint64_t) (unscaled_duration * tc_scale); michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_tstamp_scale(nestegg * ctx, uint64_t * scale) michael@0: { michael@0: *scale = ne_get_timecode_scale(ctx); michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_count(nestegg * ctx, unsigned int * tracks) michael@0: { michael@0: *tracks = ctx->track_count; michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_get_cue_point(nestegg * ctx, unsigned int cluster_num, int64_t max_offset, michael@0: int64_t * start_pos, int64_t * end_pos, uint64_t * tstamp) michael@0: { michael@0: int range_obtained = 0; michael@0: unsigned int cluster_count = 0; michael@0: struct cue_point * cue_point; michael@0: struct cue_track_positions * pos; michael@0: uint64_t seek_pos, track_number, tc_scale, time; michael@0: struct ebml_list_node * cues_node = ctx->segment.cues.cue_point.head; michael@0: struct ebml_list_node * cue_pos_node = NULL; michael@0: unsigned int track = 0, track_count = 0, track_index; michael@0: michael@0: if (!start_pos || !end_pos || !tstamp) michael@0: return -1; michael@0: michael@0: /* Initialise return values */ michael@0: *start_pos = -1; michael@0: *end_pos = -1; michael@0: *tstamp = 0; michael@0: michael@0: if (!cues_node) { michael@0: ne_init_cue_points(ctx, max_offset); michael@0: cues_node = ctx->segment.cues.cue_point.head; michael@0: /* Verify cues have been added to context. */ michael@0: if (!cues_node) michael@0: return -1; michael@0: } michael@0: michael@0: nestegg_track_count(ctx, &track_count); michael@0: michael@0: tc_scale = ne_get_timecode_scale(ctx); michael@0: michael@0: while (cues_node && !range_obtained) { michael@0: assert(cues_node->id == ID_CUE_POINT); michael@0: cue_point = cues_node->data; michael@0: cue_pos_node = cue_point->cue_track_positions.head; michael@0: while (cue_pos_node) { michael@0: assert(cue_pos_node->id == ID_CUE_TRACK_POSITIONS); michael@0: pos = cue_pos_node->data; michael@0: for (track = 0; track < track_count; track++) { michael@0: if (ne_get_uint(pos->track, &track_number) != 0) michael@0: return -1; michael@0: michael@0: if (ne_map_track_number_to_index(ctx, track_number, &track_index) != 0) michael@0: return -1; michael@0: michael@0: if (track_index == track) { michael@0: if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) michael@0: return -1; michael@0: if (cluster_count == cluster_num) { michael@0: *start_pos = ctx->segment_offset+seek_pos; michael@0: if (ne_get_uint(cue_point->time, &time) != 0) michael@0: return -1; michael@0: *tstamp = time * tc_scale; michael@0: } else if (cluster_count == cluster_num+1) { michael@0: *end_pos = (ctx->segment_offset+seek_pos)-1; michael@0: range_obtained = 1; michael@0: break; michael@0: } michael@0: cluster_count++; michael@0: } michael@0: } michael@0: cue_pos_node = cue_pos_node->next; michael@0: } michael@0: cues_node = cues_node->next; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_offset_seek(nestegg * ctx, uint64_t offset) michael@0: { michael@0: int r; michael@0: michael@0: if (offset > INT64_MAX) michael@0: return -1; michael@0: michael@0: /* Seek and set up parser state for segment-level element (Cluster). */ michael@0: r = ne_io_seek(ctx->io, offset, NESTEGG_SEEK_SET); michael@0: if (r != 0) michael@0: return -1; michael@0: ctx->last_valid = 0; michael@0: michael@0: while (ctx->ancestor) michael@0: ne_ctx_pop(ctx); michael@0: michael@0: ne_ctx_push(ctx, ne_top_level_elements, ctx); michael@0: ne_ctx_push(ctx, ne_segment_elements, &ctx->segment); michael@0: michael@0: ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cluster elements"); michael@0: r = ne_parse(ctx, NULL, -1); michael@0: if (r != 1) michael@0: return -1; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp) michael@0: { michael@0: int r; michael@0: struct cue_point * cue_point; michael@0: struct cue_track_positions * pos; michael@0: uint64_t seek_pos, tc_scale; michael@0: michael@0: /* If there are no cues loaded, check for cues element in the seek head michael@0: and load it. */ michael@0: if (!ctx->segment.cues.cue_point.head) { michael@0: r = ne_init_cue_points(ctx, -1); michael@0: if (r != 0) michael@0: return -1; michael@0: } michael@0: michael@0: tc_scale = ne_get_timecode_scale(ctx); michael@0: michael@0: cue_point = ne_find_cue_point_for_tstamp(ctx, ctx->segment.cues.cue_point.head, michael@0: track, tc_scale, tstamp); michael@0: if (!cue_point) michael@0: return -1; michael@0: michael@0: pos = ne_find_cue_position_for_track(ctx, cue_point->cue_track_positions.head, track); michael@0: if (pos == NULL) michael@0: return -1; michael@0: michael@0: if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) michael@0: return -1; michael@0: michael@0: /* Seek and set up parser state for segment-level element (Cluster). */ michael@0: r = nestegg_offset_seek(ctx, ctx->segment_offset + seek_pos); michael@0: michael@0: if (!ne_is_suspend_element(ctx->last_id)) michael@0: return -1; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_type(nestegg * ctx, unsigned int track) michael@0: { michael@0: struct track_entry * entry; michael@0: uint64_t type; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (ne_get_uint(entry->type, &type) != 0) michael@0: return -1; michael@0: michael@0: if (type & TRACK_TYPE_VIDEO) michael@0: return NESTEGG_TRACK_VIDEO; michael@0: michael@0: if (type & TRACK_TYPE_AUDIO) michael@0: return NESTEGG_TRACK_AUDIO; michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_codec_id(nestegg * ctx, unsigned int track) michael@0: { michael@0: char * codec_id; michael@0: struct track_entry * entry; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (ne_get_string(entry->codec_id, &codec_id) != 0) michael@0: return -1; michael@0: michael@0: if (strcmp(codec_id, TRACK_ID_VP8) == 0) michael@0: return NESTEGG_CODEC_VP8; michael@0: michael@0: if (strcmp(codec_id, TRACK_ID_VP9) == 0) michael@0: return NESTEGG_CODEC_VP9; michael@0: michael@0: if (strcmp(codec_id, TRACK_ID_VORBIS) == 0) michael@0: return NESTEGG_CODEC_VORBIS; michael@0: michael@0: if (strcmp(codec_id, TRACK_ID_OPUS) == 0) michael@0: return NESTEGG_CODEC_OPUS; michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_codec_data_count(nestegg * ctx, unsigned int track, michael@0: unsigned int * count) michael@0: { michael@0: struct track_entry * entry; michael@0: struct ebml_binary codec_private; michael@0: unsigned char * p; michael@0: michael@0: *count = 0; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) michael@0: return -1; michael@0: michael@0: if (ne_get_binary(entry->codec_private, &codec_private) != 0) michael@0: return -1; michael@0: michael@0: if (codec_private.length < 1) michael@0: return -1; michael@0: michael@0: p = codec_private.data; michael@0: *count = *p + 1; michael@0: michael@0: if (*count > 3) michael@0: return -1; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_codec_data(nestegg * ctx, unsigned int track, unsigned int item, michael@0: unsigned char ** data, size_t * length) michael@0: { michael@0: struct track_entry * entry; michael@0: struct ebml_binary codec_private; michael@0: uint64_t sizes[3], total; michael@0: unsigned char * p; michael@0: unsigned int count, i; michael@0: michael@0: *data = NULL; michael@0: *length = 0; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS michael@0: && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS) michael@0: return -1; michael@0: michael@0: if (ne_get_binary(entry->codec_private, &codec_private) != 0) michael@0: return -1; michael@0: michael@0: if (nestegg_track_codec_id(ctx, track) == NESTEGG_CODEC_VORBIS) { michael@0: p = codec_private.data; michael@0: count = *p++ + 1; michael@0: michael@0: if (count > 3) michael@0: return -1; michael@0: michael@0: i = 0; michael@0: total = 0; michael@0: while (--count) { michael@0: sizes[i] = ne_xiph_lace_value(&p); michael@0: total += sizes[i]; michael@0: i += 1; michael@0: } michael@0: sizes[i] = codec_private.length - total - (p - codec_private.data); michael@0: michael@0: for (i = 0; i < item; ++i) { michael@0: if (sizes[i] > LIMIT_FRAME) michael@0: return -1; michael@0: p += sizes[i]; michael@0: } michael@0: *data = p; michael@0: *length = sizes[item]; michael@0: } else { michael@0: *data = codec_private.data; michael@0: *length = codec_private.length; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_video_params(nestegg * ctx, unsigned int track, michael@0: nestegg_video_params * params) michael@0: { michael@0: struct track_entry * entry; michael@0: uint64_t value; michael@0: michael@0: memset(params, 0, sizeof(*params)); michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_VIDEO) michael@0: return -1; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->video.stereo_mode, &value); michael@0: if (value <= NESTEGG_VIDEO_STEREO_TOP_BOTTOM || michael@0: value == NESTEGG_VIDEO_STEREO_RIGHT_LEFT) michael@0: params->stereo_mode = value; michael@0: michael@0: if (ne_get_uint(entry->video.pixel_width, &value) != 0) michael@0: return -1; michael@0: params->width = value; michael@0: michael@0: if (ne_get_uint(entry->video.pixel_height, &value) != 0) michael@0: return -1; michael@0: params->height = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->video.pixel_crop_bottom, &value); michael@0: params->crop_bottom = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->video.pixel_crop_top, &value); michael@0: params->crop_top = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->video.pixel_crop_left, &value); michael@0: params->crop_left = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->video.pixel_crop_right, &value); michael@0: params->crop_right = value; michael@0: michael@0: value = params->width; michael@0: ne_get_uint(entry->video.display_width, &value); michael@0: params->display_width = value; michael@0: michael@0: value = params->height; michael@0: ne_get_uint(entry->video.display_height, &value); michael@0: params->display_height = value; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_audio_params(nestegg * ctx, unsigned int track, michael@0: nestegg_audio_params * params) michael@0: { michael@0: struct track_entry * entry; michael@0: uint64_t value; michael@0: michael@0: memset(params, 0, sizeof(*params)); michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_AUDIO) michael@0: return -1; michael@0: michael@0: params->rate = 8000; michael@0: ne_get_float(entry->audio.sampling_frequency, ¶ms->rate); michael@0: michael@0: value = 1; michael@0: ne_get_uint(entry->audio.channels, &value); michael@0: params->channels = value; michael@0: michael@0: value = 16; michael@0: ne_get_uint(entry->audio.bit_depth, &value); michael@0: params->depth = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->codec_delay, &value); michael@0: params->codec_delay = value; michael@0: michael@0: value = 0; michael@0: ne_get_uint(entry->seek_preroll, &value); michael@0: params->seek_preroll = value; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_track_default_duration(nestegg * ctx, unsigned int track, michael@0: uint64_t * duration) michael@0: { michael@0: struct track_entry * entry; michael@0: uint64_t value; michael@0: michael@0: entry = ne_find_track_entry(ctx, track); michael@0: if (!entry) michael@0: return -1; michael@0: michael@0: if (ne_get_uint(entry->default_duration, &value) != 0) michael@0: return -1; michael@0: *duration = value; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_read_packet(nestegg * ctx, nestegg_packet ** pkt) michael@0: { michael@0: int r; michael@0: uint64_t id, size; michael@0: michael@0: *pkt = NULL; michael@0: michael@0: for (;;) { michael@0: r = ne_peek_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: /* Any DESC_FLAG_SUSPEND fields must be handled here. */ michael@0: if (ne_is_suspend_element(id)) { michael@0: r = ne_read_element(ctx, &id, &size); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: /* The only DESC_FLAG_SUSPEND fields are Blocks and SimpleBlocks, which we michael@0: handle directly. */ michael@0: r = ne_read_block(ctx, id, size, pkt); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: r = ne_read_block_duration(ctx, *pkt); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: r = ne_read_discard_padding(ctx, *pkt); michael@0: if (r != 1) michael@0: return r; michael@0: michael@0: return r; michael@0: } michael@0: michael@0: r = ne_parse(ctx, NULL, -1); michael@0: if (r != 1) michael@0: return r; michael@0: } michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: void michael@0: nestegg_free_packet(nestegg_packet * pkt) michael@0: { michael@0: struct frame * frame; michael@0: michael@0: while (pkt->frame) { michael@0: frame = pkt->frame; michael@0: pkt->frame = frame->next; michael@0: free(frame->data); michael@0: free(frame); michael@0: } michael@0: michael@0: free(pkt); michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_track(nestegg_packet * pkt, unsigned int * track) michael@0: { michael@0: *track = pkt->track; michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_tstamp(nestegg_packet * pkt, uint64_t * tstamp) michael@0: { michael@0: *tstamp = pkt->timecode; michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_duration(nestegg_packet * pkt, uint64_t * duration) michael@0: { michael@0: *duration = pkt->duration; michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_discard_padding(nestegg_packet * pkt, int64_t * discard_padding) michael@0: { michael@0: *discard_padding = pkt->discard_padding; michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_count(nestegg_packet * pkt, unsigned int * count) michael@0: { michael@0: struct frame * f = pkt->frame; michael@0: michael@0: *count = 0; michael@0: michael@0: while (f) { michael@0: *count += 1; michael@0: f = f->next; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nestegg_packet_data(nestegg_packet * pkt, unsigned int item, michael@0: unsigned char ** data, size_t * length) michael@0: { michael@0: struct frame * f = pkt->frame; michael@0: unsigned int count = 0; michael@0: michael@0: *data = NULL; michael@0: *length = 0; michael@0: michael@0: while (f) { michael@0: if (count == item) { michael@0: *data = f->data; michael@0: *length = f->length; michael@0: return 0; michael@0: } michael@0: count += 1; michael@0: f = f->next; michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: int michael@0: nestegg_has_cues(nestegg * ctx) michael@0: { michael@0: return ctx->segment.cues.cue_point.head || michael@0: ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); michael@0: } michael@0: michael@0: int michael@0: nestegg_sniff(unsigned char const * buffer, size_t length) michael@0: { michael@0: nestegg_io io; michael@0: struct sniff_buffer user_data; michael@0: michael@0: user_data.buffer = buffer; michael@0: user_data.length = length; michael@0: user_data.offset = 0; michael@0: michael@0: io.read = ne_buffer_read; michael@0: io.seek = ne_buffer_seek; michael@0: io.tell = ne_buffer_tell; michael@0: io.userdata = &user_data; michael@0: return ne_match_webm(io, length); michael@0: } michael@0: michael@0: void michael@0: nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t)) michael@0: { michael@0: halloc_allocator = realloc_func; michael@0: }