--- lighttpd-1.4.18/src/Makefile.am 2007-09-03 01:23:53.000000000 +0300 +++ lighttpd-mod_h264_streaming-1.4.18/src/Makefile.am 2007-10-23 23:42:37.736979478 +0300 @@ -77,6 +77,11 @@ mod_flv_streaming_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_flv_streaming_la_LIBADD = $(common_libadd) +lib_LTLIBRARIES += mod_h264_streaming.la +mod_h264_streaming_la_SOURCES = mod_h264_streaming.c moov.c +mod_h264_streaming_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_h264_streaming_la_LIBADD = $(common_libadd) + lib_LTLIBRARIES += mod_evasive.la mod_evasive_la_SOURCES = mod_evasive.c mod_evasive_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined --- /dev/null 2008-11-04 20:33:38.146691408 +0200 +++ lighttpd-1.4.18/src/mod_h264_streaming.c 2009-01-26 20:59:51.385271731 +0200 @@ -0,0 +1,337 @@ +/******************************************************************************* + mod_h264_streaming.c + + mod_h264_streaming - A lighttpd plugin for pseudo-streaming Quicktime/MPEG4 files. + http://h264.code-shop.com + + Copyright (C) 2007-2009 CodeShop B.V. +******************************************************************************/ + +#include +#include +#include +#include + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "response.h" +#include "http_chunk.h" +#include "stat_cache.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "moov.h" + +/* plugin config for all request/connections */ + +typedef struct { + array *extensions; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *query_str; + array *get_params; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_h264_streaming_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->query_str = buffer_init(); + p->get_params = array_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_h264_streaming_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (!s) continue; + + array_free(s->extensions); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->query_str); + array_free(p->get_params); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_h264_streaming_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "h264-streaming.extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->extensions = array_init(); + + cv[0].destination = s->extensions; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_h264_streaming_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(extensions); + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + s = p->config_storage[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("h264-streaming.extensions"))) { + PATCH(extensions); + } + } + } + + return 0; +} +#undef PATCH + +static int split_get_params(array *get_params, buffer *qrystr) { + size_t is_key = 1; + size_t i; + char *key = NULL, *val = NULL; + + key = qrystr->ptr; + + /* we need the \0 */ + for (i = 0; i < qrystr->used; i++) { + switch(qrystr->ptr[i]) { + case '=': + if (is_key) { + val = qrystr->ptr + i + 1; + + qrystr->ptr[i] = '\0'; + + is_key = 0; + } + + break; + case '&': + case '\0': /* fin symbol */ + if (!is_key) { + data_string *ds; + /* we need at least a = since the last & */ + + /* terminate the value */ + qrystr->ptr[i] = '\0'; + + if (NULL == (ds = (data_string *)array_get_unused_element(get_params, TYPE_STRING))) { + ds = data_string_init(); + } + buffer_copy_string_len(ds->key, key, strlen(key)); + buffer_copy_string_len(ds->value, val, strlen(val)); + + array_insert_unique(get_params, (data_unset *)ds); + } + + key = qrystr->ptr + i + 1; + val = NULL; + is_key = 1; + break; + } + } + + return 0; +} + +URIHANDLER_FUNC(mod_h264_streaming_path_handler) { + plugin_data *p = p_d; + int s_len; + size_t k; + + UNUSED(srv); + + if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON; + + mod_h264_streaming_patch_connection(srv, con, p); + + s_len = con->physical.path->used - 1; + + for (k = 0; k < p->conf.extensions->used; k++) { + data_string *ds = (data_string *)p->conf.extensions->data[k]; + int ct_len = ds->value->used - 1; + + if (ct_len > s_len) continue; + if (ds->value->used == 0) continue; + + if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { + data_string *get_param; + stat_cache_entry *sce = NULL; + double start = 0.0; + double end = 0.0; + char *err = NULL; + int client_is_flash = 0; + + array_reset(p->get_params); + buffer_copy_string_buffer(p->query_str, con->uri.query); + split_get_params(p->get_params, p->query_str); + + /* if there is a start=[0-9]+ in the header use it as start, + * otherwise send the full file */ + if (NULL != (get_param = (data_string *)array_get_element(p->get_params, "start"))) + { + /* check if it is a number */ + start = strtod(get_param->value->ptr, &err); + if (*err != '\0') { + return HANDLER_GO_ON; + } + if (start < 0) return HANDLER_GO_ON; + } + + /* if there is an end=[0-9]+ in the header use it as end */ + if (NULL != (get_param = (data_string *)array_get_element(p->get_params, "end"))) + { + /* check if it is a number */ + end = strtod(get_param->value->ptr, &err); + if (*err != '\0') { + return HANDLER_GO_ON; + } + if (end < 0 || start >= end) return HANDLER_GO_ON; + } + + if (NULL != (get_param = (data_string *)array_get_element(p->get_params, "client"))) + { + client_is_flash = starts_with(get_param->value->ptr, "FLASH"); + } + + /* get file info */ + if (HANDLER_GO_ON != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + return HANDLER_GO_ON; + } + + /* we are safe now, let's build a h264 header */ + { + { + unsigned int filesize = sce->st.st_size; + + void* mp4_header; + uint32_t mp4_header_size; + uint64_t mdat_offset; + uint64_t mdat_size; + + int result = mp4_split(con->physical.path->ptr, filesize, start, end, + &mp4_header, &mp4_header_size, + &mdat_offset, &mdat_size, client_is_flash); + + if(result) + { + buffer* b = chunkqueue_get_append_buffer(con->write_queue); + buffer_append_memory(b, mp4_header, mp4_header_size); + b->used++; /* add virtual \0 */ + + http_chunk_append_file(srv, con, con->physical.path, + mdat_offset, mdat_size); + } + + if(mp4_header) + { + free(mp4_header); + } + + if(!result) + { + return HANDLER_GO_ON; + } + } + } + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("video/mp4")); + if(!client_is_flash) + { + response_header_overwrite(srv, con, CONST_STR_LEN("X-Mod-H264-Streaming"), CONST_STR_LEN("version=2.0")); + } + else + { + response_header_overwrite(srv, con, CONST_STR_LEN("X-Mod-H264-Streaming"), CONST_STR_LEN("version=2.0,client=flash")); + } + + con->file_finished = 1; + + return HANDLER_FINISHED; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_h264_streaming_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("h264_streaming"); + + p->init = mod_h264_streaming_init; + p->handle_physical = mod_h264_streaming_path_handler; + p->set_defaults = mod_h264_streaming_set_defaults; + p->cleanup = mod_h264_streaming_free; + + p->data = NULL; + + return 0; +} + --- /dev/null 2008-11-04 20:33:38.146691408 +0200 +++ lighttpd-1.4.18/src/moov.c 2009-01-26 21:00:05.071936866 +0200 @@ -0,0 +1,3031 @@ +/******************************************************************************* + moov.c (version 2) + + moov - A library for splitting Quicktime/MPEG4 files. + http://h264.code-shop.com + + Copyright (C) 2007-2009 CodeShop B.V. + + Licensing + The H264 Streaming Module is licened under a Creative Common License. It allows + you to use, modify and redistribute the module, but only for *noncommercial* + purposes. For corporate use, please apply for a commercial license. + + Creative Commons License: + http://creativecommons.org/licenses/by-nc-sa/3.0/ + + Commercial License: + http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-License-Version2 +******************************************************************************/ + +#include "moov.h" + +#ifdef _MSVC_VER +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +/* + The QuickTime File Format PDF from Apple: + http://developer.apple.com/techpubs/quicktime/qtdevdocs/PDF/QTFileFormat.pdf +*/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#if defined HAVE_ZLIB_H && defined HAVE_LIBZ +// Compress the MOOV atom. Turn this off for Flash as it doesn't support it. +// # define COMPRESS_MOOV_ATOM +# include +#endif + +#ifdef WIN32 +#define ftello _ftelli64 +#define fseeko _fseeki64 +#endif + +#define MAX_TRACKS 8 + +#define FOURCC(a, b, c, d) ((uint32_t)(a) << 24) + \ + ((uint32_t)(b) << 16) + \ + ((uint32_t)(c) << 8) + \ + ((uint32_t)(d)) + +/* Returns true when the test string is a prefix of the input */ +int starts_with(const char* input, const char* test) +{ + while(*input && *test) + { + if(*input != *test) + return 0; + ++input; + ++test; + } + + return *test == '\0'; +} + +static unsigned int read_8(unsigned char const* buffer) +{ + return buffer[0]; +} + +static unsigned char* write_8(unsigned char* buffer, unsigned char v) +{ + buffer[0] = v; + + return buffer + 1; +} + +static uint16_t read_16(unsigned char const* buffer) +{ + return (buffer[0] << 8) | + (buffer[1] << 0); +} + +static unsigned char* write_16(unsigned char* buffer, unsigned int v) +{ + buffer[0] = (unsigned char)(v >> 8); + buffer[1] = (unsigned char)(v >> 0); + + return buffer + 2; +} + +static unsigned int read_24(unsigned char const* buffer) +{ + return (buffer[0] << 16) | + (buffer[1] << 8) | + (buffer[2] << 0); +} + +static unsigned char* write_24(unsigned char* buffer, unsigned int v) +{ + buffer[0] = (unsigned char)(v >> 16); + buffer[1] = (unsigned char)(v >> 8); + buffer[2] = (unsigned char)(v >> 0); + + return buffer + 3; +} + +static uint32_t read_32(unsigned char const* buffer) +{ + return (buffer[0] << 24) | + (buffer[1] << 16) | + (buffer[2] << 8) | + (buffer[3] << 0); +} + +static unsigned char* write_32(unsigned char* buffer, uint32_t v) +{ + buffer[0] = (unsigned char)(v >> 24); + buffer[1] = (unsigned char)(v >> 16); + buffer[2] = (unsigned char)(v >> 8); + buffer[3] = (unsigned char)(v >> 0); + + return buffer + 4; +} + +static uint64_t read_64(unsigned char const* buffer) +{ + return ((uint64_t)(read_32(buffer)) << 32) + read_32(buffer + 4); +} + +static unsigned char* write_64(unsigned char* buffer, uint64_t v) +{ + write_32(buffer + 0, (uint32_t)(v >> 32)); + write_32(buffer + 4, (uint32_t)(v >> 0)); + + return buffer + 8; +} + +#define ATOM_PREAMBLE_SIZE 8 + +struct atom_t +{ + uint32_t type_; + uint32_t short_size_; + uint64_t size_; + unsigned char* start_; + unsigned char* end_; +}; + +static unsigned char* atom_read_header(unsigned char* buffer, struct atom_t* atom) +{ + atom->start_ = buffer; + atom->short_size_ = read_32(buffer); + atom->type_ = read_32(buffer + 4); + + if(atom->short_size_ == 1) + atom->size_ = read_64(buffer + 8); + else + atom->size_ = atom->short_size_; + + atom->end_ = atom->start_ + atom->size_; + + return buffer + ATOM_PREAMBLE_SIZE + (atom->short_size_ == 1 ? 8 : 0); +} + +static void atom_print(struct atom_t const* atom) +{ + printf("Atom(%c%c%c%c,%lld)\n", + atom->type_ >> 24, + atom->type_ >> 16, + atom->type_ >> 8, + atom->type_, + atom->size_); +} + +struct unknown_atom_t +{ + void* atom_; + struct unknown_atom_t* next_; +}; + +static struct unknown_atom_t* unknown_atom_init() +{ + struct unknown_atom_t* atom = (struct unknown_atom_t*)malloc(sizeof(struct unknown_atom_t)); + atom->atom_ = 0; + atom->next_ = 0; + + return atom; +} + +static void unknown_atom_exit(struct unknown_atom_t* atom) +{ + while(atom) + { + struct unknown_atom_t* next = atom->next_; + free(atom->atom_); + free(atom); + atom = next; + } +} + +static struct unknown_atom_t* unknown_atom_add_atom(struct unknown_atom_t* parent, void* atom) +{ + size_t size = read_32(atom); + struct unknown_atom_t* unknown = unknown_atom_init(); + unknown->atom_ = malloc(size); + memcpy(unknown->atom_, atom, size); + unknown->next_ = parent; + return unknown; +} + +struct atom_read_list_t +{ + uint32_t type_; + void* parent_; + int (*destination_)(void* parent, void* child); + void* (*reader_)(void* parent, unsigned char* buffer, uint64_t size); +}; + +static int atom_reader(struct atom_read_list_t* atom_read_list, + unsigned int atom_read_list_size, + void* parent, + unsigned char* buffer, uint64_t size) +{ + struct atom_t leaf_atom; + unsigned char* buffer_start = buffer; + + while(buffer < buffer_start + size) + { + unsigned int i; + buffer = atom_read_header(buffer, &leaf_atom); + + atom_print(&leaf_atom); + + for(i = 0; i != atom_read_list_size; ++i) + { + if(leaf_atom.type_ == atom_read_list[i].type_) + { + break; + } + } + + if(i == atom_read_list_size) + { + // add to unkown chunks + (*(struct unknown_atom_t**)parent) = + unknown_atom_add_atom(*(struct unknown_atom_t**)(parent), buffer - ATOM_PREAMBLE_SIZE); + } + else + { + void* child = + atom_read_list[i].reader_(parent, buffer, + leaf_atom.size_ - ATOM_PREAMBLE_SIZE); + if(!child) + break; + if(!atom_read_list[i].destination_(parent, child)) + break; + } + buffer = leaf_atom.end_; + } + + if(buffer < buffer_start + size) + { + return 0; + } + + return 1; +} + +struct atom_write_list_t +{ + uint32_t type_; + void* parent_; + void* source_; + unsigned char* (*writer_)(void* parent, void* atom, unsigned char* buffer); +}; + +static unsigned char* atom_writer_unknown(struct unknown_atom_t* atoms, + unsigned char* buffer) +{ + while(atoms) + { + size_t size = read_32(atoms->atom_); + memcpy(buffer, atoms->atom_, size); + buffer += size; + atoms = atoms->next_; + } + + return buffer; +} + +static unsigned char* atom_writer(struct unknown_atom_t* unknown_atoms, + struct atom_write_list_t* atom_write_list, + unsigned int atom_write_list_size, + unsigned char* buffer) +{ + unsigned i; + const int write_box64 = 0; + + if(unknown_atoms) + { + buffer = atom_writer_unknown(unknown_atoms, buffer); + } + + for(i = 0; i != atom_write_list_size; ++i) + { + if(atom_write_list[i].source_ != 0) + { + unsigned char* atom_start = buffer; + // atom size + if(write_box64) + { + write_32(buffer, 1); // box64 + } + buffer += 4; + + // atom type + buffer = write_32(buffer, atom_write_list[i].type_); + if(write_box64) + { + buffer += 8; // box64 + } + + // atom payload + buffer = atom_write_list[i].writer_(atom_write_list[i].parent_, + atom_write_list[i].source_, buffer); + + if(write_box64) + write_64(atom_start + 8, buffer - atom_start); + else + write_32(atom_start, buffer - atom_start); + } + } + + return buffer; +} + +struct tkhd_t +{ + unsigned int version_; + unsigned int flags_; + uint64_t creation_time_; + uint64_t modification_time_; + uint32_t track_id_; + uint32_t reserved_; + uint64_t duration_; + uint32_t reserved2_[2]; + uint16_t layer_; + uint16_t predefined_; + uint16_t volume_; + uint16_t reserved3_; + uint32_t matrix_[9]; + uint32_t width_; + uint32_t height_; +}; + +struct mdhd_t +{ + unsigned int version_; + unsigned int flags_; + uint64_t creation_time_; + uint64_t modification_time_; + uint32_t timescale_; + uint64_t duration_; + unsigned int language_[3]; + uint16_t predefined_; +}; + +struct vmhd_t +{ + unsigned int version_; + unsigned int flags_; + uint16_t graphics_mode_; + uint16_t opcolor_[3]; +}; + +struct hdlr_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t predefined_; + uint32_t handler_type_; + uint32_t reserved1_; + uint32_t reserved2_; + uint32_t reserved3_; + char* name_; +}; + +struct stbl_t +{ + struct unknown_atom_t* unknown_atoms_; +//struct stsd_t* stsd_; // sample description + struct stts_t* stts_; // decoding time-to-sample + struct stss_t* stss_; // sync sample + struct stsc_t* stsc_; // sample-to-chunk + struct stsz_t* stsz_; // sample size + struct stco_t* stco_; // chunk offset + struct ctts_t* ctts_; // composition time-to-sample + + void* stco_inplace_; // newly generated stco (patched inplace) +}; + +struct stts_table_t +{ + uint32_t sample_count_; + uint32_t sample_duration_; +}; + +struct stts_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t entries_; + struct stts_table_t* table_; +}; + +struct stss_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t entries_; + uint32_t* sample_numbers_; +}; + +struct stsc_table_t +{ + uint32_t chunk_; + uint32_t samples_; + uint32_t id_; +}; + +struct stsc_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t entries_; + struct stsc_table_t* table_; +}; + +struct stsz_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t sample_size_; + uint32_t entries_; + uint32_t* sample_sizes_; +}; + +struct stco_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t entries_; + uint64_t* chunk_offsets_; +}; + +struct ctts_table_t +{ + uint32_t sample_count_; + uint32_t sample_offset_; +}; + +struct ctts_t +{ + unsigned int version_; + unsigned int flags_; + uint32_t entries_; + struct ctts_table_t* table_; +}; + +struct minf_t +{ + struct unknown_atom_t* unknown_atoms_; + struct vmhd_t* vmhd_; +// struct dinf_t* dinf_; + struct stbl_t* stbl_; +}; + +struct mdia_t +{ + struct unknown_atom_t* unknown_atoms_; + struct mdhd_t* mdhd_; + struct hdlr_t* hdlr_; + struct minf_t* minf_; +}; + +struct chunks_t +{ + unsigned int sample_; // number of the first sample in the chunk + unsigned int size_; // number of samples in the chunk + int id_; // for multiple codecs mode - not used + uint64_t pos_; // start byte position of chunk +}; + +struct samples_t +{ + unsigned int pts_; // decoding/presentation time + unsigned int size_; // size in bytes + uint64_t pos_; // byte offset + unsigned int cto_; // composition time offset +}; + +struct trak_t +{ + struct unknown_atom_t* unknown_atoms_; + struct tkhd_t* tkhd_; + struct mdia_t* mdia_; + + /* temporary indices */ + unsigned int chunks_size_; + struct chunks_t* chunks_; + + unsigned int samples_size_; + struct samples_t* samples_; +}; + +struct mvhd_t +{ + unsigned int version_; + unsigned int flags_; + uint64_t creation_time_; + uint64_t modification_time_; + uint32_t timescale_; + uint64_t duration_; + uint32_t rate_; + uint16_t volume_; + uint16_t reserved1_; + uint32_t reserved2_[2]; + uint32_t matrix_[9]; + uint32_t predefined_[6]; + uint32_t next_track_id_; +}; + +struct moov_t +{ + struct unknown_atom_t* unknown_atoms_; + struct mvhd_t* mvhd_; + unsigned int tracks_; + struct trak_t* traks_[MAX_TRACKS]; +}; + + +static struct tkhd_t* tkhd_init() +{ + struct tkhd_t* tkhd = (struct tkhd_t*)malloc(sizeof(struct tkhd_t)); + + return tkhd; +} + +static void tkhd_exit(struct tkhd_t* tkhd) +{ + free(tkhd); +} + +static void* tkhd_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct tkhd_t* tkhd = tkhd_init(); + + tkhd->version_ = read_8(buffer + 0); + tkhd->flags_ = read_24(buffer + 1); + if(tkhd->version_ == 0) + { + if(size < 92-8) + return 0; + + tkhd->creation_time_ = read_32(buffer + 4); + tkhd->modification_time_ = read_32(buffer + 8); + tkhd->track_id_ = read_32(buffer + 12); + tkhd->reserved_ = read_32(buffer + 16); + tkhd->duration_ = read_32(buffer + 20); + buffer += 24; + } + else + { + if(size < 104-8) + return 0; + + tkhd->creation_time_ = read_64(buffer + 4); + tkhd->modification_time_ = read_64(buffer + 12); + tkhd->track_id_ = read_32(buffer + 20); + tkhd->reserved_ = read_32(buffer + 24); + tkhd->duration_ = read_64(buffer + 28); + buffer += 36; + } + + tkhd->reserved2_[0] = read_32(buffer + 0); + tkhd->reserved2_[1] = read_32(buffer + 4); + tkhd->layer_ = read_16(buffer + 8); + tkhd->predefined_ = read_16(buffer + 10); + tkhd->volume_ = read_16(buffer + 12); + tkhd->reserved3_ = read_16(buffer + 14); + buffer += 16; + + for(i = 0; i != 9; ++i) + { + tkhd->matrix_[i] = read_32(buffer); + buffer += 4; + } + + tkhd->width_ = read_32(buffer + 0); + tkhd->height_ = read_32(buffer + 4); + + return tkhd; +} + +static unsigned char* tkhd_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct tkhd_t const* tkhd = atom; + unsigned int i; + + buffer = write_8(buffer, tkhd->version_); + buffer = write_24(buffer, tkhd->flags_); + + if(tkhd->version_ == 0) + { + buffer = write_32(buffer, (uint32_t)tkhd->creation_time_); + buffer = write_32(buffer, (uint32_t)tkhd->modification_time_); + buffer = write_32(buffer, tkhd->track_id_); + buffer = write_32(buffer, tkhd->reserved_); + buffer = write_32(buffer, (uint32_t)tkhd->duration_); + } + else + { + buffer = write_64(buffer, tkhd->creation_time_); + buffer = write_64(buffer, tkhd->modification_time_); + buffer = write_32(buffer, tkhd->track_id_); + buffer = write_32(buffer, tkhd->reserved_); + buffer = write_64(buffer, tkhd->duration_); + } + + buffer = write_32(buffer, tkhd->reserved2_[0]); + buffer = write_32(buffer, tkhd->reserved2_[1]); + buffer = write_16(buffer, tkhd->layer_); + buffer = write_16(buffer, tkhd->predefined_); + buffer = write_16(buffer, tkhd->volume_); + buffer = write_16(buffer, tkhd->reserved3_); + + for(i = 0; i != 9; ++i) + { + buffer = write_32(buffer, tkhd->matrix_[i]); + } + + buffer = write_32(buffer, tkhd->width_); + buffer = write_32(buffer, tkhd->height_); + + return buffer; +} + +static struct mdhd_t* mdhd_init() +{ + struct mdhd_t* mdhd = (struct mdhd_t*)malloc(sizeof(struct mdhd_t)); + + return mdhd; +} + +static void mdhd_exit(struct mdhd_t* mdhd) +{ + free(mdhd); +} + +static void* mdhd_read(void* UNUSED(parent), unsigned char* buffer, uint64_t UNUSED(size)) +{ + uint16_t language; + unsigned int i; + + struct mdhd_t* mdhd = mdhd_init(); + mdhd->version_ = read_8(buffer + 0); + mdhd->flags_ = read_24(buffer + 1); + if(mdhd->version_ == 0) + { + mdhd->creation_time_ = read_32(buffer + 4); + mdhd->modification_time_ = read_32(buffer + 8); + mdhd->timescale_ = read_32(buffer + 12); + mdhd->duration_ = read_32(buffer + 16); + buffer += 20; + } + else + { + mdhd->creation_time_ = read_64(buffer + 4); + mdhd->modification_time_ = read_64(buffer + 12); + mdhd->timescale_ = read_32(buffer + 20); + mdhd->duration_ = read_64(buffer + 24); + buffer += 32; + } + + language = read_16(buffer + 0); + for(i = 0; i != 3; ++i) + { + mdhd->language_[i] = ((language >> ((2 - i) * 5)) & 0x1f) + 0x60; + } + + mdhd->predefined_ = read_16(buffer + 2); + + return mdhd; +} + +static unsigned char* mdhd_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct mdhd_t const* mdhd = atom; + + buffer = write_8(buffer, mdhd->version_); + buffer = write_24(buffer, mdhd->flags_); + + if(mdhd->version_ == 0) + { + buffer = write_32(buffer, (uint32_t)mdhd->creation_time_); + buffer = write_32(buffer, (uint32_t)mdhd->modification_time_); + buffer = write_32(buffer, mdhd->timescale_); + buffer = write_32(buffer, (uint32_t)mdhd->duration_); + } + else + { + buffer = write_64(buffer, mdhd->creation_time_); + buffer = write_64(buffer, mdhd->modification_time_); + buffer = write_32(buffer, mdhd->timescale_); + buffer = write_64(buffer, mdhd->duration_); + } + + buffer = write_16(buffer, + ((mdhd->language_[0] - 0x60) << 10) + + ((mdhd->language_[1] - 0x60) << 5) + + ((mdhd->language_[2] - 0x60) << 0)); + + buffer = write_16(buffer, mdhd->predefined_); + + return buffer; +} + +static struct vmhd_t* vmhd_init() +{ + struct vmhd_t* atom = (struct vmhd_t*)malloc(sizeof(struct vmhd_t)); + + return atom; +} + +void vmhd_exit(struct vmhd_t* atom) +{ + free(atom); +} + +static void* vmhd_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct vmhd_t* atom; + + if(size < 20-8) + return 0; + + atom = vmhd_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + + atom->graphics_mode_ = read_16(buffer + 4); + buffer += 6; + for(i = 0; i != 3; ++i) + { + atom->opcolor_[i] = read_16(buffer); + buffer += 2; + } + + return atom; +} + +static unsigned char* vmhd_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct vmhd_t const* vmhd = atom; + unsigned int i; + + buffer = write_8(buffer, vmhd->version_); + buffer = write_24(buffer, vmhd->flags_); + buffer = write_16(buffer, vmhd->graphics_mode_); + for(i = 0; i != 3; ++i) + { + buffer = write_16(buffer, vmhd->opcolor_[i]); + } + + return buffer; +} + +static struct hdlr_t* hdlr_init() +{ + struct hdlr_t* atom = (struct hdlr_t*)malloc(sizeof(struct hdlr_t)); + atom->name_ = 0; + + return atom; +} + +static void hdlr_exit(struct hdlr_t* atom) +{ + if(atom->name_) + { + free(atom->name_); + } + free(atom); +} + +static void* hdlr_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct hdlr_t* atom; + + if(size < 8) + return 0; + + atom = hdlr_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->predefined_ = read_32(buffer + 4); + atom->handler_type_ = read_32(buffer + 8); + atom->reserved1_ = read_32(buffer + 12); + atom->reserved2_ = read_32(buffer + 16); + atom->reserved3_ = read_32(buffer + 20); + buffer += 24; + size -= 24; + if(size > 0) + { + size_t length = (size_t)size; + atom->name_ = malloc(length + 1); + if(atom->predefined_ == FOURCC('m', 'h', 'l', 'r')) + { + length = read_8(buffer); + buffer += 1; + if(size < length) + length = (size_t)size; + } + memcpy(atom->name_, buffer, length); + atom->name_[length] = '\0'; + } + + return atom; +} + +static unsigned char* hdlr_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct hdlr_t* hdlr = atom; + buffer = write_8(buffer, hdlr->version_); + buffer = write_24(buffer, hdlr->flags_); + + buffer = write_32(buffer, hdlr->predefined_); + buffer = write_32(buffer, hdlr->handler_type_); + buffer = write_32(buffer, hdlr->reserved1_); + buffer = write_32(buffer, hdlr->reserved2_); + buffer = write_32(buffer, hdlr->reserved3_); + if(hdlr->name_) + { + char const* p; + if(hdlr->predefined_ == FOURCC('m', 'h', 'l', 'r')) + { + buffer = write_8(buffer, strlen(hdlr->name_)); + } + + for(p = hdlr->name_; *p; ++p) + buffer = write_8(buffer, *p); + } + + return buffer; +} + +static struct stts_t* stts_init() +{ + struct stts_t* atom = (struct stts_t*)malloc(sizeof(struct stts_t)); + atom->table_ = 0; + + return atom; +} + +void stts_exit(struct stts_t* atom) +{ + if(atom->table_) + { + free(atom->table_); + } + free(atom); +} + +static void* stts_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stts_t* atom; + + if(size < 8) + return 0; + + atom = stts_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + + if(size < 8 + atom->entries_ * sizeof(struct stts_table_t)) + return 0; + + buffer += 8; + + atom->table_ = (struct stts_table_t*)(malloc(atom->entries_ * sizeof(struct stts_table_t))); + + for(i = 0; i != atom->entries_; ++i) + { + atom->table_[i].sample_count_ = read_32(buffer + 0); + atom->table_[i].sample_duration_ = read_32(buffer + 4); + buffer += 8; + } + + return atom; +} + +static unsigned char* stts_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct stts_t* stts = atom; + unsigned int i; + + buffer = write_8(buffer, stts->version_); + buffer = write_24(buffer, stts->flags_); + buffer = write_32(buffer, stts->entries_); + for(i = 0; i != stts->entries_; ++i) + { + buffer = write_32(buffer, stts->table_[i].sample_count_); + buffer = write_32(buffer, stts->table_[i].sample_duration_); + } + + return buffer; +} + +static unsigned int stts_get_sample(struct stts_t const* stts, uint64_t time) +{ + unsigned int stts_index = 0; + unsigned int stts_count; + + unsigned int ret = 0; + uint64_t time_count = 0; + + for(; stts_index != stts->entries_; ++stts_index) + { + unsigned int sample_count = stts->table_[stts_index].sample_count_; + unsigned int sample_duration = stts->table_[stts_index].sample_duration_; + if(time_count + (uint64_t)sample_duration * (uint64_t)sample_count >= time) + { + stts_count = (unsigned int)((time - time_count) / sample_duration); + time_count += (uint64_t)stts_count * (uint64_t)sample_duration; + ret += stts_count; + break; + } + else + { + time_count += (uint64_t)sample_duration * (uint64_t)sample_count; + ret += sample_count; + } + } + return ret; +} + +static uint64_t stts_get_time(struct stts_t const* stts, unsigned int sample) +{ + uint64_t ret = 0; + unsigned int stts_index = 0; + unsigned int sample_count = 0; + + for(;;) + { + unsigned int table_sample_count = stts->table_[stts_index].sample_count_; + unsigned int table_sample_duration = stts->table_[stts_index].sample_duration_; + if(sample_count + table_sample_count > sample) + { + unsigned int stts_count = (sample - sample_count); + ret += (uint64_t)stts_count * (uint64_t)table_sample_duration; + break; + } + else + { + sample_count += table_sample_count; + ret += (uint64_t)table_sample_count * (uint64_t)table_sample_duration; + stts_index++; + } + } + return ret; +} + +static uint64_t stts_get_duration(struct stts_t const* stts) +{ + uint64_t duration = 0; + unsigned int i; + for(i = 0; i != stts->entries_; ++i) + { + unsigned int sample_count = stts->table_[i].sample_count_; + unsigned int sample_duration = stts->table_[i].sample_duration_; + duration += (uint64_t)sample_duration * (uint64_t)sample_count; + } + + return duration; +} + +static unsigned int stts_get_samples(struct stts_t const* stts) +{ + unsigned int samples = 0; + unsigned int entries = stts->entries_; + unsigned int i; + for(i = 0; i != entries; ++i) + { + unsigned int sample_count = stts->table_[i].sample_count_; +// unsigned int sample_duration = stts->table_[i].sample_duration_; + samples += sample_count; + } + + return samples; +} + +static struct stss_t* stss_init() +{ + struct stss_t* atom = (struct stss_t*)malloc(sizeof(struct stss_t)); + atom->sample_numbers_ = 0; + + return atom; +} + +void stss_exit(struct stss_t* atom) +{ + if(atom->sample_numbers_) + { + free(atom->sample_numbers_); + } + free(atom); +} + +static void* stss_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stss_t* atom; + + if(size < 8) + return 0; + + atom = stss_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + + if(size < 8 + atom->entries_ * sizeof(uint32_t)) + return 0; + + buffer += 8; + + atom->sample_numbers_ = (uint32_t*)malloc(atom->entries_ * sizeof(uint32_t)); + + for(i = 0; i != atom->entries_; ++i) + { + atom->sample_numbers_[i] = read_32(buffer); + buffer += 4; + } + + return atom; +} + +static unsigned char* stss_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct stss_t const* stss = atom; + unsigned int i; + + buffer = write_8(buffer, stss->version_); + buffer = write_24(buffer, stss->flags_); + buffer = write_32(buffer, stss->entries_); + for(i = 0; i != stss->entries_; ++i) + { + buffer = write_32(buffer, stss->sample_numbers_[i]); + } + + return buffer; +} + +static unsigned int stss_get_nearest_keyframe(struct stss_t const* stss, unsigned int sample) +{ + // scan the sync samples to find the key frame that precedes the sample number + unsigned int i; + unsigned int table_sample = 0; + for(i = 0; i != stss->entries_; ++i) + { + table_sample = stss->sample_numbers_[i]; + if(table_sample >= sample) + break; + } + if(table_sample == sample) + return table_sample; + else + return stss->sample_numbers_[i - 1]; +} + +static struct stsc_t* stsc_init() +{ + struct stsc_t* atom = (struct stsc_t*)malloc(sizeof(struct stsc_t)); + atom->table_ = 0; + + return atom; +} + +static void stsc_exit(struct stsc_t* atom) +{ + if(atom->table_) + { + free(atom->table_); + } + free(atom); +} + +static void* stsc_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stsc_t* atom; + + if(size < 8) + return 0; + + atom = stsc_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + + if(size < 8 + atom->entries_ * sizeof(struct stsc_table_t)) + return 0; + + buffer += 8; + + // reserve space for one extra entry as when splitting the video we may have to + // split the first entry + atom->table_ = (struct stsc_table_t*)(malloc((atom->entries_ + 1) * sizeof(struct stsc_table_t))); + + for(i = 0; i != atom->entries_; ++i) + { + atom->table_[i].chunk_ = read_32(buffer + 0) - 1; // Note: we use zero based + atom->table_[i].samples_ = read_32(buffer + 4); + atom->table_[i].id_ = read_32(buffer + 8); + buffer += 12; + } + + return atom; +} + +static unsigned char* stsc_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct stsc_t* stsc = atom; + unsigned int i; + + buffer = write_8(buffer, stsc->version_); + buffer = write_24(buffer, stsc->flags_); + buffer = write_32(buffer, stsc->entries_); + for(i = 0; i != stsc->entries_; ++i) + { + buffer = write_32(buffer, stsc->table_[i].chunk_ + 1); + buffer = write_32(buffer, stsc->table_[i].samples_); + buffer = write_32(buffer, stsc->table_[i].id_); + } + + return buffer; +} + +static struct stsz_t* stsz_init() +{ + struct stsz_t* atom = (struct stsz_t*)malloc(sizeof(struct stsz_t)); + atom->sample_sizes_ = 0; + + return atom; +} + +static void stsz_exit(struct stsz_t* atom) +{ + if(atom->sample_sizes_) + { + free(atom->sample_sizes_); + } + free(atom); +} + +static void* stsz_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stsz_t* atom; + + if(size < 12) + { + printf("Error: not enough bytes for stsz atom\n"); + return 0; + } + + atom = stsz_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->sample_size_ = read_32(buffer + 4); + atom->entries_ = read_32(buffer + 8); + buffer += 12; + + // fix for clayton.mp4, it mistakenly says there is 1 entry + if(atom->sample_size_ && atom->entries_) + atom->entries_ = 0; + + if(size < 12 + atom->entries_ * sizeof(uint32_t)) + { + printf("Error: stsz.entries don't match with size\n"); + stsz_exit(atom); + return 0; + } + + if(!atom->sample_size_) + { + atom->sample_sizes_ = (uint32_t*)malloc(atom->entries_ * sizeof(uint32_t)); + for(i = 0; i != atom->entries_; ++i) + { + atom->sample_sizes_[i] = read_32(buffer); + buffer += 4; + } + } + + return atom; +} + +static unsigned char* stsz_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct stsz_t* stsz = atom; + unsigned int i; + unsigned int entries = stsz->sample_size_ ? 0 : stsz->entries_; + + buffer = write_8(buffer, stsz->version_); + buffer = write_24(buffer, stsz->flags_); + buffer = write_32(buffer, stsz->sample_size_); + buffer = write_32(buffer, entries); + for(i = 0; i != entries; ++i) + { + buffer = write_32(buffer, stsz->sample_sizes_[i]); + } + + return buffer; +} + +static struct stco_t* stco_init() +{ + struct stco_t* atom = (struct stco_t*)malloc(sizeof(struct stco_t)); + atom->chunk_offsets_ = 0; + + return atom; +} + +static void stco_exit(struct stco_t* atom) +{ + if(atom->chunk_offsets_) + { + free(atom->chunk_offsets_); + } + free(atom); +} + +static void* stco_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stco_t* atom; + + if(size < 8) + return 0; + + atom = stco_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + buffer += 8; + + if(size < 8 + atom->entries_ * sizeof(uint32_t)) + return 0; + + atom->chunk_offsets_ = (uint64_t*)malloc(atom->entries_ * sizeof(uint64_t)); + for(i = 0; i != atom->entries_; ++i) + { + atom->chunk_offsets_[i] = read_32(buffer); + buffer += 4; + } + + return atom; +} + +static void* co64_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct stco_t* atom; + + if(size < 8) + return 0; + + atom = stco_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + buffer += 8; + + if(size < 8 + atom->entries_ * sizeof(uint64_t)) + return 0; + + atom->chunk_offsets_ = (uint64_t*)malloc(atom->entries_ * sizeof(uint64_t)); + for(i = 0; i != atom->entries_; ++i) + { + atom->chunk_offsets_[i] = read_64(buffer); + buffer += 8; + } + + return atom; +} + +static unsigned char* stco_write(void* parent, void* atom, unsigned char* buffer) +{ + struct stbl_t* stbl = parent; + struct stco_t* stco = atom; + unsigned int i; + + stbl->stco_inplace_ = buffer; // newly generated stco (patched inplace) + + buffer = write_8(buffer, stco->version_); + buffer = write_24(buffer, stco->flags_); + buffer = write_32(buffer, stco->entries_); + for(i = 0; i != stco->entries_; ++i) + { + buffer = write_32(buffer, (uint32_t)(stco->chunk_offsets_[i])); + } + + return buffer; +} + +static void stco_shift_offsets(struct stco_t* stco, int offset) +{ + unsigned int i; + for(i = 0; i != stco->entries_; ++i) + stco->chunk_offsets_[i] += offset; +} + +static void stco_shift_offsets_inplace(unsigned char* stco, int offset) +{ + unsigned int entries = read_32(stco + 4); + unsigned int* table = (unsigned int*)(stco + 8); + unsigned int i; + for(i = 0; i != entries; ++i) + write_32((unsigned char*)&table[i], (read_32((unsigned char*)&table[i]) + offset)); +} + +static struct ctts_t* ctts_init() +{ + struct ctts_t* atom = (struct ctts_t*)malloc(sizeof(struct ctts_t)); + atom->table_ = 0; + + return atom; +} + +static void ctts_exit(struct ctts_t* atom) +{ + if(atom->table_) + { + free(atom->table_); + } + free(atom); +} + +static void* ctts_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct ctts_t* atom; + + if(size < 8) + return 0; + + atom = ctts_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + atom->entries_ = read_32(buffer + 4); + + if(size < 8 + atom->entries_ * sizeof(struct ctts_table_t)) + return 0; + + buffer += 8; + + atom->table_ = (struct ctts_table_t*)(malloc(atom->entries_ * sizeof(struct ctts_table_t))); + + for(i = 0; i != atom->entries_; ++i) + { + atom->table_[i].sample_count_ = read_32(buffer + 0); + atom->table_[i].sample_offset_ = read_32(buffer + 4); + buffer += 8; + } + + return atom; +} + +static unsigned char* ctts_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct ctts_t const* ctts = atom; + unsigned int i; + + buffer = write_8(buffer, ctts->version_); + buffer = write_24(buffer, ctts->flags_); + buffer = write_32(buffer, ctts->entries_); + for(i = 0; i != ctts->entries_; ++i) + { + buffer = write_32(buffer, (uint32_t)(ctts->table_[i].sample_count_)); + buffer = write_32(buffer, (uint32_t)(ctts->table_[i].sample_offset_)); + } + + return buffer; +} + +static unsigned int ctts_get_samples(struct ctts_t const* ctts) +{ + unsigned int samples = 0; + unsigned int entries = ctts->entries_; + unsigned int i; + for(i = 0; i != entries; ++i) + { + unsigned int sample_count = ctts->table_[i].sample_count_; +// unsigned int sample_offset = ctts->table_[i].sample_offset_; + samples += sample_count; + } + + return samples; +} + +static struct stbl_t* stbl_init() +{ + struct stbl_t* atom = (struct stbl_t*)malloc(sizeof(struct stbl_t)); + atom->unknown_atoms_ = 0; + atom->stts_ = 0; + atom->stss_ = 0; + atom->stsc_ = 0; + atom->stsz_ = 0; + atom->stco_ = 0; + atom->ctts_ = 0; + + return atom; +} + +static void stbl_exit(struct stbl_t* atom) +{ + if(atom->unknown_atoms_) + { + unknown_atom_exit(atom->unknown_atoms_); + } + if(atom->stts_) + { + stts_exit(atom->stts_); + } + if(atom->stss_) + { + stss_exit(atom->stss_); + } + if(atom->stsc_) + { + stsc_exit(atom->stsc_); + } + if(atom->stsz_) + { + stsz_exit(atom->stsz_); + } + if(atom->stco_) + { + stco_exit(atom->stco_); + } + if(atom->ctts_) + { + ctts_exit(atom->ctts_); + } + + free(atom); +} + +static int stbl_add_stts(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->stts_ = child; + + return 1; +} + +static int stbl_add_stss(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->stss_ = child; + + return 1; +} + +static int stbl_add_stsc(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->stsc_ = child; + + return 1; +} + +static int stbl_add_stsz(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->stsz_ = child; + + return 1; +} + +static int stbl_add_stco(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->stco_ = child; + + return 1; +} + +static int stbl_add_ctts(void* parent, void* child) +{ + struct stbl_t* stbl = parent; + stbl->ctts_ = child; + + return 1; +} + +static void* stbl_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct stbl_t* atom = stbl_init(); + + struct atom_read_list_t atom_read_list[] = { + { FOURCC('s', 't', 't', 's'), atom, &stbl_add_stts, &stts_read }, + { FOURCC('s', 't', 's', 's'), atom, &stbl_add_stss, &stss_read }, + { FOURCC('s', 't', 's', 'c'), atom, &stbl_add_stsc, &stsc_read }, + { FOURCC('s', 't', 's', 'z'), atom, &stbl_add_stsz, &stsz_read }, + { FOURCC('s', 't', 'c', 'o'), atom, &stbl_add_stco, &stco_read }, + { FOURCC('c', 'o', '6', '4'), atom, &stbl_add_stco, &co64_read }, + { FOURCC('c', 't', 't', 's'), atom, &stbl_add_ctts, &ctts_read }, + }; + + int result = atom_reader(atom_read_list, + sizeof(atom_read_list) / sizeof(atom_read_list[0]), + atom, + buffer, size); + + // check for mandatory atoms + if(!atom->stts_) + { + printf("stbl: missing stts\n"); + result = 0; + } + + if(!atom->stsc_) + { + printf("stbl: missing stsc\n"); + result = 0; + } + + if(!atom->stsz_) + { + printf("stbl: missing stsz\n"); + result = 0; + } + + if(!atom->stco_) + { + printf("stbl: missing stco\n"); + result = 0; + } + + if(!result) + { + stbl_exit(atom); + return 0; + } + + return atom; +} + +static unsigned char* stbl_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct stbl_t* stbl = atom; + struct atom_write_list_t atom_write_list[] = { + { FOURCC('s', 't', 't', 's'), stbl, stbl->stts_, &stts_write }, + { FOURCC('s', 't', 's', 's'), stbl, stbl->stss_, &stss_write }, + { FOURCC('s', 't', 's', 'c'), stbl, stbl->stsc_, &stsc_write }, + { FOURCC('s', 't', 's', 'z'), stbl, stbl->stsz_, &stsz_write }, + { FOURCC('s', 't', 'c', 'o'), stbl, stbl->stco_, &stco_write }, + { FOURCC('c', 't', 't', 's'), stbl, stbl->ctts_, &ctts_write }, + }; + + buffer = atom_writer(stbl->unknown_atoms_, + atom_write_list, + sizeof(atom_write_list) / sizeof(atom_write_list[0]), + buffer); + + return buffer; +} + +static unsigned int stbl_get_nearest_keyframe(struct stbl_t const* stbl, unsigned int sample) +{ + // If the sync atom is not present, all samples are implicit sync samples. + if(!stbl->stss_) + return sample; + + return stss_get_nearest_keyframe(stbl->stss_, sample); +} + +static struct minf_t* minf_init() +{ + struct minf_t* atom = (struct minf_t*)malloc(sizeof(struct minf_t)); + atom->unknown_atoms_ = 0; + atom->vmhd_ = 0; + atom->stbl_ = 0; + + return atom; +} + +static void minf_exit(struct minf_t* atom) +{ + if(atom->unknown_atoms_) + { + unknown_atom_exit(atom->unknown_atoms_); + } + if(atom->vmhd_) + { + vmhd_exit(atom->vmhd_); + } + if(atom->stbl_) + { + stbl_exit(atom->stbl_); + } + free(atom); +} + +static int minf_add_vmhd(void* parent, void* child) +{ + struct minf_t* minf = parent; + minf->vmhd_ = child; + + return 1; +} + +static int minf_add_stbl(void* parent, void* child) +{ + struct minf_t* minf = parent; + minf->stbl_ = child; + + return 1; +} + +static void* minf_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct minf_t* atom = minf_init(); + + struct atom_read_list_t atom_read_list[] = { + { FOURCC('v', 'm', 'h', 'd'), atom, &minf_add_vmhd, &vmhd_read }, + { FOURCC('s', 't', 'b', 'l'), atom, &minf_add_stbl, &stbl_read } + }; + + int result = atom_reader(atom_read_list, + sizeof(atom_read_list) / sizeof(atom_read_list[0]), + atom, + buffer, size); + + // check for mandatory atoms + if(!atom->stbl_) + { + printf("minf: missing stbl\n"); + result = 0; + } + + if(!result) + { + minf_exit(atom); + return 0; + } + + return atom; +} + +static unsigned char* minf_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct minf_t const* minf = atom; + struct atom_write_list_t atom_write_list[] = { + { FOURCC('v', 'm', 'h', 'd'), atom, minf->vmhd_, &vmhd_write }, + { FOURCC('s', 't', 'b', 'l'), atom, minf->stbl_, &stbl_write } + }; + + buffer = atom_writer(minf->unknown_atoms_, + atom_write_list, + sizeof(atom_write_list) / sizeof(atom_write_list[0]), + buffer); + + return buffer; +} + +static struct mdia_t* mdia_init() +{ + struct mdia_t* atom = (struct mdia_t*)malloc(sizeof(struct mdia_t)); + atom->unknown_atoms_ = 0; + atom->mdhd_ = 0; + atom->hdlr_ = 0; + atom->minf_ = 0; + + return atom; +} + +static void mdia_exit(struct mdia_t* atom) +{ + if(atom->unknown_atoms_) + { + unknown_atom_exit(atom->unknown_atoms_); + } + if(atom->mdhd_) + { + mdhd_exit(atom->mdhd_); + } + if(atom->hdlr_) + { + hdlr_exit(atom->hdlr_); + } + if(atom->minf_) + { + minf_exit(atom->minf_); + } + free(atom); +} + +static int mdia_add_mdhd(void* parent, void* child) +{ + struct mdia_t* mdia = parent; + mdia->mdhd_ = child; + + return 1; +} + +static int mdia_add_hdlr(void* parent, void* child) +{ + struct mdia_t* mdia = parent; + mdia->hdlr_ = child; + + return 1; +} + +static int mdia_add_minf(void* parent, void* child) +{ + struct mdia_t* mdia = parent; + mdia->minf_ = child; + + return 1; +} + +static void* mdia_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct mdia_t* atom = mdia_init(); + + struct atom_read_list_t atom_read_list[] = { + { FOURCC('m', 'd', 'h', 'd'), atom, &mdia_add_mdhd, &mdhd_read }, + { FOURCC('h', 'd', 'l', 'r'), atom, &mdia_add_hdlr, &hdlr_read }, + { FOURCC('m', 'i', 'n', 'f'), atom, &mdia_add_minf, &minf_read } + }; + + int result = atom_reader(atom_read_list, + sizeof(atom_read_list) / sizeof(atom_read_list[0]), + atom, + buffer, size); + + // check for mandatory atoms + if(!atom->mdhd_) + { + printf("mdia: missing mdhd\n"); + result = 0; + } + + if(!atom->hdlr_) + { + printf("mdia: missing hdlr\n"); + result = 0; + } + + if(!atom->minf_) + { + printf("mdia: missing minf\n"); + result = 0; + } + + if(!result) + { + mdia_exit(atom); + return 0; + } + + return atom; +} + +static unsigned char* mdia_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct mdia_t const* mdia = atom; + struct atom_write_list_t atom_write_list[] = { + { FOURCC('m', 'd', 'h', 'd'), atom, mdia->mdhd_, &mdhd_write }, + { FOURCC('h', 'd', 'l', 'r'), atom, mdia->hdlr_, &hdlr_write }, + { FOURCC('m', 'i', 'n', 'f'), atom, mdia->minf_, &minf_write } + }; + + buffer = atom_writer(mdia->unknown_atoms_, + atom_write_list, + sizeof(atom_write_list) / sizeof(atom_write_list[0]), + buffer); + + return buffer; +} + +void trak_build_index(struct trak_t* trak) +{ + struct stco_t const* stco = trak->mdia_->minf_->stbl_->stco_; + + trak->chunks_size_ = stco->entries_; + trak->chunks_ = malloc(trak->chunks_size_ * sizeof(struct chunks_t)); + + { + unsigned int i; + for(i = 0; i != trak->chunks_size_; ++i) + { + trak->chunks_[i].pos_ = stco->chunk_offsets_[i]; + } + } + + // process chunkmap: + { + struct stsc_t const* stsc = trak->mdia_->minf_->stbl_->stsc_; + unsigned int last = trak->chunks_size_; + unsigned int i = stsc->entries_; + while(i > 0) + { + unsigned int j; + + --i; + + for(j = stsc->table_[i].chunk_; j < last; j++) + { + trak->chunks_[j].id_ = stsc->table_[i].id_; + trak->chunks_[j].size_ = stsc->table_[i].samples_; + } + last = stsc->table_[i].chunk_; + } + } + + // calc pts of chunks: + { + struct stsz_t const* stsz = trak->mdia_->minf_->stbl_->stsz_; + unsigned int sample_size = stsz->sample_size_; + unsigned int s = 0; + { + unsigned int j; + for(j = 0; j < trak->chunks_size_; j++) + { + trak->chunks_[j].sample_ = s; + s += trak->chunks_[j].size_; + } + } + + if(sample_size == 0) + { + trak->samples_size_ = stsz->entries_; + } + else + { + trak->samples_size_ = s; + } + + trak->samples_ = malloc(trak->samples_size_ * sizeof(struct samples_t)); + + if(sample_size == 0) + { + unsigned int i; + for(i = 0; i != trak->samples_size_ ; ++i) + trak->samples_[i].size_ = stsz->sample_sizes_[i]; + } + else + { + unsigned int i; + for(i = 0; i != trak->samples_size_ ; ++i) + trak->samples_[i].size_ = sample_size; + } + } + +// i = 0; +// for (j = 0; j < trak->durmap_size; j++) +// i += trak->durmap[j].num; +// if (i != s) { +// mp_msg(MSGT_DEMUX, MSGL_WARN, +// "MOV: durmap and chunkmap sample count differ (%i vs %i)\n", i, s); +// if (i > s) s = i; +// } + + // calc pts: + { + struct stts_t const* stts = trak->mdia_->minf_->stbl_->stts_; + unsigned int s = 0; + unsigned int pts = 0; + unsigned int entries = stts->entries_; + unsigned int j; + for(j = 0; j < entries; j++) + { + unsigned int i; + unsigned int sample_count = stts->table_[j].sample_count_; + unsigned int sample_duration = stts->table_[j].sample_duration_; + for(i = 0; i < sample_count; i++) + { + trak->samples_[s].pts_ = pts; + ++s; + pts += sample_duration; + } + } + } + + // calc composition times: + { + struct ctts_t const* ctts = trak->mdia_->minf_->stbl_->ctts_; + if(ctts) + { + unsigned int s = 0; + unsigned int entries = ctts->entries_; + unsigned int j; + for(j = 0; j != entries; j++) + { + unsigned int i; + unsigned int sample_count = ctts->table_[j].sample_count_; + unsigned int sample_offset = ctts->table_[j].sample_offset_; + for(i = 0; i < sample_count; i++) + { + trak->samples_[s].cto_ = sample_offset; + ++s; + } + } + } + } + + // calc sample offsets + { + unsigned int s = 0; + unsigned int j; + for(j = 0; j != trak->chunks_size_; j++) + { + uint64_t pos = trak->chunks_[j].pos_; + unsigned int i; + for(i = 0; i != trak->chunks_[j].size_; i++) + { + trak->samples_[s].pos_ = pos; + pos += trak->samples_[s].size_; + ++s; + } + } + } +} + +void trak_update_index(struct trak_t* trak, unsigned int start, unsigned int end) +{ + // write samples [start,end> + + // stts = [entries * [sample_count, sample_duration] + { + struct stts_t* stts = trak->mdia_->minf_->stbl_->stts_; + + unsigned int entries = 0; + unsigned int s; + + for(s = start; s != end; ++s) + { + unsigned int sample_count = 1; + unsigned int sample_duration = + trak->samples_[s + 1].pts_ - trak->samples_[s].pts_; + while(s != end - 1) + { + if((trak->samples_[s + 1].pts_ - trak->samples_[s].pts_) != sample_duration) + break; + ++sample_count; + ++s; + } + stts->table_[entries].sample_count_ = sample_count; + stts->table_[entries].sample_duration_ = sample_duration; + ++entries; + } + stts->entries_ = entries; + + if(stts_get_samples(stts) != end - start) + { + printf("ERROR: stts_get_samples=%d, should be %d\n", + stts_get_samples(stts), end - start); + } + } + + // ctts = [entries * [sample_count, sample_offset] + { + struct ctts_t* ctts = trak->mdia_->minf_->stbl_->ctts_; + if(ctts) + { + unsigned int entries = 0; + unsigned int s; + + for(s = start; s != end; ++s) + { + unsigned int sample_count = 1; + unsigned int sample_offset = trak->samples_[s].cto_; + while(s != end - 1) + { + if(trak->samples_[s + 1].cto_ != sample_offset) + break; + ++sample_count; + ++s; + } + // write entry + ctts->table_[entries].sample_count_ = sample_count; + ctts->table_[entries].sample_offset_ = sample_offset; + ++entries; + } + ctts->entries_ = entries; + if(ctts_get_samples(ctts) != end - start) + { + printf("ERROR: ctts_get_samples=%d, should be %d\n", + ctts_get_samples(ctts), end - start); + } + } + } + + // process chunkmap: + { + struct stsc_t* stsc = trak->mdia_->minf_->stbl_->stsc_; + unsigned int i; + + for(i = 0; i != trak->chunks_size_; ++i) + { + if(trak->chunks_[i].sample_ + trak->chunks_[i].size_ > start) + break; + } + + { + unsigned int stsc_entries = 0; + unsigned int chunk_start = i; + unsigned int chunk_end; + // problem.mp4: reported by Jin-seok Lee. Second track contains no samples + if(trak->chunks_size_ != 0) + { + unsigned int samples = + trak->chunks_[i].sample_ + trak->chunks_[i].size_ - start; + unsigned int id = trak->chunks_[i].id_; + + // write entry [chunk,samples,id] + stsc->table_[stsc_entries].chunk_ = 0; + stsc->table_[stsc_entries].samples_ = samples; + stsc->table_[stsc_entries].id_ = id; + ++stsc_entries; + + if(i != trak->chunks_size_) + { + for(i += 1; i != trak->chunks_size_; ++i) + { + if(trak->chunks_[i].sample_ >= end) + break; + + if(trak->chunks_[i].size_ != samples) + { + samples = trak->chunks_[i].size_; + id = trak->chunks_[i].id_; + + stsc->table_[stsc_entries].chunk_ = i - chunk_start; + stsc->table_[stsc_entries].samples_ = samples; + stsc->table_[stsc_entries].id_ = id; + ++stsc_entries; + } + } + } + } + chunk_end = i; + stsc->entries_ = stsc_entries; + + { + struct stco_t* stco = trak->mdia_->minf_->stbl_->stco_; + unsigned int entries = 0; + for(i = chunk_start; i != chunk_end; ++i) + { + stco->chunk_offsets_[entries] = stco->chunk_offsets_[i]; + ++entries; + } + stco->entries_ = entries; + + // patch first chunk with correct sample offset + stco->chunk_offsets_[0] = (uint32_t)trak->samples_[start].pos_; + } + } + } + + // process sync samples: + if(trak->mdia_->minf_->stbl_->stss_) + { + struct stss_t* stss = trak->mdia_->minf_->stbl_->stss_; + unsigned int entries = 0; + unsigned int stss_start; + unsigned int i; + + for(i = 0; i != stss->entries_; ++i) + { + if(stss->sample_numbers_[i] >= start + 1) + break; + } + stss_start = i; + for(; i != stss->entries_; ++i) + { + unsigned int sync_sample = stss->sample_numbers_[i]; + if(sync_sample >= end + 1) + break; + stss->sample_numbers_[entries] = sync_sample - start; + ++entries; + } + stss->entries_ = entries; + } + + // process sample sizes + { + struct stsz_t* stsz = trak->mdia_->minf_->stbl_->stsz_; + + if(stsz->sample_size_ == 0) + { + unsigned int entries = 0; + unsigned int i; + for(i = start; i != end; ++i) + { + stsz->sample_sizes_[entries] = stsz->sample_sizes_[i]; + ++entries; + } + stsz->entries_ = entries; + } + } +} + +static struct trak_t* trak_init() +{ + struct trak_t* trak = (struct trak_t*)malloc(sizeof(struct trak_t)); + trak->unknown_atoms_ = 0; + trak->tkhd_ = 0; + trak->mdia_ = 0; + trak->chunks_size_ = 0; + trak->chunks_ = 0; + trak->samples_size_ = 0; + trak->samples_ = 0; + + return trak; +} + +static void trak_exit(struct trak_t* trak) +{ + if(trak->unknown_atoms_) + { + unknown_atom_exit(trak->unknown_atoms_); + } + if(trak->tkhd_) + { + tkhd_exit(trak->tkhd_); + } + if(trak->mdia_) + { + mdia_exit(trak->mdia_); + } + if(trak->chunks_) + { + free(trak->chunks_); + } + if(trak->samples_) + { + free(trak->samples_); + } + free(trak); +} + +static int trak_add_tkhd(void* parent, void* tkhd) +{ + struct trak_t* trak = parent; + trak->tkhd_ = tkhd; + + return 1; +} + +static int trak_add_mdia(void* parent, void* mdia) +{ + struct trak_t* trak = parent; + trak->mdia_ = mdia; + + return 1; +} + +static void* trak_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct trak_t* atom = trak_init(); + + struct atom_read_list_t atom_read_list[] = { + { FOURCC('t', 'k', 'h', 'd'), atom, &trak_add_tkhd, &tkhd_read }, + { FOURCC('m', 'd', 'i', 'a'), atom, &trak_add_mdia, &mdia_read } + }; + + int result = atom_reader(atom_read_list, + sizeof(atom_read_list) / sizeof(atom_read_list[0]), + atom, + buffer, size); + + // check for mandatory atoms + if(!atom->tkhd_) + { + printf("trak: missing tkhd\n"); + result = 0; + } + + if(!atom->mdia_) + { + printf("trak: missing mdia\n"); + result = 0; + } + + if(!result) + { + trak_exit(atom); + return 0; + } + + trak_build_index(atom); + + return atom; +} + +static unsigned char* trak_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct trak_t* trak = atom; + struct atom_write_list_t atom_write_list[] = { + { FOURCC('t', 'k', 'h', 'd'), atom, trak->tkhd_, &tkhd_write }, + { FOURCC('m', 'd', 'i', 'a'), atom, trak->mdia_, &mdia_write } + }; + + buffer = atom_writer(trak->unknown_atoms_, + atom_write_list, + sizeof(atom_write_list) / sizeof(atom_write_list[0]), + buffer); + + return buffer; +} + +void trak_shift_offsets(struct trak_t* trak, int64_t offset) +{ + struct stco_t* stco = trak->mdia_->minf_->stbl_->stco_; + stco_shift_offsets(stco, (int32_t)offset); +} + +void trak_shift_offsets_inplace(struct trak_t* trak, int64_t offset) +{ + void* stco = trak->mdia_->minf_->stbl_->stco_inplace_; + stco_shift_offsets_inplace(stco, (int32_t)offset); +} + +static struct mvhd_t* mvhd_init() +{ + struct mvhd_t* atom = (struct mvhd_t*)malloc(sizeof(struct mvhd_t)); + + return atom; +} + +void mvhd_exit(struct mvhd_t* atom) +{ + free(atom); +} + +static void* mvhd_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + unsigned int i; + + struct mvhd_t* atom = mvhd_init(); + atom->version_ = read_8(buffer + 0); + atom->flags_ = read_24(buffer + 1); + if(atom->version_ == 0) + { + if(size < 108-8) + return 0; + + atom->creation_time_ = read_32(buffer + 4); + atom->modification_time_ = read_32(buffer + 8); + atom->timescale_ = read_32(buffer + 12); + atom->duration_ = read_32(buffer + 16); + buffer += 20; + } + else + { + if(size < 120-8) + return 0; + + atom->creation_time_ = read_64(buffer + 4); + atom->modification_time_ = read_64(buffer + 12); + atom->timescale_ = read_32(buffer + 20); + atom->duration_ = read_64(buffer + 24); + buffer += 32; + } + atom->rate_ = read_32(buffer + 0); + atom->volume_ = read_16(buffer + 4); + atom->reserved1_ = read_16(buffer + 6); + atom->reserved2_[0] = read_32(buffer + 8); + atom->reserved2_[1] = read_32(buffer + 12); + buffer += 16; + + for(i = 0; i != 9; ++i) + { + atom->matrix_[i] = read_32(buffer); + buffer += 4; + } + + for(i = 0; i != 6; ++i) + { + atom->predefined_[i] = read_32(buffer); + buffer += 4; + } + + atom->next_track_id_ = read_32(buffer + 0); + + return atom; +} + +static unsigned char* mvhd_write(void* UNUSED(parent), void* atom, unsigned char* buffer) +{ + struct mvhd_t const* mvhd = atom; + unsigned int i; + + buffer = write_8(buffer, mvhd->version_); + buffer = write_24(buffer, mvhd->flags_); + + if(mvhd->version_ == 0) + { + buffer = write_32(buffer, (uint32_t)mvhd->creation_time_); + buffer = write_32(buffer, (uint32_t)mvhd->modification_time_); + buffer = write_32(buffer, mvhd->timescale_); + buffer = write_32(buffer, (uint32_t)mvhd->duration_); + } + else + { + buffer = write_64(buffer, mvhd->creation_time_); + buffer = write_64(buffer, mvhd->modification_time_); + buffer = write_32(buffer, mvhd->timescale_); + buffer = write_64(buffer, mvhd->duration_); + } + + buffer = write_32(buffer, mvhd->rate_); + buffer = write_16(buffer, mvhd->volume_); + buffer = write_16(buffer, mvhd->reserved1_); + buffer = write_32(buffer, mvhd->reserved2_[0]); + buffer = write_32(buffer, mvhd->reserved2_[1]); + + for(i = 0; i != 9; ++i) + { + buffer = write_32(buffer, mvhd->matrix_[i]); + } + + for(i = 0; i != 6; ++i) + { + buffer = write_32(buffer, mvhd->predefined_[i]); + } + + buffer = write_32(buffer, mvhd->next_track_id_); + + return buffer; +} + +static struct moov_t* moov_init() +{ + struct moov_t* moov = malloc(sizeof(struct moov_t)); + moov->unknown_atoms_ = 0; + moov->mvhd_ = 0; + moov->tracks_ = 0; + + return moov; +} + +static void moov_exit(struct moov_t* atom) +{ + unsigned int i; + if(atom->unknown_atoms_) + { + unknown_atom_exit(atom->unknown_atoms_); + } + if(atom->mvhd_) + { + mvhd_exit(atom->mvhd_); + } + for(i = 0; i != atom->tracks_; ++i) + { + trak_exit(atom->traks_[i]); + } + free(atom); +} + +static int moov_add_mvhd(void* parent, void* mvhd) +{ + struct moov_t* moov = parent; + moov->mvhd_ = mvhd; + + return 1; +} + +static int moov_add_trak(void* parent, void* child) +{ + struct moov_t* moov = parent; + struct trak_t* trak = child; + if(moov->tracks_ == MAX_TRACKS) + { + trak_exit(trak); + return 0; + } + + if(trak->mdia_->hdlr_->handler_type_ != FOURCC('v', 'i', 'd', 'e') && + trak->mdia_->hdlr_->handler_type_ != FOURCC('s', 'o', 'u', 'n')) + { + printf("Trak ignored (handler_type=%c%c%c%c, name=%s)\n", + trak->mdia_->hdlr_->handler_type_ >> 24, + trak->mdia_->hdlr_->handler_type_ >> 16, + trak->mdia_->hdlr_->handler_type_ >> 8, + trak->mdia_->hdlr_->handler_type_, + trak->mdia_->hdlr_->name_); + trak_exit(trak); + return 1; // continue + } + + moov->traks_[moov->tracks_] = trak; + ++moov->tracks_; + + return 1; +} + +static void* moov_read(void* UNUSED(parent), unsigned char* buffer, uint64_t size) +{ + struct moov_t* atom = moov_init(); + + struct atom_read_list_t atom_read_list[] = { + { FOURCC('m', 'v', 'h', 'd'), atom, &moov_add_mvhd, &mvhd_read }, + { FOURCC('t', 'r', 'a', 'k'), atom, &moov_add_trak, &trak_read } + }; + + int result = atom_reader(atom_read_list, + sizeof(atom_read_list) / sizeof(atom_read_list[0]), + atom, + buffer, size); + + // check for mandatory atoms + if(!atom->mvhd_) + { + printf("moov: missing mvhd\n"); + result = 0; + } + + if(!atom->tracks_) + { + printf("moov: missing trak\n"); + result = 0; + } + + if(!result) + { + moov_exit(atom); + return 0; + } + + return atom; +} + +static void moov_write(struct moov_t* atom, unsigned char* buffer) +{ + unsigned i; + + unsigned char* atom_start = buffer; + + struct atom_write_list_t atom_write_list[] = { + { FOURCC('m', 'v', 'h', 'd'), atom, atom->mvhd_, &mvhd_write }, + }; + + // atom size + buffer += 4; + + // atom type + buffer = write_32(buffer, FOURCC('m', 'o', 'o', 'v')); + + buffer = atom_writer(atom->unknown_atoms_, + atom_write_list, + sizeof(atom_write_list) / sizeof(atom_write_list[0]), + buffer); + + for(i = 0; i != atom->tracks_; ++i) + { + struct atom_write_list_t trak_atom_write_list[] = { + { FOURCC('t', 'r', 'a', 'k'), atom, atom->traks_[i], &trak_write }, + }; + buffer = atom_writer(0, + trak_atom_write_list, + sizeof(trak_atom_write_list) / sizeof(trak_atom_write_list[0]), + buffer); + } + write_32(atom_start, buffer - atom_start); +} + +void moov_shift_offsets(struct moov_t* moov, int64_t offset) +{ + unsigned int i; + for(i = 0; i != moov->tracks_; ++i) + { + trak_shift_offsets(moov->traks_[i], offset); + } +} + +void moov_shift_offsets_inplace(struct moov_t* moov, int64_t offset) +{ + unsigned int i; + for(i = 0; i != moov->tracks_; ++i) + { + trak_shift_offsets_inplace(moov->traks_[i], offset); + } +} + +unsigned int moov_seek(unsigned char* moov_data, + uint64_t* moov_size, + float start_time, + float end_time, + uint64_t* mdat_start, + uint64_t* mdat_size, + uint64_t offset, + int client_is_flash) +{ + struct moov_t* moov = moov_read(NULL, moov_data + ATOM_PREAMBLE_SIZE, + *moov_size - ATOM_PREAMBLE_SIZE); + + if(moov == 0 || moov->mvhd_ == 0) + { + printf("Error parsing moov header\n"); + return 0; + } + + { + long moov_time_scale = moov->mvhd_->timescale_; + unsigned int start = (unsigned int)(start_time * moov_time_scale); + unsigned int end = (unsigned int)(end_time * moov_time_scale); + uint64_t skip_from_start = UINT64_MAX; + uint64_t end_offset = 0; + unsigned int i; + unsigned int pass; + + // for every trak, convert seconds to sample (time-to-sample). + // adjust sample to keyframe + unsigned int trak_sample_start[MAX_TRACKS]; + unsigned int trak_sample_end[MAX_TRACKS]; + + uint64_t moov_duration = 0; + + // clayton.mp4 has a third track with one sample that lasts the whole clip. + // Assuming the first two tracks are the audio and video track, we patch + // the remaining tracks to 'free' atoms. +// if(moov->tracks_ > 2) +// { +// for(i = 2; i != moov->tracks_; ++i) +// { +// // patch 'trak' to 'free' +// unsigned char* p = moov->traks_[i].start_ - 4; +// p[0] = 'f'; +// p[1] = 'r'; +// p[2] = 'e'; +// p[3] = 'e'; +// } +// moov->tracks_ = 2; +// } + + // reported by everwanna: + // av out of sync because: + // audio track 0 without stss, seek to the exact time. + // video track 1 with stss, seek to the nearest key frame time. + // + // fixed: + // first pass we get the new aligned times for traks with an stss present + // second pass is for traks without an stss + for(pass = 0; pass != 2; ++pass) + { + for(i = 0; i != moov->tracks_; ++i) + { + struct trak_t* trak = moov->traks_[i]; + struct stbl_t* stbl = trak->mdia_->minf_->stbl_; + long trak_time_scale = trak->mdia_->mdhd_->timescale_; + float moov_to_trak_time = (float)trak_time_scale / (float)moov_time_scale; + float trak_to_moov_time = (float)moov_time_scale / (float)trak_time_scale; + + // 1st pass: stss present, 2nd pass: no stss present + if(pass == 0 && !stbl->stss_) + continue; + if(pass == 1 && stbl->stss_) + continue; + + // ignore empty track + if(trak->mdia_->mdhd_->duration_ == 0) + continue; + + // get start + if(start == 0) + { + trak_sample_start[i] = start; + } + else + { + start = stts_get_sample(stbl->stts_, (uint64_t)(start * moov_to_trak_time)); + printf("start=%u (trac time)=%.2f (seconds)", start, + stts_get_time(stbl->stts_, start) / (float)trak_time_scale); + start = stbl_get_nearest_keyframe(stbl, start + 1) - 1; + printf("=%u (zero based keyframe)", start); + trak_sample_start[i] = start; + start = (unsigned int)(stts_get_time(stbl->stts_, start) * trak_to_moov_time); + printf("=%u (moov time)\n", start); + } + + // get end + if(end == 0) + { + trak_sample_end[i] = trak->samples_size_; + } + else + { + end = stts_get_sample(stbl->stts_, (uint64_t)(end * moov_to_trak_time)); + if(end >= trak->samples_size_) + { + end = trak->samples_size_; + } + else + { + end = stbl_get_nearest_keyframe(stbl, end + 1) - 1; + } + trak_sample_end[i] = end; + printf("endframe=%u, samples_size_=%u\n", end, trak->samples_size_); + end = (unsigned int)(stts_get_time(stbl->stts_, end) * trak_to_moov_time); + } + } + } + + printf("start=%u\n", start); + printf("end=%u\n", end); + + if(end && start >= end) + { + moov_exit(moov); + return 0; + } + + for(i = 0; i != moov->tracks_; ++i) + { + struct trak_t* trak = moov->traks_[i]; + struct stbl_t* stbl = trak->mdia_->minf_->stbl_; + + unsigned int start_sample = trak_sample_start[i]; + unsigned int end_sample = trak_sample_end[i]; + + // ignore empty track + if(trak->mdia_->mdhd_->duration_ == 0) + continue; + + trak_update_index(trak, start_sample, end_sample); + + { + uint64_t skip = + trak->samples_[start_sample].pos_ - trak->samples_[0].pos_; + if(skip < skip_from_start) + skip_from_start = skip; + printf("Trak can skip %llu bytes\n", skip); + + if(end_sample != trak->samples_size_) + { + uint64_t end_pos = trak->samples_[end_sample].pos_; + if(end_pos > end_offset) + end_offset = end_pos; + printf("New endpos=%llu\n", end_pos); + printf("Trak can skip %llu bytes at end\n", + *mdat_start + *mdat_size - end_offset); + } + } + + { + // fixup trak (duration) + uint64_t trak_duration = stts_get_duration(stbl->stts_); + long trak_time_scale = trak->mdia_->mdhd_->timescale_; + float trak_to_moov_time = (float)moov_time_scale / (float)trak_time_scale; + { + uint64_t duration = (long)((float)trak_duration * trak_to_moov_time); + trak->mdia_->mdhd_->duration_= trak_duration; + trak->tkhd_->duration_ = duration; + printf("trak: new_duration=%lld\n", duration); + + if(duration > moov_duration) + moov_duration = duration; + } + } + +// printf("stco.size=%d, ", read_int32(stbl->stco_ + 4)); +// printf("stts.size=%d samples=%d\n", read_int32(stbl->stts_ + 4), stts_get_samples(stbl->stts_)); +// printf("stsz.size=%d\n", read_int32(stbl->stsz_ + 8)); +// printf("stsc.samples=%d\n", stsc_get_samples(stbl->stsc_)); + } + moov->mvhd_->duration_ = moov_duration; + + // subtract bytes we skip at the front of the mdat atom + offset -= skip_from_start; + + // subtract old moov size + offset -= *moov_size; + + printf("moov: writing header\n"); + + moov_write(moov, moov_data); + *moov_size = read_32(moov_data); + + // add new moov size + offset += *moov_size; + + printf("shifting offsets by %lld\n", offset); + moov_shift_offsets_inplace(moov, offset); + +// moov_write(moov, moov_data); + +#ifdef COMPRESS_MOOV_ATOM + if(!client_is_flash) + { + uLong sourceLen = *moov_size - ATOM_PREAMBLE_SIZE; + uLong destLen = compressBound(sourceLen); + unsigned char* cmov = malloc(destLen); + int zstatus = compress(cmov, &destLen, moov_data, sourceLen); + if(zstatus == Z_OK) + { + printf("cmov size = %lu (%ld%%)\n", destLen, 100 * destLen / sourceLen); + } + + { + const int extra_space = 4096; + if(destLen + extra_space < sourceLen) + { + const int bytes_saved = sourceLen - destLen; + uLong destLen2; + int extra = 0; + printf("shifting offsets by %d\n", -bytes_saved); + moov_shift_offsets_inplace(moov, -bytes_saved); + + extra += ATOM_PREAMBLE_SIZE + 4; // dcom + extra += ATOM_PREAMBLE_SIZE + 4; // cmvd + extra += ATOM_PREAMBLE_SIZE; // cmov + extra += ATOM_PREAMBLE_SIZE + extra_space; // free + + printf("shifting offsets by %d\n", extra); + moov_shift_offsets_inplace(moov, extra); + + // recompress + destLen2 = compressBound(sourceLen); + zstatus = compress(cmov, &destLen2, moov_data, sourceLen); + if(zstatus == Z_OK) + { + printf("cmov size = %lu (%ld%%)\n", destLen2, 100 * destLen2 / sourceLen); + + if(destLen2 < destLen + extra_space) + { + // copy compressed movie atom + unsigned char* outbuffer = moov_data; + + uint32_t dcom_size = ATOM_PREAMBLE_SIZE + 4; + uint32_t cmvd_size = ATOM_PREAMBLE_SIZE + 4 + destLen2; + uint32_t cmov_size = ATOM_PREAMBLE_SIZE + dcom_size + cmvd_size; + uint32_t free_size = ATOM_PREAMBLE_SIZE + extra_space + destLen - destLen2; + *moov_size = ATOM_PREAMBLE_SIZE + cmov_size + free_size; + + outbuffer = write_32(outbuffer, (uint32_t)*moov_size); + + // skip 'moov' + outbuffer += 4; + + outbuffer = write_32(outbuffer, cmov_size); + { + outbuffer = write_32(outbuffer, FOURCC('c', 'm', 'o', 'v')); + outbuffer = write_32(outbuffer, dcom_size); + outbuffer = write_32(outbuffer, FOURCC('d', 'c', 'o', 'm')); + outbuffer = write_32(outbuffer, FOURCC('z', 'l', 'i', 'b')); + + outbuffer = write_32(outbuffer, cmvd_size); + { + outbuffer = write_32(outbuffer, FOURCC('c', 'm', 'v', 'd')); + outbuffer = write_32(outbuffer, sourceLen); + memcpy(outbuffer, cmov, destLen2); + outbuffer += destLen2; + } + } + + // add final padding + outbuffer = write_32(outbuffer, free_size); + outbuffer = write_32(outbuffer, FOURCC('f', 'r', 'e', 'e')); + { + const char free_bytes[8] = + { + 'C', 'o', 'd', 'e','S','h', 'o', 'p' + }; + uint32_t padding_index; + for(padding_index = ATOM_PREAMBLE_SIZE; padding_index != free_size; ++padding_index) + { + outbuffer[padding_index] = free_bytes[padding_index % 8]; + } + } + } + else + { + printf("2nd pass compress overflow\n"); + } + } + } + } + free(cmov); + } +#endif + + *mdat_start += skip_from_start; + if(end_offset != 0) + { + *mdat_size = end_offset; + } + *mdat_size -= skip_from_start; + } + + moov_exit(moov); + + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct mp4_atom_t +{ + uint32_t type_; + uint32_t short_size_; + uint64_t size_; + uint64_t start_; + uint64_t end_; +}; + +static int mp4_atom_read_header(FILE* infile, struct mp4_atom_t* atom) +{ + unsigned char atom_header[8]; + + atom->start_ = ftell(infile); + fread(atom_header, 8, 1, infile); + atom->short_size_ = read_32(&atom_header[0]); + atom->type_ = read_32(&atom_header[4]); + + if(atom->short_size_ == 1) + { + fread(atom_header, 8, 1, infile); + atom->size_ = read_64(&atom_header[0]); + } + else + { + atom->size_ = atom->short_size_; + } + + atom->end_ = atom->start_ + atom->size_; + + return 1; +} + +static int mp4_atom_write_header(unsigned char* outbuffer, struct mp4_atom_t* atom) +{ + int write_box64 = atom->short_size_ == 1 ? 1 : 0; + + if(write_box64) + write_32(outbuffer, 1); + else + write_32(outbuffer, (uint32_t)atom->size_); + + write_32(outbuffer + 4, atom->type_); + + if(write_box64) + { + write_64(outbuffer + 8, atom->size_); + return 16; + } + else + { + return 8; + } +} + +int mp4_split(const char* filename, int64_t filesize, + float start_time, float end_time, + void** mp4_header, uint32_t* mp4_header_size, + uint64_t* mdat_offset, uint64_t* mdat_size, + int client_is_flash) +{ + FILE* infile; + struct mp4_atom_t ftyp_atom; + struct mp4_atom_t moov_atom; + struct mp4_atom_t mdat_atom; + unsigned char* moov_data = 0; + unsigned char* buffer; + uint64_t new_mdat_start; + + *mp4_header = 0; + memset(&ftyp_atom, 0, sizeof(ftyp_atom)); + memset(&moov_atom, 0, sizeof(moov_atom)); + memset(&mdat_atom, 0, sizeof(mdat_atom)); + + infile = fopen(filename, "rb"); + if(infile == NULL) + { + return 0; + } + + while(ftello(infile) < filesize) + { + struct mp4_atom_t leaf_atom; + + if(!mp4_atom_read_header(infile, &leaf_atom)) + break; + + printf("Atom(%c%c%c%c,%lld)\n", + leaf_atom.type_ >> 24, leaf_atom.type_ >> 16, + leaf_atom.type_ >> 8, leaf_atom.type_, + leaf_atom.size_); + + switch(leaf_atom.type_) + { + case FOURCC('f', 't', 'y', 'p'): + ftyp_atom = leaf_atom; + break; + case FOURCC('m', 'o', 'o', 'v'): + moov_atom = leaf_atom; + moov_data = (unsigned char*)malloc((size_t)moov_atom.size_); + fseeko(infile, moov_atom.start_, SEEK_SET); + fread(moov_data, (off_t)moov_atom.size_, 1, infile); + break; + case FOURCC('m', 'd', 'a', 't'): + mdat_atom = leaf_atom; + break; + } + fseeko(infile, leaf_atom.end_, SEEK_SET); + } + + if(moov_atom.size_ == 0) + { + printf("Error: moov atom not found\n"); + fclose(infile); + return 0; + } + + if(mdat_atom.size_ == 0) + { + printf("Error: mdat atom not found\n"); + fclose(infile); + return 0; + } + + buffer = (unsigned char*)malloc((uint32_t)moov_atom.size_ + 4 * 1024); + *mp4_header = buffer; + + if(ftyp_atom.size_) + { + fseeko(infile, ftyp_atom.start_, SEEK_SET); + fread(buffer, (off_t)ftyp_atom.size_, 1, infile); + buffer += ftyp_atom.size_; + } + + { + static char const free_data[] = { + 0x0, 0x0, 0x0, 42, 'f', 'r', 'e', 'e', + 'v', 'i', 'd', 'e', 'o', ' ', 's', 'e', + 'r', 'v', 'e', 'd', ' ', 'b', 'y', ' ', + 'm', 'o', 'd', '_', 'h', '2', '6', '4', + '_', 's', 't', 'r', 'e', 'a', 'm', 'i', + 'n', 'g' + }; + memcpy(buffer, free_data, sizeof(free_data)); + buffer += sizeof(free_data); + } + + new_mdat_start = buffer - (unsigned char*)(*mp4_header) + moov_atom.size_; + if(!moov_seek(moov_data, + &moov_atom.size_, + start_time, + end_time, + &mdat_atom.start_, + &mdat_atom.size_, + new_mdat_start - mdat_atom.start_, + client_is_flash)) + { + free(moov_data); + fclose(infile); + return 0; + } + + memcpy(buffer, moov_data, (uint32_t)moov_atom.size_); + buffer += moov_atom.size_; + free(moov_data); + + { + int mdat_header_size = mp4_atom_write_header(buffer, &mdat_atom); + buffer += mdat_header_size; + *mdat_offset = mdat_atom.start_ + mdat_header_size; + *mdat_size = mdat_atom.size_ - mdat_header_size; + } + + *mp4_header_size = (uint32_t)(buffer - (unsigned char*)(*mp4_header)); + + fclose(infile); + + return 1; +} + +// End Of File + --- /dev/null 2008-11-04 20:33:38.146691408 +0200 +++ lighttpd-1.4.18/src/moov.h 2009-01-26 21:00:05.071936866 +0200 @@ -0,0 +1,49 @@ +/******************************************************************************* + moov.h (version 2) + + moov - A library for splitting Quicktime/MPEG4 files. + http://h264.code-shop.com + + Copyright (C) 2007-2009 CodeShop B.V. + + Licensing + The H264 Streaming Module is licened under a Creative Common License. It allows + you to use, modify and redistribute the module, but only for *noncommercial* + purposes. For corporate use, please apply for a commercial license. + + Creative Commons License: + http://creativecommons.org/licenses/by-nc-sa/3.0/ + + Commercial License: + http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-License-Version2 +******************************************************************************/ + +// NOTE: don't include stdio.h (for FILE) or sys/types.h (for off_t). +// nginx redefines _FILE_OFFSET_BITS and off_t will have different sizes +// depending on include order + +#ifndef _MSC_VER +#include +#else +#include "inttypes.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern int mp4_split(const char* infile, int64_t filesize, + float start_time, float end_time, + void** mp4_header, uint32_t* mp4_header_size, + uint64_t* mdat_offset, uint64_t* mdat_size, + int client_is_flash); + +/* Returns true when the test string is a prefix of the input */ +extern int starts_with(const char* input, const char* test); + +#ifdef __cplusplus +} /* extern C definitions */ +#endif + +// End Of File +