From f147a2c5bc5183eac329166d61d1ea20b395c076 Mon Sep 17 00:00:00 2001 From: lieff Date: Thu, 12 Dec 2019 13:35:31 +0300 Subject: [PATCH] basic fragmentation mode support --- .gitignore | 1 + minimp4.h | 444 +++++++++++++++++++++++++++++-------------------- minimp4_test.c | 26 +-- 3 files changed, 280 insertions(+), 191 deletions(-) diff --git a/.gitignore b/.gitignore index 782ce2e..5aecbb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode minimp4_* qemu-prof *.mp4 diff --git a/minimp4.h b/minimp4.h index 7209f9c..8dc99bf 100644 --- a/minimp4.h +++ b/minimp4.h @@ -25,44 +25,32 @@ extern "C" { /* Build configuration */ /************************************************************************/ -// Debug trace -#define MP4D_TRACE_SUPPORTED 0 -#define MP4D_TRACE_TIMESTAMPS 1 - #define FIX_BAD_ANDROID_META_BOX 1 -// Max chunks nesting level -#define MAX_CHUNKS_DEPTH 64 +#define MAX_CHUNKS_DEPTH 64 // Max chunks nesting level #define MINIMP4_MAX_SPS 32 #define MINIMP4_MAX_PPS 256 -#define MINIMP4_TRANSCODE_SPS_ID 1 +#define MINIMP4_TRANSCODE_SPS_ID 1 // Support indexing of MP4 files over 4 GB. // If disabled, files with 64-bit offset fields is still supported, // but error signaled if such field contains too big offset -// This switch affect return type of MP4D__frame_offset() function -#define MINIMP4_ALLOW_64BIT 1 +// This switch affect return type of MP4D_frame_offset() function +#define MINIMP4_ALLOW_64BIT 1 +#define MP4D_TRACE_SUPPORTED 0 // Debug trace +#define MP4D_TRACE_TIMESTAMPS 1 // Support parsing of supplementary information, not necessary for decoding: // duration, language, bitrate, metadata tags, etc -#ifndef MP4D_INFO_SUPPORTED -# define MP4D_INFO_SUPPORTED 1 -#endif +#define MP4D_INFO_SUPPORTED 1 // Enable code, which prints to stdout supplementary MP4 information: -#ifndef MP4D_PRINT_INFO_SUPPORTED -# define MP4D_PRINT_INFO_SUPPORTED MP4D_INFO_SUPPORTED -#endif +#define MP4D_PRINT_INFO_SUPPORTED 0 -#ifndef MP4D_AVC_SUPPORTED -# define MP4D_AVC_SUPPORTED 1 -#endif - -#ifndef MP4D_TIMESTAMPS_SUPPORTED -# define MP4D_TIMESTAMPS_SUPPORTED 1 -#endif +#define MP4D_AVC_SUPPORTED 1 +#define MP4D_TIMESTAMPS_SUPPORTED 1 /************************************************************************/ /* Some values of MP4(E/D)_track_t->object_type_indication */ @@ -92,7 +80,7 @@ extern "C" { #define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4 /************************************************************************/ -/* Sample kind for MP4E__put_sample() */ +/* Sample kind for MP4E_put_sample() */ /************************************************************************/ #define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame #define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame) @@ -356,7 +344,7 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt * It is guaranteed that function will read/seek the file sequentially, * and will never jump back. */ -int MP4D__open(MP4D_demux_t *mp4, FILE *f); +int MP4D_open(MP4D_demux_t *mp4, FILE *f); /** * Return position and size for given sample from given track. The 'sample' is a @@ -368,26 +356,26 @@ int MP4D__open(MP4D_demux_t *mp4, FILE *f); * * function return file offset for the frame */ -MP4D_file_offset_t MP4D__frame_offset(const MP4D_demux_t *mp4, unsigned int ntrack, unsigned int nsample, unsigned int *frame_bytes, unsigned *timestamp, unsigned *duration); +MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned int ntrack, unsigned int nsample, unsigned int *frame_bytes, unsigned *timestamp, unsigned *duration); /** * De-allocated memory */ -void MP4D__close(MP4D_demux_t *mp4); +void MP4D_close(MP4D_demux_t *mp4); /** * Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS * Return pointer to internal mp4 memory, it must not be free()-ed * * Example: process all SPS in MP4 file: -* while (sps = MP4D__read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) +* while (sps = MP4D_read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) * { * process(sps, sps_bytes); * sps_count++; * } */ -const void *MP4D__read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes); -const void *MP4D__read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes); +const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes); +const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes); #if MP4D_PRINT_INFO_SUPPORTED /** @@ -395,7 +383,7 @@ const void *MP4D__read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npp * Uses printf() as well as floating-point functions * Given as implementation example and for test purposes */ -void MP4D__printf_info(const MP4D_demux_t *mp4); +void MP4D_printf_info(const MP4D_demux_t *mp4); #endif // #if MP4D_PRINT_INFO_SUPPORTED /** @@ -407,8 +395,8 @@ void MP4D__printf_info(const MP4D_demux_t *mp4); * * return multiplexor handle on success; NULL on failure */ -MP4E_mux_t *MP4E__open(int sequential_mode_flag, void *token, - void (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)); +MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, + int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)); /** * Add new track @@ -418,19 +406,19 @@ MP4E_mux_t *MP4E__open(int sequential_mode_flag, void *token, * * return ID of added track, or error code MP4E_STATUS_* */ -int MP4E__add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data); +int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data); /** * Add new sample to specified track -* The tracks numbered starting with 0, according to order of MP4E__add_track() calls +* The tracks numbered starting with 0, according to order of MP4E_add_track() calls * 'kind' is one of MP4E_SAMPLE_... defines * * return error code MP4E_STATUS_* * * Example: -* MP4E__put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT); +* MP4E_put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT); */ -int MP4E__put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, int duration, int kind); +int MP4E_put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, int duration, int kind); /** * Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer. @@ -440,7 +428,7 @@ int MP4E__put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, * * return error code MP4E_STATUS_* */ -int MP4E__close(MP4E_mux_t *mux); +int MP4E_close(MP4E_mux_t *mux); /** * Set Decoder Specific Info (DSI) @@ -450,35 +438,35 @@ int MP4E__close(MP4E_mux_t *mux); * * return error code MP4E_STATUS_* */ -int MP4E__set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes); +int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes); /** * Set VPS data. MUST be used for HEVC (H.265) track. * * return error code MP4E_STATUS_* */ -int MP4E__set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes); +int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes); /** * Set SPS data. MUST be used for AVC (H.264) track. Up to 32 different SPS can be used in one track. * * return error code MP4E_STATUS_* */ -int MP4E__set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes); +int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes); /** * Set PPS data. MUST be used for AVC (H.264) track. Up to 256 different PPS can be used in one track. * * return error code MP4E_STATUS_* */ -int MP4E__set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes); +int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes); /** * Set or replace ASCII test comment for the file. Set comment to NULL to remove comment. * * return error code MP4E_STATUS_* */ -int MP4E__set_text_comment(MP4E_mux_t *mux, const char *comment); +int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment); #ifdef __cplusplus @@ -585,6 +573,7 @@ enum BOX_traf = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ),//TrackFragmentAtomType BOX_tfhd = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ),//TrackFragmentHeaderAtomType BOX_trun = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ),//TrackFragmentRunAtomType + BOX_mehd = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),//MovieExtendsHeaderBox // Object Descriptors (OD) data coding // These takes only 1 byte; this implementation translate to @@ -689,12 +678,13 @@ typedef struct MP4E_mux_tag void *token; int64_t write_pos; - int64_t mdat_pos; - void(*write_callback)(int64_t offset, const void *buffer, size_t size, void *token); + int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token); char *text_comment; int sequential_mode_flag; + int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode + int fragments_count; // # of fragments in 'fragmentation' mode } MP4E_mux_t; @@ -784,15 +774,11 @@ static unsigned char *minimp4_vector_alloc_tail(minimp4_vector_t *h, int bytes) { unsigned char *p; if (!h->data && !minimp4_vector_init(h, 2*bytes + 1024)) - { return NULL; - } - if (h->capacity - h->bytes < bytes && !minimp4_vector_grow(h, bytes)) - { + if ((h->capacity - h->bytes) < bytes && !minimp4_vector_grow(h, bytes)) return NULL; - } assert(h->data); - assert(h->capacity - h->bytes >= bytes); + assert((h->capacity - h->bytes) >= bytes); p = h->data + h->bytes; h->bytes += bytes; return p; @@ -805,9 +791,7 @@ static unsigned char *minimp4_vector_put(minimp4_vector_t *h, const void *buf, i { unsigned char *tail = minimp4_vector_alloc_tail(h, bytes); if (tail) - { memcpy(tail, buf, bytes); - } return tail; } @@ -836,33 +820,27 @@ static void mp4e_free(MP4E_mux_t *mux) * Allocates and initialize mp4 multiplexer * return multiplexor handle on success; NULL on failure */ -MP4E_mux_t *MP4E__open(int sequential_mode_flag, void *token, - void(*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)) +MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, + int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)) { - MP4E_mux_t *mux; + if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) // Write fixed header: 'ftyp' box + return 0; + MP4E_mux_t *mux = (MP4E_mux_t*)malloc(sizeof(MP4E_mux_t)); + if (!mux) + return mux; + mux->sequential_mode_flag = sequential_mode_flag || enable_fragmentation; + mux->enable_fragmentation = enable_fragmentation; + mux->fragments_count = 0; + mux->write_callback = write_callback; + mux->token = token; + mux->text_comment = NULL; + mux->write_pos = sizeof(box_ftyp); + minimp4_vector_init(&mux->tracks, 2*sizeof(track_t)); - mux = (MP4E_mux_t*)malloc(sizeof(MP4E_mux_t)); - if (mux) - { - mux->sequential_mode_flag = sequential_mode_flag; - mux->write_callback = write_callback; - mux->token = token; - minimp4_vector_init(&mux->tracks, 2*sizeof(track_t)); - mux->text_comment = NULL; - - // Write fixed header: 'ftyp' box - mux->write_callback(0, box_ftyp, sizeof(box_ftyp), mux->token); - mux->write_pos = sizeof(box_ftyp); - - if (!mux->sequential_mode_flag) - { - // Save position, which would be updated with mdat box size - mux->mdat_pos = mux->write_pos; - - // Write filler, which would be updated later - mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token); - mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding - } + if (!mux->sequential_mode_flag) + { // Write filler, which would be updated later + mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token); + mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding } return mux; } @@ -870,27 +848,21 @@ MP4E_mux_t *MP4E__open(int sequential_mode_flag, void *token, /** * Add new track */ -int MP4E__add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data) +int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data) { track_t *tr; int ntr = mux->tracks.bytes / sizeof(track_t); if (!mux || !track_data) - { return MP4E_STATUS_BAD_ARGUMENTS; - } tr = (track_t*)minimp4_vector_alloc_tail(&mux->tracks, sizeof(track_t)); if (!tr) - { return MP4E_STATUS_NO_MEMORY; - } memset(tr, 0, sizeof(track_t)); memcpy(&tr->info, track_data, sizeof(*track_data)); if (!minimp4_vector_init(&tr->smpl, 256)) - { return MP4E_STATUS_NO_MEMORY; - } minimp4_vector_init(&tr->vsps, 0); minimp4_vector_init(&tr->vpps, 0); minimp4_vector_init(&tr->pending_sample, 0); @@ -904,9 +876,7 @@ static const unsigned char *next_dsi(const unsigned char *p, const unsigned char *bytes = p[0]*256 + p[1]; return p + 2; } else - { return NULL; - } } static int append_mem(minimp4_vector_t *v, const void *mem, int bytes) @@ -941,7 +911,7 @@ static int items_count(minimp4_vector_t *v) return count; } -int MP4E__set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes) +int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_audio || @@ -953,21 +923,21 @@ int MP4E__set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes) return append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E__set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes) +int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vvps, vps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E__set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes) +int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E__set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes) +int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); @@ -1027,40 +997,147 @@ static int add_sample_descriptor(MP4E_mux_t *mux, track_t *tr, int data_bytes, i return NULL != minimp4_vector_put(&tr->smpl, &smp, sizeof(sample_t)); } +static int mp4e_flush_index(MP4E_mux_t *mux); + +/** +* Write Movie Fragment: 'moof' box +*/ +static int mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int duration, int kind) +{ + unsigned char base[888], *p = base; + unsigned char *stack_base[20]; // atoms nesting stack + unsigned char **stack = stack_base; + unsigned char *pdata_offset; + unsigned flags; + enum + { + default_sample_duration_present = 0x000008, + default_sample_flags_present = 0x000020, + } e; + + track_t *tr = ((track_t*)mux->tracks.data) + track_num; + + ATOM(BOX_moof) + ATOM_FULL(BOX_mfhd, 0) + WRITE_4(mux->fragments_count); // start from 1 + END_ATOM + ATOM(BOX_traf) + flags = 0; + if (tr->info.track_media_kind == e_video) + flags |= 0x20; // default-sample-flags-present + else + flags |= 0x08; // default-sample-duration-present + flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008; + + ATOM_FULL(BOX_tfhd, flags) + WRITE_4(track_num + 1); // track_ID + if (tr->info.track_media_kind == e_video) + { + flags = 0x001; // data-offset-present + flags |= 0x100; // sample-duration-present + WRITE_4(0x1010000); // default_sample_flags + } else + { + WRITE_4(duration); + } + END_ATOM + if (tr->info.track_media_kind == e_audio) + { + flags = 0; + flags |= 0x001; // data-offset-present + flags |= 0x200; // sample-size-present + ATOM_FULL(BOX_trun, flags) + WRITE_4(1); // sample_count + pdata_offset = p; p += 4; // save ptr to data_offset + WRITE_4(duration); // sample_duration + END_ATOM + } else if (kind == MP4E_SAMPLE_RANDOM_ACCESS) + { + flags = 0; + flags |= 0x001; // data-offset-present + flags |= 0x004; // first-sample-flags-present + flags |= 0x100; // sample-duration-present + flags |= 0x200; // sample-size-present + ATOM_FULL(BOX_trun, flags) + WRITE_4(1); // sample_count + pdata_offset = p; p += 4; // save ptr to data_offset + WRITE_4(0x2000000); // first_sample_flags + WRITE_4(duration); // sample_duration + WRITE_4(data_bytes);// sample_size + END_ATOM + } else + { + flags = 0; + flags |= 0x001; // data-offset-present + flags |= 0x100; // sample-duration-present + flags |= 0x200; // sample-size-present + ATOM_FULL(BOX_trun, flags) + WRITE_4(1); // sample_count + pdata_offset = p; p += 4; // save ptr to data_offset + WRITE_4(duration); // sample_duration + WRITE_4(data_bytes);// sample_size + END_ATOM + } + END_ATOM + END_ATOM + WR4(pdata_offset, (p - base) + 8); + + mux->write_callback(mux->write_pos, base, p - base, mux->token); + mux->write_pos += p - base; + return MP4E_STATUS_OK; +} + +static void mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size) +{ + unsigned char base[8], *p = base; + WRITE_4(size); + WRITE_4(BOX_mdat); + mux->write_callback(mux->write_pos, base, p - base, mux->token); + mux->write_pos += p - base; +} + /** * Add new sample to specified track */ -int MP4E__put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, int duration, int kind) +int MP4E_put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, int duration, int kind) { track_t *tr; if (!mux || !data) - { return MP4E_STATUS_BAD_ARGUMENTS; - } tr = ((track_t*)mux->tracks.data) + track_num; + if (mux->enable_fragmentation) + { + if (!mux->fragments_count++) + { + int error_code = mp4e_flush_index(mux); // write file headers before 1st sample + if (error_code) + return error_code; + } + // write MOOF + MDAT + sample data + mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind); + // write MDAT box for each sample + mp4e_write_mdat_box(mux, data_bytes + 8); + mux->write_callback(mux->write_pos, data, data_bytes, mux->token); + mux->write_pos += data_bytes; + return MP4E_STATUS_OK; + } + if (kind != MP4E_SAMPLE_CONTINUATION) { if (mux->sequential_mode_flag) - { write_pending_data(mux, tr); - } if (!add_sample_descriptor(mux, tr, data_bytes, duration, kind)) - { return MP4E_STATUS_NO_MEMORY; - } } else { if (!mux->sequential_mode_flag) { sample_t *smpl_desc; if (tr->smpl.bytes < sizeof(sample_t)) - { - // write continuation, but there are no samples in the index - return MP4E_STATUS_NO_MEMORY; - } + return MP4E_STATUS_NO_MEMORY; // write continuation, but there are no samples in the index // Accumulate size of the continuation in the sample descriptor - smpl_desc = ((sample_t*)minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1; + smpl_desc = (sample_t*)(tr->smpl.data + tr->smpl.bytes) - 1; smpl_desc->size += data_bytes; } } @@ -1068,15 +1145,12 @@ int MP4E__put_sample(MP4E_mux_t *mux, int track_num, void *data, int data_bytes, if (mux->sequential_mode_flag) { if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes)) - { return MP4E_STATUS_NO_MEMORY; - } } else { mux->write_callback(mux->write_pos, data, data_bytes, mux->token); mux->write_pos += data_bytes; } - return MP4E_STATUS_OK; } @@ -1097,44 +1171,29 @@ static int od_size_of_size(int size) * http://atomicparsley.sourceforge.net/mpeg-4files.html * note that ISO did not specify comment format. */ -int MP4E__set_text_comment(MP4E_mux_t *mux, const char *comment) +int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment) { - if (!mux) + if (!mux || !comment) return MP4E_STATUS_BAD_ARGUMENTS; if (mux->text_comment) free(mux->text_comment); - mux->text_comment = NULL; - if (comment) - { - mux->text_comment = (char*)malloc(strlen(comment) + 1); - if (mux->text_comment) - { - strcpy(mux->text_comment, comment); - } - } + mux->text_comment = strdup(comment); + if (!mux->text_comment) + return MP4E_STATUS_NO_MEMORY; return MP4E_STATUS_OK; } /** -* Closes MP4 multiplexer +* Write file index 'moov' box with all its boxes and indexes */ -int MP4E__close(MP4E_mux_t *mux) +static int mp4e_flush_index(MP4E_mux_t *mux) { - // atoms nesting stack - unsigned char *stack_base[20]; + unsigned char *stack_base[20]; // atoms nesting stack unsigned char **stack = stack_base; - - // in-memory indexes unsigned char *base, *p; - unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t); int i; - if (!mux) - { - return MP4E_STATUS_BAD_ARGUMENTS; - } - // How much memory needed for indexes // Experimental data: // file with 1 track = 560 bytes @@ -1187,13 +1246,11 @@ int MP4E__close(MP4E_mux_t *mux) WRITE_4(size - 8); WRITE_4(BOX_mdat); } - mux->write_callback(sizeof(box_ftyp), base, p-base, mux->token); + mux->write_callback(sizeof(box_ftyp), base, p - base, mux->token); p = base; } - // // Write index atoms; order taken from Table 1 of [1] - // #define MOOV_TIMESCALE 1000 ATOM(BOX_moov); ATOM_FULL(BOX_mvhd, 0); @@ -1239,10 +1296,10 @@ int MP4E__close(MP4E_mux_t *mux) unsigned handler_type; const char *handler_ascii = NULL; - if (samples_count <= 0) - { + if (mux->enable_fragmentation) + samples_count = 0; + else if (samples_count <= 0) continue; // skip empty track - } switch (tr->info.track_media_kind) { @@ -1517,10 +1574,16 @@ int MP4E__close(MP4E_mux_t *mux) // Sample To Chunk Box ATOM_FULL(BOX_stsc, 0); - WRITE_4(1); // entry_count - WRITE_4(1); // first_chunk; - WRITE_4(1); // samples_per_chunk; - WRITE_4(1); // sample_description_index; + if (mux->enable_fragmentation) + { + WRITE_4(0); // entry_count + } else + { + WRITE_4(1); // entry_count + WRITE_4(1); // first_chunk; + WRITE_4(1); // samples_per_chunk; + WRITE_4(1); // sample_description_index; + } END_ATOM; // Sample Size Box @@ -1612,23 +1675,51 @@ int MP4E__close(MP4E_mux_t *mux) END_ATOM; END_ATOM; } + + if (mux->enable_fragmentation) + { + track_t *tr = ((track_t*)mux->tracks.data) + 0; + uint32_t movie_duration = get_duration(tr); + + ATOM(BOX_mvex); + ATOM_FULL(BOX_mehd, 0); + WRITE_4(movie_duration); // duration + END_ATOM; + for (ntr = 0; ntr < ntracks; ntr++) + { + ATOM_FULL(BOX_trex, 0); + WRITE_4(ntr + 1); // track_ID + WRITE_4(1); // default_sample_description_index + WRITE_4(0); // default_sample_duration + WRITE_4(0); // default_sample_size + WRITE_4(0); // default_sample_flags + END_ATOM; + } + END_ATOM; + } END_ATOM; // moov atom assert((unsigned)(p - base) <= index_bytes); mux->write_callback(mux->write_pos, base, p - base, mux->token); - mux->write_pos += p - base; free(base); - if (mux->text_comment) - { - free(mux->text_comment); - } - mp4e_free(mux); - return MP4E_STATUS_OK; } +int MP4E_close(MP4E_mux_t *mux) +{ + int err = MP4E_STATUS_OK; + if (!mux) + return MP4E_STATUS_BAD_ARGUMENTS; + if (!mux->enable_fragmentation) + err = mp4e_flush_index(mux); + if (mux->text_comment) + free(mux->text_comment); + mp4e_free(mux); + return err; +} + #if MINIMP4_TRANSCODE_SPS_ID typedef uint32_t bs_item_t; @@ -1820,15 +1911,11 @@ static int find_mem_cache(void *cache[], int cache_bytes[], int cache_size, void { int i; if (!bytes) - { return -1; - } for (i = 0; i < cache_size; i++) { if (cache_bytes[i] == bytes && !memcmp(mem, cache[i], bytes)) - { return i; // found - } } for (i = 0; i < cache_size; i++) { @@ -1871,9 +1958,7 @@ static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int //return 0; } } else - { return 0; - } } dst[i++] = src[j]; if (src[j]) @@ -2142,7 +2227,7 @@ int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int he tr.default_duration = 0; tr.u.v.width = width; tr.u.v.height = height; - h->mux_track_id = MP4E__add_track(mux, &tr); + h->mux_track_id = MP4E_add_track(mux, &tr); h->mux = mux; h->is_hevc = is_hevc; @@ -2173,15 +2258,15 @@ static void mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, i switch (payload_type) { case HEVC_NAL_VPS: - MP4E__set_vps(h->mux, h->mux_track_id, nal, sizeof_nal); + MP4E_set_vps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_vps = 0; break; case HEVC_NAL_SPS: - MP4E__set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); + MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_sps = 0; break; case HEVC_NAL_PPS: - MP4E__set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); + MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_pps = 0; break; default: @@ -2201,7 +2286,7 @@ static void mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, i { sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; } - MP4E__put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); + MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } } @@ -2252,13 +2337,13 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt switch (payload_type) { case 7: - MP4E__set_sps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); + MP4E_set_sps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_sps = 0; break; case 8: if (h->need_sps) return 0; - MP4E__set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); + MP4E_set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_pps = 0; break; case 5: @@ -2284,7 +2369,7 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; } prev_payload_type = payload_type; - MP4E__put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind); + MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind); } break; } @@ -2297,11 +2382,11 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt payload_type = nal[0] & 31; switch (payload_type) { case 7: - MP4E__set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); + MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_sps = 0; break; case 8: - MP4E__set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); + MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_pps = 0; break; case 5: @@ -2324,14 +2409,11 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt tmp[3] = (unsigned char)(sizeof_nal); memcpy(tmp + 4, nal, sizeof_nal); if (payload_type == prev_payload_type) - { sample_kind = MP4E_SAMPLE_CONTINUATION; - } else if (payload_type == 5) - { + else if (payload_type == 5) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; - } prev_payload_type = payload_type; - MP4E__put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); + MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } } @@ -2428,23 +2510,23 @@ static void my_fseek(FILE *f, boxsize_t pos, int *eof_flag) #define RETURN_ERROR(mess) { \ TRACE(("\nMP4 ERROR: " mess)); \ fseek(f, 0, SEEK_SET); \ - MP4D__close(mp4); \ + MP4D_close(mp4); \ return 0; \ } /* * Any errors, occurred on top-level hierarchy is passed to exit check: 'if (!mp4->track_count) ... ' */ -#define ERROR(mess) \ - if (!depth) \ - break; \ - else \ +#define ERROR(mess) \ + if (!depth) \ + break; \ + else \ RETURN_ERROR(mess); -typedef enum {BOX_ATOM, BOX_OD} boxtype_t; +typedef enum { BOX_ATOM, BOX_OD } boxtype_t; // Exported API function -int MP4D__open(MP4D_demux_t *mp4, FILE *f) +int MP4D_open(MP4D_demux_t *mp4, FILE *f) { // box stack size int depth = 0; @@ -3143,7 +3225,7 @@ static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_ } // Exported API function -MP4D_file_offset_t MP4D__frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, unsigned *frame_bytes, unsigned *timestamp, unsigned *duration) +MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, unsigned *frame_bytes, unsigned *timestamp, unsigned *duration) { MP4D_track_t *tr = mp4->track + ntrack; unsigned ns; @@ -3187,7 +3269,7 @@ MP4D_file_offset_t MP4D__frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, #define FREE(x) if (x) {free(x); x = NULL;} // Exported API function -void MP4D__close(MP4D_demux_t *mp4) +void MP4D_close(MP4D_demux_t *mp4) { while (mp4->track_count) { @@ -3228,7 +3310,7 @@ static int skip_spspps(const unsigned char *p, int nbytes, int nskip) return k; } -static const void *MP4D__read_spspps(const MP4D_demux_t *mp4, unsigned int ntrack, int pps_flag, int nsps, int *sps_bytes) +static const void *MP4D_read_spspps(const MP4D_demux_t *mp4, unsigned int ntrack, int pps_flag, int nsps, int *sps_bytes) { int sps_count, skip_bytes; int bytepos = 0; @@ -3271,14 +3353,14 @@ static const void *MP4D__read_spspps(const MP4D_demux_t *mp4, unsigned int ntrac } -const void *MP4D__read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes) +const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes) { - return MP4D__read_spspps(mp4, ntrack, 0, nsps, sps_bytes); + return MP4D_read_spspps(mp4, ntrack, 0, nsps, sps_bytes); } -const void *MP4D__read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes) +const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes) { - return MP4D__read_spspps(mp4, ntrack, 1, npps, pps_bytes); + return MP4D_read_spspps(mp4, ntrack, 1, npps, pps_bytes); } #if MP4D_PRINT_INFO_SUPPORTED @@ -3380,7 +3462,7 @@ No|type|lng| duration | bitrate| Stream type | Object type 0|mdir|und| 92.42 s 3980 frm| 128000| AudioStream | Audio ISO/IEC 14496-3MP4 FILE: 1 tracks found. Movie time 92.42 sec */ -void MP4D__printf_info(const MP4D_demux_t *mp4) +void MP4D_printf_info(const MP4D_demux_t *mp4) { unsigned i; printf("\nMP4 FILE: %d tracks found. Movie time %.2f sec\n", mp4->track_count, (4294967296.0*mp4->duration_hi + mp4->duration_lo) / mp4->timescale); diff --git a/minimp4_test.c b/minimp4_test.c index eb5bb86..21a4091 100644 --- a/minimp4_test.c +++ b/minimp4_test.c @@ -51,11 +51,11 @@ static ssize_t get_nal_size(uint8_t *buf, ssize_t size) return size; } -static void write_callback(int64_t offset, const void *buffer, size_t size, void *token) +static int write_callback(int64_t offset, const void *buffer, size_t size, void *token) { FILE *f = (FILE*)token; fseek(f, offset, SEEK_SET); - fwrite(buffer, size, 1, f); + return fwrite(buffer, 1, size, f) != size; } int main(int argc, char **argv) @@ -83,9 +83,10 @@ int main(int argc, char **argv) int is_hevc = (0 != strstr(argv[1], "265")) || (0 != strstr(argv[1], "hevc")); int sequential_mode = 0; + int enable_fragmentation = 0; MP4E_mux_t *mux; mp4_h26x_writer_t mp4wr; - mux = MP4E__open(sequential_mode, fout, write_callback); + mux = MP4E_open(sequential_mode, enable_fragmentation, fout, write_callback); mp4_h26x_write_init(&mp4wr, mux, 352, 288, is_hevc); #if ENABLE_AUDIO @@ -120,21 +121,26 @@ int main(int argc, char **argv) tr.time_scale = 90000; tr.default_duration = 0; tr.u.a.channelcount = 1; - int audio_track_id = MP4E__add_track(mux, &tr); - MP4E__set_dsi(mux, audio_track_id, info.confBuf, info.confSize); + int audio_track_id = MP4E_add_track(mux, &tr); + MP4E_set_dsi(mux, audio_track_id, info.confBuf, info.confSize); #endif while (h264_size > 0) { ssize_t nal_size = get_nal_size(buf_h264, h264_size); - //printf("nal size=%ld, rest=%ld\n", nal_size, h264_size); - if (!nal_size) + if (nal_size < 4) { buf_h264 += 1; h264_size -= 1; continue; } + /*int startcode_size = 4; + if (buf_h264[0] == 0 && buf_h264[1] == 0 && buf_h264[2] == 1) + startcode_size = 3; + int nal_type = buf_h264[startcode_size] & 31; + int is_intra = (nal_type == 5); + printf("nal size=%ld, nal_type=%d\n", nal_size, nal_type);*/ - mp4_h26x_write_nal(&mp4wr, buf_h264, nal_size, 90000/VIDEO_FPS); + int ret = mp4_h26x_write_nal(&mp4wr, buf_h264, nal_size, 90000/VIDEO_FPS); buf_h264 += nal_size; h264_size -= nal_size; @@ -181,13 +187,13 @@ int main(int argc, char **argv) total_samples -= in_args.numInSamples; ats = (uint64_t)sample*90000/AUDIO_RATE; - MP4E__put_sample(mux, audio_track_id, buf, out_args.numOutBytes, 1024*90000/AUDIO_RATE, MP4E_SAMPLE_RANDOM_ACCESS); + MP4E_put_sample(mux, audio_track_id, buf, out_args.numOutBytes, 1024*90000/AUDIO_RATE, MP4E_SAMPLE_RANDOM_ACCESS); } #endif } if (alloc_buf) free(alloc_buf); - MP4E__close(mux); + MP4E_close(mux); if (fout) fclose(fout); }