/* * Copyright (C) 2013 nu774 * For conditions of distribution and use, see copyright notice in COPYING */ #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #if HAVE_STDINT_H # include #endif #if HAVE_INTTYPES_H # include #elif defined _MSC_VER # define PRId64 "I64d" #endif #include "m4af.h" #include "m4af_endian.h" #define m4af_realloc(memory,size) realloc(memory, size) #define m4af_free(memory) free(memory) #define m4af_max(a,b) ((a)<(b)?(b):(a)) #define M4AF_ATOM_WILD 0xffffffff typedef struct m4af_sample_entry_t { uint32_t size; uint32_t delta; } m4af_sample_entry_t; typedef struct m4af_chunk_entry_t { int64_t offset; uint32_t size; uint32_t samples_per_chunk; uint32_t duration; } m4af_chunk_entry_t; typedef struct m4af_itmf_entry_t { uint32_t fcc; char *name; uint32_t type_code; char *data; uint32_t data_size; } m4af_itmf_entry_t; typedef struct m4af_track_t { uint32_t codec; uint32_t timescale; uint16_t num_channels; int64_t creation_time; int64_t modification_time; int64_t duration; uint32_t frame_duration; uint32_t encoder_delay; uint32_t padding; uint8_t *decSpecificInfo; uint32_t decSpecificInfoSize; uint32_t bufferSizeDB; uint32_t maxBitrate; uint32_t avgBitrate; int is_vbr; m4af_sample_entry_t *sample_table; uint32_t num_samples; uint32_t sample_table_capacity; m4af_chunk_entry_t *chunk_table; uint32_t num_chunks; uint32_t chunk_table_capacity; uint8_t *chunk_buffer; uint32_t chunk_size; uint32_t chunk_capacity; /* temporary, to help parsing */ uint64_t stsc_pos; uint64_t stsc_size; uint64_t stts_pos; uint64_t stts_size; } m4af_track_t; struct m4af_ctx_t { uint32_t timescale; int64_t creation_time; int64_t modification_time; int64_t mdat_pos; int64_t mdat_size; int priming_mode; int last_error; m4af_itmf_entry_t *itmf_table; uint32_t num_tags; uint32_t itmf_table_capacity; m4af_io_callbacks_t io; void *io_cookie; uint16_t num_tracks; m4af_track_t track[2]; m4af_itmf_entry_t current_tag; }; typedef struct m4af_box_parser_t { uint32_t name; int (*handler)(m4af_ctx_t *ctx, uint32_t name, uint64_t size); } m4af_box_parser_t; static int m4af_write_null_cb(void *cookie, const void *data, uint32_t size) { int64_t *pos = cookie; *pos += size; return 0; } static int m4af_seek_null_cb(void *cookie, int64_t off, int whence) { int64_t *pos = cookie; *pos = off; /* XXX: we use only SEEK_SET */ return 0; } static int64_t m4af_tell_null_cb(void *cookie) { return *((int64_t*)cookie); } static m4af_io_callbacks_t m4af_null_io_callbacks = { 0, m4af_write_null_cb, m4af_seek_null_cb, m4af_tell_null_cb }; static int64_t m4af_timestamp(void) { return (int64_t)(time(0)) + (((1970 - 1904) * 365) + 17) * 24 * 60 * 60; } /* * http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */ static uint32_t m4af_roundup(uint32_t n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n++; return n; } static int64_t m4af_tell(m4af_ctx_t *ctx) { int64_t pos = -1; if ((pos = ctx->io.tell(ctx->io_cookie)) < 0) ctx->last_error = M4AF_IO_ERROR; return pos; } static int m4af_set_pos(m4af_ctx_t *ctx, int64_t pos) { int rc = -1; if ((rc = ctx->io.seek(ctx->io_cookie, pos, SEEK_SET)) < 0) ctx->last_error = M4AF_IO_ERROR; return rc; } static int m4af_write(m4af_ctx_t *ctx, const void *data, uint32_t size) { int rc = -1; if ((rc = ctx->io.write(ctx->io_cookie, data, size)) < 0) ctx->last_error = M4AF_IO_ERROR; return rc; } static int m4af_write16(m4af_ctx_t *ctx, uint32_t data) { data = m4af_htob16(data); return m4af_write(ctx, &data, 2); } static int m4af_write32(m4af_ctx_t *ctx, uint32_t data) { data = m4af_htob32(data); return m4af_write(ctx, &data, 4); } static int m4af_write64(m4af_ctx_t *ctx, uint64_t data) { data = m4af_htob64(data); return m4af_write(ctx, &data, 8); } static int m4af_write24(m4af_ctx_t *ctx, uint32_t data) { data = m4af_htob32(data << 8); return m4af_write(ctx, &data, 3); } static void m4af_write32_at(m4af_ctx_t *ctx, int64_t pos, uint32_t value) { int64_t current_pos = m4af_tell(ctx); m4af_set_pos(ctx, pos); m4af_write32(ctx, value); m4af_set_pos(ctx, current_pos); } m4af_ctx_t *m4af_create(uint32_t codec, uint32_t timescale, m4af_io_callbacks_t *io, void *io_cookie, int no_timestamp) { m4af_ctx_t *ctx; int64_t timestamp; if (codec != M4AF_FOURCC('m','p','4','a') && codec != M4AF_FOURCC('a','l','a','c')) return 0; if ((ctx = m4af_realloc(0, sizeof(m4af_ctx_t))) == 0) return 0; memset(ctx, 0, sizeof(m4af_ctx_t)); memcpy(&ctx->io, io, sizeof(m4af_io_callbacks_t)); ctx->io_cookie = io_cookie; ctx->timescale = timescale; timestamp = no_timestamp ? 0 : m4af_timestamp(); ctx->creation_time = timestamp; ctx->modification_time = timestamp; ctx->num_tracks = 1; ctx->track[0].codec = codec; ctx->track[0].timescale = timescale; ctx->track[0].creation_time = timestamp; ctx->track[0].modification_time = timestamp; ctx->track[0].num_channels = 2; return ctx; } static void m4af_free_itmf_table(m4af_ctx_t *ctx) { uint32_t i; m4af_itmf_entry_t *entry = ctx->itmf_table; for (i = 0; i < ctx->num_tags; ++i, ++entry) { if (entry->fcc == M4AF_FOURCC('-','-','-','-')) m4af_free(entry->name); m4af_free(entry->data); } m4af_free(ctx->itmf_table); } static void m4af_clear_track(m4af_ctx_t *ctx, int track_idx) { m4af_track_t *track = ctx->track + track_idx; if (track->decSpecificInfo) m4af_free(track->decSpecificInfo); if (track->sample_table) m4af_free(track->sample_table); if (track->chunk_table) m4af_free(track->chunk_table); if (track->chunk_buffer) m4af_free(track->chunk_buffer); memset(track, 0, sizeof(m4af_track_t)); } void m4af_teardown(m4af_ctx_t **ctxp) { unsigned i; m4af_ctx_t *ctx = *ctxp; for (i = 0; i < ctx->num_tracks; ++i) m4af_clear_track(ctx, i); if (ctx->itmf_table) m4af_free_itmf_table(ctx); m4af_free(ctx); *ctxp = 0; } void m4af_set_num_channels(m4af_ctx_t *ctx, uint32_t track_idx, uint16_t channels) { ctx->track[track_idx].num_channels = channels; } void m4af_set_fixed_frame_duration(m4af_ctx_t *ctx, uint32_t track_idx, uint32_t length) { ctx->track[track_idx].frame_duration = length; } int m4af_set_decoder_specific_info(m4af_ctx_t *ctx, uint32_t track_idx, uint8_t *data, uint32_t size) { m4af_track_t *track = &ctx->track[track_idx]; if (size > track->decSpecificInfoSize) { uint8_t *memory = m4af_realloc(track->decSpecificInfo, size); if (memory == 0) { ctx->last_error = M4AF_NO_MEMORY; goto DONE; } track->decSpecificInfo = memory; } if (size > 0) memcpy(track->decSpecificInfo, data, size); track->decSpecificInfoSize = size; DONE: return ctx->last_error; } void m4af_set_vbr_mode(m4af_ctx_t *ctx, uint32_t track_idx, int is_vbr) { m4af_track_t *track = &ctx->track[track_idx]; track->is_vbr = is_vbr; } void m4af_set_priming(m4af_ctx_t *ctx, uint32_t track_idx, uint32_t encoder_delay, uint32_t padding) { m4af_track_t *track = &ctx->track[track_idx]; track->encoder_delay = encoder_delay; track->padding = padding; } void m4af_set_priming_mode(m4af_ctx_t *ctx, int mode) { ctx->priming_mode = mode; } static int m4af_add_sample_entry(m4af_ctx_t *ctx, uint32_t track_idx, uint32_t size, uint32_t delta) { m4af_track_t *track = &ctx->track[track_idx]; m4af_sample_entry_t *entry; if (ctx->last_error) return -1; if (track->num_samples == track->sample_table_capacity) { uint32_t new_size = track->sample_table_capacity; new_size = new_size ? new_size * 2 : 1; entry = m4af_realloc(track->sample_table, new_size * sizeof(*entry)); if (entry == 0) { ctx->last_error = M4AF_NO_MEMORY; return -1; } track->sample_table = entry; track->sample_table_capacity = new_size; } entry = track->sample_table + track->num_samples; entry->size = size; entry->delta = delta; ++track->num_samples; return 0; } static int m4af_flush_chunk(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_chunk_entry_t *entry; if (!track->num_chunks || !track->chunk_size) return 0; entry = &track->chunk_table[track->num_chunks - 1]; entry->offset = m4af_tell(ctx); m4af_write(ctx, track->chunk_buffer, track->chunk_size); ctx->mdat_size += track->chunk_size; track->chunk_size = 0; return ctx->last_error ? -1 : 0; } static int m4af_add_chunk_entry(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_chunk_entry_t *entry; if (track->num_chunks == track->chunk_table_capacity) { uint32_t new_size = track->chunk_table_capacity; new_size = new_size ? new_size * 2 : 1; entry = m4af_realloc(track->chunk_table, new_size * sizeof(*entry)); if (entry == 0) { ctx->last_error = M4AF_NO_MEMORY; return -1; } track->chunk_table = entry; track->chunk_table_capacity = new_size; } memset(&track->chunk_table[track->num_chunks++], 0, sizeof(m4af_chunk_entry_t)); return 0; } static int m4af_update_chunk_table(m4af_ctx_t *ctx, uint32_t track_idx, uint32_t size, uint32_t delta) { m4af_track_t *track = &ctx->track[track_idx]; m4af_chunk_entry_t *entry; int add_new_chunk = 0; if (ctx->last_error) return -1; if (track->num_chunks == 0) add_new_chunk = 1; else { entry = &track->chunk_table[track->num_chunks - 1]; if (entry->duration + delta > track->timescale / 2) add_new_chunk = 1; } if (add_new_chunk) { m4af_flush_chunk(ctx, track_idx); if (m4af_add_chunk_entry(ctx, track_idx) < 0) return -1; } entry = &track->chunk_table[track->num_chunks - 1]; entry->size += size; ++entry->samples_per_chunk; entry->duration += delta; return 0; } static void m4af_update_max_bitrate(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; uint32_t duration = 0, size = 0, bitrate; m4af_sample_entry_t *ent = track->sample_table + track->num_samples - 1; for (; ent >= track->sample_table && duration < track->timescale; --ent) { duration += ent->delta; size += ent->size; } bitrate = (uint32_t)(size * 8.0 * track->timescale / duration + .5); if (bitrate > track->maxBitrate) track->maxBitrate = bitrate; } static int m4af_append_sample_to_chunk(m4af_ctx_t *ctx, uint32_t track_idx, const void *data, uint32_t size) { m4af_track_t *track = &ctx->track[track_idx]; uint32_t newsize = track->chunk_size + size; if (ctx->last_error) return -1; if (track->chunk_capacity < newsize) { uint32_t capacity = m4af_roundup(newsize); uint8_t *memory = realloc(track->chunk_buffer, capacity); if (!memory) { ctx->last_error = M4AF_NO_MEMORY; return -1; } track->chunk_buffer = memory; track->chunk_capacity = capacity; } memcpy(track->chunk_buffer + track->chunk_size, data, size); track->chunk_size = newsize; return 0; } int m4af_write_sample(m4af_ctx_t *ctx, uint32_t track_idx, const void *data, uint32_t size, uint32_t duration) { m4af_track_t *track = &ctx->track[track_idx]; if (track->frame_duration) duration = track->frame_duration; if (size > track->bufferSizeDB) track->bufferSizeDB = size; track->duration += duration; m4af_add_sample_entry(ctx, track_idx, size, duration); m4af_update_chunk_table(ctx, track_idx, size, duration); m4af_update_max_bitrate(ctx, track_idx); m4af_append_sample_to_chunk(ctx, track_idx, data, size); return ctx->last_error; } static m4af_itmf_entry_t *m4af_find_itmf_slot(m4af_ctx_t *ctx, uint32_t fcc, const char *name) { m4af_itmf_entry_t *entry = ctx->itmf_table; if (name) fcc = M4AF_FOURCC('-','-','-','-'); if (fcc != M4AF_TAG_ARTWORK) for (; entry != ctx->itmf_table + ctx->num_tags; ++entry) if (fcc == entry->fcc && (!name || !strcmp(name, entry->name))) return entry; if (ctx->num_tags == ctx->itmf_table_capacity) { uint32_t new_size = ctx->itmf_table_capacity; new_size = new_size ? new_size * 2 : 1; entry = m4af_realloc(ctx->itmf_table, new_size * sizeof(*entry)); if (entry == 0) { ctx->last_error = M4AF_NO_MEMORY; return 0; } ctx->itmf_table = entry; ctx->itmf_table_capacity = new_size; } entry = &ctx->itmf_table[ctx->num_tags++]; memset(entry, 0, sizeof(m4af_itmf_entry_t)); entry->fcc = fcc; if (name) { char *name_copy = m4af_realloc(0, strlen(name) + 1); if (!name_copy) { ctx->last_error = M4AF_NO_MEMORY; --ctx->num_tags; return 0; } strcpy(name_copy, name); entry->name = name_copy; } return entry; } int m4af_add_itmf_long_tag(m4af_ctx_t *ctx, const char *name, const char *data) { m4af_itmf_entry_t *entry; char *data_copy = 0; size_t name_len = strlen(name); size_t data_len = strlen(data); if (!name_len || !data_len) return 0; if ((entry = m4af_find_itmf_slot(ctx, 0, name)) == 0) goto FAIL; entry->type_code = M4AF_UTF8; if ((data_copy = m4af_realloc(entry->data, data_len)) == 0) { ctx->last_error = M4AF_NO_MEMORY; goto FAIL; } memcpy(data_copy, data, data_len); entry->data = data_copy; entry->data_size = data_len; return 0; FAIL: return ctx->last_error; } int m4af_add_itmf_short_tag(m4af_ctx_t *ctx, uint32_t fcc, uint32_t type_code, const void *data, uint32_t data_size) { m4af_itmf_entry_t *entry; char *data_copy = 0; if (!data_size) return 0; if ((entry = m4af_find_itmf_slot(ctx, fcc, 0)) == 0) goto FAIL; entry->type_code = type_code; if ((data_copy = m4af_realloc(entry->data, data_size)) == 0) { ctx->last_error = M4AF_NO_MEMORY; goto FAIL; } memcpy(data_copy, data, data_size); entry->data = data_copy; entry->data_size = data_size; return 0; FAIL: return ctx->last_error; } int m4af_add_itmf_string_tag(m4af_ctx_t *ctx, uint32_t fcc, const char *data) { return m4af_add_itmf_short_tag(ctx, fcc, M4AF_UTF8, data, strlen(data)); } int m4af_add_itmf_int8_tag(m4af_ctx_t *ctx, uint32_t fcc, int value) { uint8_t data = value; return m4af_add_itmf_short_tag(ctx, fcc, M4AF_INTEGER, &data, 1); } int m4af_add_itmf_int16_tag(m4af_ctx_t *ctx, uint32_t fcc, int value) { uint16_t data = m4af_htob16(value); return m4af_add_itmf_short_tag(ctx, fcc, M4AF_INTEGER, &data, 2); } int m4af_add_itmf_int32_tag(m4af_ctx_t *ctx, uint32_t fcc, uint32_t value) { uint32_t data = m4af_htob32(value); return m4af_add_itmf_short_tag(ctx, fcc, M4AF_INTEGER, &data, 4); } int m4af_add_itmf_int64_tag(m4af_ctx_t *ctx, uint32_t fcc, uint64_t value) { uint64_t data = m4af_htob64(value); return m4af_add_itmf_short_tag(ctx, fcc, M4AF_INTEGER, &data, 8); } int m4af_add_itmf_track_tag(m4af_ctx_t *ctx, int track, int total) { uint16_t data[4] = { 0 }; data[1] = m4af_htob16(track); data[2] = m4af_htob16(total); return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('t','r','k','n'), M4AF_IMPLICIT, &data, 8); } int m4af_add_itmf_disk_tag(m4af_ctx_t *ctx, int disk, int total) { uint16_t data[3] = { 0 }; data[1] = m4af_htob16(disk); data[2] = m4af_htob16(total); return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('d','i','s','k'), M4AF_IMPLICIT, &data, 6); } int m4af_add_itmf_genre_tag(m4af_ctx_t *ctx, int genre) { uint16_t data = m4af_htob16(genre); return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('g','n','r','e'), M4AF_IMPLICIT, &data, 2); } static int m4af_set_iTunSMPB(m4af_ctx_t *ctx) { const char *fmt = " 00000000 %08X %08X %08X%08X 00000000 00000000 " "00000000 00000000 00000000 00000000 00000000 00000000"; m4af_track_t *track = &ctx->track[0]; char buf[256]; uint64_t length = track->duration - track->encoder_delay - track->padding; sprintf(buf, fmt, track->encoder_delay, track->padding, (uint32_t)(length >> 32), (uint32_t)length); return m4af_add_itmf_long_tag(ctx, "iTunSMPB", buf); } static uint32_t m4af_update_box_size(m4af_ctx_t *ctx, int64_t pos) { int64_t current_pos = m4af_tell(ctx); m4af_set_pos(ctx, pos); m4af_write32(ctx, current_pos - pos); m4af_set_pos(ctx, current_pos); return current_pos - pos; } static void m4af_write_descriptor(m4af_ctx_t *ctx, uint32_t tag, uint32_t size) { uint8_t buf[5]; buf[0] = tag; buf[1] = ((size >> 21) | 0x80); buf[2] = ((size >> 14) | 0x80); buf[3] = ((size >> 7) | 0x80); buf[4] = (size & 0x7f); m4af_write(ctx, buf, 5); } static void m4af_write_ftyp_box(m4af_ctx_t *ctx) { m4af_write(ctx, "\0\0\0\040""ftypM4A \0\0\0\0M4A mp42isom\0\0\0\0", 32); } static void m4af_write_free_box(m4af_ctx_t *ctx, uint32_t size) { int64_t pos = m4af_tell(ctx); m4af_write32(ctx, size + 8); m4af_write(ctx, "free", 4); if (size > 0) m4af_set_pos(ctx, pos + size + 8); } int m4af_begin_write(m4af_ctx_t *ctx) { m4af_write_ftyp_box(ctx); m4af_write_free_box(ctx, 0); m4af_write(ctx, "\0\0\0\0mdat", 8); ctx->mdat_pos = m4af_tell(ctx); return ctx->last_error; } static void m4af_write_stco_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; uint32_t i; m4af_chunk_entry_t *index = track->chunk_table; int is_co64 = index[track->num_chunks - 1].offset > 0xffffffff; int64_t pos = m4af_tell(ctx); m4af_write32(ctx, 0); /* size */ m4af_write(ctx, is_co64 ? "co64" : "stco", 4); m4af_write32(ctx, 0); /* version and flags */ m4af_write32(ctx, track->num_chunks); for (i = 0; i < track->num_chunks; ++i, ++index) { if (is_co64) m4af_write64(ctx, index->offset); else m4af_write32(ctx, index->offset); } m4af_update_box_size(ctx, pos); } static void m4af_write_stsz_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_sample_entry_t *index = track->sample_table; uint32_t i; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "stsz" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\0" /* sample_size: 0(variable) */ , 16); m4af_write32(ctx, track->num_samples); for (i = 0; i < track->num_samples; ++i, ++index) m4af_write32(ctx, index->size); m4af_update_box_size(ctx, pos); } static void m4af_write_stsc_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_chunk_entry_t *index = track->chunk_table; uint32_t i, prev_samples_per_chunk = 0, entry_count = 0; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "stsc" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\0" /* entry_count */ , 16); for (i = 0; i < track->num_chunks; ++i, ++index) { if (index->samples_per_chunk != prev_samples_per_chunk) { ++entry_count; m4af_write32(ctx, i + 1); m4af_write32(ctx, index->samples_per_chunk); m4af_write32(ctx, 1); /* sample_description_index */ prev_samples_per_chunk = index->samples_per_chunk; } } m4af_write32_at(ctx, pos + 12, entry_count); m4af_update_box_size(ctx, pos); } static void m4af_write_stts_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_sample_entry_t *index = track->sample_table; uint32_t i, prev_delta = 0, entry_count = 0, sample_count = 0; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "stts" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\0" /* entry_count */ , 16); for (i = 0; i < track->num_samples; ++i, ++index) { if (index->delta == prev_delta) ++sample_count; else { ++entry_count; if (sample_count) { m4af_write32(ctx, sample_count); m4af_write32(ctx, prev_delta); } prev_delta = index->delta; sample_count = 1; } } if (sample_count) { m4af_write32(ctx, sample_count); m4af_write32(ctx, prev_delta); } m4af_write32_at(ctx, pos + 12, entry_count); m4af_update_box_size(ctx, pos); } static void m4af_write_esds_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0esds", 8); m4af_write32(ctx, 0); /* version + flags */ /* ES_Descriptor */ m4af_write_descriptor(ctx, 3, 32 + track->decSpecificInfoSize); m4af_write(ctx, "\0\0\0", 3); /* DecoderConfigDescriptor */ m4af_write_descriptor(ctx, 4, 18 + track->decSpecificInfoSize); m4af_write(ctx, "\x40" /* objectTypeIndication: 0x40(Audio ISO/IEC 14496-3)*/ "\x15" /* streamType(6): 0x05(AudioStream) * upStream(1) : 0 * reserved(1) : 1 */ , 2); m4af_write24(ctx, track->bufferSizeDB); m4af_write32(ctx, track->maxBitrate); m4af_write32(ctx, track->is_vbr ? 0: track->avgBitrate); /* DecoderSpecificInfo */ m4af_write_descriptor(ctx, 5, track->decSpecificInfoSize); m4af_write(ctx, track->decSpecificInfo, track->decSpecificInfoSize); /* SLConfigDescriptor */ m4af_write_descriptor(ctx, 6, 1); m4af_write(ctx, "\002", 1); /* predefined */ m4af_update_box_size(ctx, pos); } static void m4af_write_alac_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "alac" /* type */ "\0" /* version */ "\0\0\0" /* flags */ , 12); m4af_write(ctx, track->decSpecificInfo, track->decSpecificInfoSize); m4af_update_box_size(ctx, pos); } static void m4af_write_mp4a_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write32(ctx, 0); /* size */ m4af_write32(ctx, track->codec); /* mp4a or alac */ m4af_write(ctx, "\0\0\0\0\0\0" /* reserved */ "\0\001" /* data_reference_index: 1 */ "\0\0\0\0" /* reserved[0] */ "\0\0\0\0" /* reserved[1] */ ,16); m4af_write16(ctx, track->num_channels); m4af_write(ctx, "\0\020" /* samplesize: 16 */ "\0\0" /* pre_defined */ "\0\0" /* reserved */ ,6); if (track->codec == M4AF_FOURCC('m','p','4','a')) { m4af_write32(ctx, track->timescale << 16); m4af_write_esds_box(ctx, track_idx); } else { m4af_write32(ctx, 44100 << 16); m4af_write_alac_box(ctx, track_idx); } m4af_update_box_size(ctx, pos); } static void m4af_write_stsd_box(m4af_ctx_t *ctx, uint32_t track_idx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0stsd", 8); m4af_write(ctx, "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\001" /* entry_count: 1 */ , 8); m4af_write_mp4a_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_sbgp_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; m4af_write(ctx, "\0\0\0\034" /* size: 28 */ "sbgp" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "roll" /* grouping_type */ "\0\0\0\001" /* entry_count: 1 */ , 20); m4af_write32(ctx, track->num_samples); m4af_write32(ctx, 1); /* group_description_index */ } static void m4af_write_sgpd_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_write(ctx, "\0\0\0\026" /* size: 22 */ "sgpd" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "roll" /* grouping_type */ "\0\0\0\001" /* entry_count: 1 */ "\377\377" /* payload_data: -1 */ , 22); } static void m4af_write_stbl_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0stbl", 8); m4af_write_stsd_box(ctx, track_idx); if ((ctx->priming_mode & M4AF_PRIMING_MODE_EDTS) && (track->encoder_delay || track->padding)) { m4af_write_sbgp_box(ctx, track_idx); m4af_write_sgpd_box(ctx, track_idx); } m4af_write_stts_box(ctx, track_idx); m4af_write_stsc_box(ctx, track_idx); m4af_write_stsz_box(ctx, track_idx); m4af_write_stco_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_url_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_write(ctx, "\0\0\0\014" /* size */ "url " /* type */ "\0" /* version */ "\0\0\001" /* flags: 1(in the same file) */ , 12); } static void m4af_write_dref_box(m4af_ctx_t *ctx, uint32_t track_idx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0dref", 8); m4af_write(ctx, "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\001" /* entry_count: 1 */ ,8); m4af_write_url_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_dinf_box(m4af_ctx_t *ctx, uint32_t track_idx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0dinf", 8); m4af_write_dref_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_smhd_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_write(ctx, "\0\0\0\020" /* size */ "smhd" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "\0\0" /* balance */ "\0\0" /* reserved */ , 16); } static void m4af_write_minf_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0minf", 8); /* TODO: add TEXT support */ if (track->codec != M4AF_CODEC_TEXT) m4af_write_smhd_box(ctx, track_idx); m4af_write_dinf_box(ctx, track_idx); m4af_write_stbl_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_mdhd_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); uint8_t version = (track->creation_time > UINT32_MAX || track->modification_time > UINT32_MAX || track->duration > UINT32_MAX); m4af_write(ctx, "\0\0\0\0mdhd", 8); m4af_write(ctx, &version, 1); m4af_write(ctx, "\0\0\0", 3); /* flags */ if (version) { m4af_write64(ctx, track->creation_time); m4af_write64(ctx, track->modification_time); m4af_write32(ctx, track->timescale); m4af_write64(ctx, track->duration); } else { m4af_write32(ctx, track->creation_time); m4af_write32(ctx, track->modification_time); m4af_write32(ctx, track->timescale); m4af_write32(ctx, track->duration); } m4af_write(ctx, "\x55\xc4" /* language: und */ "\0\0" /* pre_defined */ , 4); m4af_update_box_size(ctx, pos); } static void m4af_write_hdlr_box(m4af_ctx_t *ctx, uint32_t track_idx, const char *type) { int64_t pos = m4af_tell(ctx); static const char reserved_and_name[10] = { 0 }; m4af_write(ctx, "\0\0\0\0" /* size */ "hdlr" /* type */ "\0" /* version */ "\0\0\0" /* flags */ "\0\0\0\0" /* pre_defined */ , 16); m4af_write(ctx, type, 4); /* handler_type */ /* reserved[0] */ m4af_write(ctx, !strcmp(type, "mdir") ? "appl" : "\0\0\0\0", 4); /* reserved[1], reserved[2], name */ m4af_write(ctx, reserved_and_name, 9); m4af_update_box_size(ctx, pos); } static void m4af_write_mdia_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; const char *hdlr = (track->codec == M4AF_CODEC_TEXT) ? "text" : "soun"; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0mdia", 8); m4af_write_mdhd_box(ctx, track_idx); m4af_write_hdlr_box(ctx, track_idx, hdlr); m4af_write_minf_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_elst_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; uint8_t version; int64_t duration = track->duration - track->encoder_delay - track->padding; int64_t pos = m4af_tell(ctx); duration = (double)duration / track->timescale * ctx->timescale + .5; version = (duration > UINT32_MAX); m4af_write(ctx, "\0\0\0\0elst", 8); m4af_write(ctx, &version, 1); m4af_write(ctx, "\0\0\0", 3); /* flags */ m4af_write32(ctx, 1); /* entry_count: 1 */ if (version) { m4af_write64(ctx, duration); m4af_write64(ctx, track->encoder_delay); } else { m4af_write32(ctx, duration); m4af_write32(ctx, track->encoder_delay); } m4af_write16(ctx, 1); /* media_rate_integer */ m4af_write16(ctx, 0); /* media_rate_fraction */ m4af_update_box_size(ctx, pos); } static void m4af_write_edts_box(m4af_ctx_t *ctx, uint32_t track_idx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0edts", 8); m4af_write_elst_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static void m4af_write_tkhd_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); int64_t duration = track->duration; if (ctx->priming_mode & M4AF_PRIMING_MODE_EDTS) duration -= (track->encoder_delay + track->padding); duration = (double)duration / track->timescale * ctx->timescale + .5; uint8_t version = (track->creation_time > UINT32_MAX || track->modification_time > UINT32_MAX || duration > UINT32_MAX); m4af_write(ctx, "\0\0\0\0tkhd", 8); m4af_write(ctx, &version, 1); m4af_write(ctx, "\0\0\007", 3); /* flags */ if (version) { m4af_write64(ctx, track->creation_time); m4af_write64(ctx, track->modification_time); m4af_write32(ctx, track_idx + 1); m4af_write(ctx, "\0\0\0\0" /* reserved */ , 4); m4af_write64(ctx, duration); } else { m4af_write32(ctx, track->creation_time); m4af_write32(ctx, track->modification_time); m4af_write32(ctx, track_idx + 1); m4af_write(ctx, "\0\0\0\0" /* reserved */ , 4); m4af_write32(ctx, duration); } m4af_write(ctx, "\0\0\0\0" /* reserved[0] */ "\0\0\0\0" /* reserved[1] */ "\0\0" /* layer */ "\0\0" /* alternate_group */ "\001\0" /* volume: 1.0 */ "\0\0" /* reserved */ "\0\001\0\0" /* matrix[0] */ "\0\0\0\0" /* matrix[1] */ "\0\0\0\0" /* matrix[2] */ "\0\0\0\0" /* matrix[3] */ "\0\001\0\0" /* matrix[4] */ "\0\0\0\0" /* matrix[5] */ "\0\0\0\0" /* matrix[6] */ "\0\0\0\0" /* matrix[7] */ "\100\0\0\0" /* matrix[8] */ "\0\0\0\0" /* width */ "\0\0\0\0" /* height */ , 60); m4af_update_box_size(ctx, pos); } static void m4af_write_trak_box(m4af_ctx_t *ctx, uint32_t track_idx) { m4af_track_t *track = &ctx->track[track_idx]; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0trak", 8); m4af_write_tkhd_box(ctx, track_idx); if ((ctx->priming_mode & M4AF_PRIMING_MODE_EDTS) && (track->encoder_delay || track->padding)) m4af_write_edts_box(ctx, track_idx); m4af_write_mdia_box(ctx, track_idx); m4af_update_box_size(ctx, pos); } static int64_t m4af_movie_duration(m4af_ctx_t *ctx) { int64_t movie_duration = 0; unsigned i; for (i = 0; i < ctx->num_tracks; ++i) { double x = ctx->track[i].duration; if (ctx->priming_mode & M4AF_PRIMING_MODE_EDTS) x -= (ctx->track[i].encoder_delay + ctx->track[i].padding); int64_t duration = x / ctx->track[i].timescale * ctx->timescale + .5; if (duration > movie_duration) movie_duration = duration; } return movie_duration; } static void m4af_write_mvhd_box(m4af_ctx_t *ctx) { int64_t pos = m4af_tell(ctx); int64_t movie_duration = m4af_movie_duration(ctx); uint8_t version = (ctx->creation_time > UINT32_MAX || ctx->modification_time > UINT32_MAX || movie_duration > UINT32_MAX); m4af_write(ctx, "\0\0\0\0mvhd", 8); m4af_write(ctx, &version, 1); m4af_write(ctx, "\0\0\0", 3); /* flags */ if (version) { m4af_write64(ctx, ctx->creation_time); m4af_write64(ctx, ctx->modification_time); m4af_write32(ctx, ctx->timescale); m4af_write64(ctx, movie_duration); } else { m4af_write32(ctx, ctx->creation_time); m4af_write32(ctx, ctx->modification_time); m4af_write32(ctx, ctx->timescale); m4af_write32(ctx, movie_duration); } m4af_write(ctx, "\0\001\0\0" /* rate: 1.0 */ "\001\0" /* volume: 1.0 */ "\0\0" /* reserved */ "\0\0\0\0" /* reserved[0] */ "\0\0\0\0" /* reserved[1] */ "\0\001\0\0" /* matrix[0] */ "\0\0\0\0" /* matrix[1] */ "\0\0\0\0" /* matrix[2] */ "\0\0\0\0" /* matrix[3] */ "\0\001\0\0" /* matrix[4] */ "\0\0\0\0" /* matrix[5] */ "\0\0\0\0" /* matrix[6] */ "\0\0\0\0" /* matrix[7] */ "\100\0\0\0" /* matrix[8] */ "\0\0\0\0" /* pre_defined[0] */ "\0\0\0\0" /* pre_defined[1] */ "\0\0\0\0" /* pre_defined[2] */ "\0\0\0\0" /* pre_defined[3] */ "\0\0\0\0" /* pre_defined[4] */ "\0\0\0\0" /* pre_defined[5] */ , 76); m4af_write32(ctx, ctx->num_tracks + 1); m4af_update_box_size(ctx, pos); } static void m4af_write_mean_box(m4af_ctx_t *ctx) { m4af_write(ctx, "\0\0\0\034" /* size */ "mean" "\0" /* version */ "\0\0\0" /* flags */ "com.apple.iTunes" /* meaning-string */ , 28); } static void m4af_write_name_box(m4af_ctx_t *ctx, const char *name) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "name" /* type */ "\0" /* version */ "\0\0\0" /* flags */ , 12); m4af_write(ctx, name, strlen(name)); m4af_update_box_size(ctx, pos); } static void m4af_write_data_box(m4af_ctx_t *ctx, uint32_t type_code, const char *data, uint32_t data_size) { int64_t pos = m4af_tell(ctx); uint8_t code = type_code; m4af_write(ctx, "\0\0\0\0" /* size */ "data" /* type */ "\0\0" /* reserved */ "\0" /* type_set_indifier */ ,11); m4af_write(ctx, &code, 1); m4af_write(ctx, "\0\0\0\0", 4); /* locale */ m4af_write(ctx, data, data_size); m4af_update_box_size(ctx, pos); } static void m4af_write_metadata(m4af_ctx_t *ctx, m4af_itmf_entry_t *entry) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0", 4); m4af_write32(ctx, entry->fcc); if (entry->fcc != M4AF_FOURCC('-','-','-','-')) m4af_write_data_box(ctx, entry->type_code, entry->data, entry->data_size); else { m4af_write_mean_box(ctx); m4af_write_name_box(ctx, entry->name); m4af_write_data_box(ctx, 1, entry->data, entry->data_size); } m4af_update_box_size(ctx, pos); } static void m4af_write_ilst_box(m4af_ctx_t *ctx) { uint32_t i; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0ilst", 8); for (i = 0; i < ctx->num_tags; ++i) m4af_write_metadata(ctx, &ctx->itmf_table[i]); m4af_update_box_size(ctx, pos); } static void m4af_write_meta_box(m4af_ctx_t *ctx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0" /* size */ "meta" /* type */ "\0" /* version */ "\0\0\0" /* flags */ , 12); m4af_write_hdlr_box(ctx, 0, "mdir"); m4af_write_ilst_box(ctx); m4af_update_box_size(ctx, pos); } static void m4af_write_udta_box(m4af_ctx_t *ctx) { int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0udta", 8); m4af_write_meta_box(ctx); m4af_update_box_size(ctx, pos); } static uint32_t m4af_write_moov_box(m4af_ctx_t *ctx) { unsigned i; int64_t pos = m4af_tell(ctx); m4af_write(ctx, "\0\0\0\0moov", 8); m4af_write_mvhd_box(ctx); for (i = 0; i < ctx->num_tracks; ++i) m4af_write_trak_box(ctx, i); if (ctx->num_tags) m4af_write_udta_box(ctx); return m4af_update_box_size(ctx, pos); } static void m4af_finalize_mdat(m4af_ctx_t *ctx) { if (ctx->mdat_size + 8 > UINT32_MAX) { m4af_set_pos(ctx, ctx->mdat_pos - 16); m4af_write32(ctx, 1); m4af_write(ctx, "mdat", 4); m4af_write64(ctx, ctx->mdat_size + 16); } else { m4af_set_pos(ctx, ctx->mdat_pos - 8); m4af_write32(ctx, ctx->mdat_size + 8); } m4af_set_pos(ctx, ctx->mdat_pos + ctx->mdat_size); } static uint64_t m4af_patch_moov(m4af_ctx_t *ctx, uint32_t moov_size, uint32_t offset) { int64_t pos = 0; uint32_t moov_size2; int i, j; m4af_io_callbacks_t io_reserve = ctx->io; void *io_cookie_reserve = ctx->io_cookie; for (i = 0; i < ctx->num_tracks; ++i) for (j = 0; j < ctx->track[i].num_chunks; ++j) ctx->track[i].chunk_table[j].offset += offset; ctx->io = m4af_null_io_callbacks; ctx->io_cookie = &pos; moov_size2 = m4af_write_moov_box(ctx); if (moov_size2 != moov_size) { /* stco -> co64 switching */ for (i = 0; i < ctx->num_tracks; ++i) for (j = 0; j < ctx->track[i].num_chunks; ++j) ctx->track[i].chunk_table[j].offset += moov_size2 - moov_size; moov_size2 = m4af_write_moov_box(ctx); } ctx->io = io_reserve; ctx->io_cookie = io_cookie_reserve; return moov_size2; } static void m4af_shift_mdat_pos(m4af_ctx_t *ctx, uint32_t offset) { int64_t begin, end; char *buf; buf = malloc(1024*1024*2); end = ctx->mdat_pos + ctx->mdat_size; for (; (begin = m4af_max(ctx->mdat_pos, end - 1024*1024*2)) < end; end = begin) { m4af_set_pos(ctx, begin); ctx->io.read(ctx->io_cookie, buf, end - begin); m4af_set_pos(ctx, begin + offset); m4af_write(ctx, buf, end - begin); } ctx->mdat_pos += offset; m4af_set_pos(ctx, ctx->mdat_pos - 16); m4af_write_free_box(ctx, 0); m4af_write(ctx, "\0\0\0\0mdat", 8); m4af_finalize_mdat(ctx); free(buf); } int m4af_finalize(m4af_ctx_t *ctx, int optimize) { unsigned i; m4af_track_t *track; uint32_t moov_size; for (i = 0; i < ctx->num_tracks; ++i) { track = ctx->track + i; if (track->duration) { int64_t track_size = 0; unsigned j; for (j = 0; j < track->num_chunks; ++j) track_size += track->chunk_table[j].size; track->avgBitrate = 8.0 * track_size * track->timescale / track->duration + .5; } m4af_flush_chunk(ctx, i); } track = ctx->track; if ((ctx->priming_mode & M4AF_PRIMING_MODE_ITUNSMPB) && (track->encoder_delay || track->padding)) m4af_set_iTunSMPB(ctx); m4af_finalize_mdat(ctx); moov_size = m4af_write_moov_box(ctx); if (optimize) { int64_t pos; uint32_t moov_size2 = m4af_patch_moov(ctx, moov_size, moov_size + 1024); m4af_shift_mdat_pos(ctx, moov_size2 + 1024); m4af_set_pos(ctx, 32); m4af_write_moov_box(ctx); pos = m4af_tell(ctx); m4af_write_free_box(ctx, ctx->mdat_pos - pos - 24); } return ctx->last_error; }