+/*
+ * Copyright (C) 2013 nu774
+ * For conditions of distribution and use, see copyright notice in COPYING
+ */
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include "m4af.h"
+#include "m4af_endian.h"
+
+#define m4af_realloc(memory,size) realloc(memory, size)
+#define m4af_free(memory) free(memory)
+
+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 type; /* fcc */
+ union {
+ uint32_t type_code;
+ char *name;
+ } u;
+ char *data;
+ uint32_t data_size;
+} m4af_itmf_entry_t;
+
+typedef struct m4af_track_t {
+ uint32_t codec;
+ uint32_t timescale;
+ 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;
+
+ 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;
+} m4af_track_t;
+
+struct m4af_writer_t {
+ uint32_t timescale;
+ int64_t creation_time;
+ int64_t modification_time;
+ int64_t mdat_pos;
+ int64_t mdat_size;
+ 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[1];
+};
+
+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_writer_t *ctx)
+{
+ int64_t pos = -1;
+ if (!ctx->last_error && (pos = ctx->io.tell(ctx->io_cookie)) < 0)
+ ctx->last_error = M4AF_IO_ERROR;
+ return pos;
+}
+
+static
+int m4af_set_pos(m4af_writer_t *ctx, int64_t pos)
+{
+ int rc = -1;
+ if (!ctx->last_error &&
+ (rc = ctx->io.seek(ctx->io_cookie, pos, SEEK_SET)) < 0)
+ ctx->last_error = M4AF_IO_ERROR;
+ return rc;
+}
+
+static
+int m4af_write(m4af_writer_t *ctx, const void *data, uint32_t size)
+{
+ int rc = -1;
+ if (!ctx->last_error &&
+ (rc = ctx->io.write(ctx->io_cookie, data, size)) < 0)
+ ctx->last_error = M4AF_IO_ERROR;
+ return rc;
+}
+
+static
+int m4af_write32(m4af_writer_t *ctx, uint32_t data)
+{
+ data = m4af_htob32(data);
+ return m4af_write(ctx, &data, 4);
+}
+
+static
+int m4af_write64(m4af_writer_t *ctx, uint64_t data)
+{
+ data = m4af_htob64(data);
+ return m4af_write(ctx, &data, 8);
+}
+
+static
+int m4af_write24(m4af_writer_t *ctx, uint32_t data)
+{
+ data = m4af_htob32(data << 8);
+ return m4af_write(ctx, &data, 3);
+}
+
+static
+void m4af_write32_at(m4af_writer_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_writer_t *m4af_create(uint32_t codec, uint32_t timescale,
+ m4af_io_callbacks_t *io, void *io_cookie)
+{
+ m4af_writer_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_writer_t))) == 0)
+ return 0;
+ memset(ctx, 0, sizeof(m4af_writer_t));
+ memcpy(&ctx->io, io, sizeof(m4af_io_callbacks_t));
+ ctx->io_cookie = io_cookie;
+ ctx->timescale = timescale;
+ timestamp = 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;
+ return ctx;
+}
+
+void m4af_set_fixed_frame_duration(m4af_writer_t *ctx, int track_idx,
+ uint32_t length)
+{
+ ctx->track[track_idx].frame_duration = length;
+}
+
+int m4af_decoder_specific_info(m4af_writer_t *ctx, int 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;
+ return -1;
+ }
+ track->decSpecificInfo = memory;
+ }
+ if (size > 0)
+ memcpy(track->decSpecificInfo, data, size);
+ track->decSpecificInfoSize = size;
+ return 0;
+}
+
+void m4af_set_priming(m4af_writer_t *ctx, int 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;
+}
+
+static
+int m4af_add_sample_entry(m4af_writer_t *ctx, int 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_writer_t *ctx, int 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_update_chunk_table(m4af_writer_t *ctx, int 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 (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));
+ }
+ 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_writer_t *ctx, int 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_writer_t *ctx, int 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_writer_t *ctx, int 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 ? -1 : 0;
+}
+
+static
+int m4af_add_itmf_entry(m4af_writer_t *ctx)
+{
+ m4af_itmf_entry_t *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 -1;
+ }
+ ctx->itmf_table = entry;
+ ctx->itmf_table_capacity = new_size;
+ }
+ ++ctx->num_tags;
+ return 0;
+}
+
+int m4af_add_itmf_long_tag(m4af_writer_t *ctx, const char *name,
+ const char *data)
+{
+ m4af_itmf_entry_t *entry;
+ size_t name_len = strlen(name);
+ size_t data_len = strlen(data);
+ char *name_copy = m4af_realloc(0, name_len + 1);
+ char *data_copy = m4af_realloc(0, data_len);
+ if (!name_copy || !data_copy) {
+ ctx->last_error = M4AF_NO_MEMORY;
+ goto FAIL;
+ }
+ if (m4af_add_itmf_entry(ctx) < 0)
+ goto FAIL;
+ memcpy(name_copy, name, name_len + 1);
+ memcpy(data_copy, data, data_len);
+ entry = ctx->itmf_table + ctx->num_tags - 1;
+ entry->type = M4AF_FOURCC('-','-','-','-');
+ entry->u.name = name_copy;
+ entry->data = data_copy;
+ entry->data_size = data_len;
+ return 0;
+FAIL:
+ if (name_copy)
+ m4af_free(name_copy);
+ if (data_copy)
+ m4af_free(data_copy);
+ return -1;
+}
+
+int m4af_add_itmf_short_tag(m4af_writer_t *ctx, uint32_t type,
+ uint32_t type_code, const void *data,
+ uint32_t data_size)
+{
+ m4af_itmf_entry_t *entry;
+ char *data_copy = m4af_realloc(0, data_size);
+ if (!data_copy) {
+ ctx->last_error = M4AF_NO_MEMORY;
+ goto FAIL;
+ }
+ if (m4af_add_itmf_entry(ctx) < 0)
+ goto FAIL;
+ entry = ctx->itmf_table + ctx->num_tags - 1;
+ entry->type = type;
+ entry->u.type_code = type_code;
+ memcpy(data_copy, data, data_size);
+ entry->data = data_copy;
+ entry->data_size = data_size;
+ return 0;
+FAIL:
+ if (data_copy)
+ m4af_free(data_copy);
+ return -1;
+}
+
+int m4af_add_itmf_string_tag(m4af_writer_t *ctx, uint32_t type,
+ const char *data)
+{
+ return m4af_add_itmf_short_tag(ctx, type, M4AF_UTF8, data, strlen(data));
+}
+
+int m4af_add_itmf_int8_tag(m4af_writer_t *ctx, uint32_t type, int value)
+{
+ uint8_t data = value;
+ return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 1);
+}
+
+int m4af_add_itmf_int16_tag(m4af_writer_t *ctx, uint32_t type, int value)
+{
+ uint16_t data = m4af_htob16(value);
+ return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 2);
+}
+
+int m4af_add_itmf_int32_tag(m4af_writer_t *ctx, uint32_t type, int value)
+{
+ uint32_t data = m4af_htob32(value);
+ return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 4);
+}
+
+int m4af_add_itmf_track_tag(m4af_writer_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_writer_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_writer_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_writer_t *ctx)
+{
+ const char *template = " 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, template, track->encoder_delay, track->padding,
+ (uint32_t)(length >> 32), (uint32_t)length);
+ return m4af_add_itmf_long_tag(ctx, "iTunSMPB", buf);
+}
+
+static
+void m4af_update_size(m4af_writer_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);
+}
+
+static
+int m4af_head_version(m4af_writer_t *ctx, int track_idx)
+{
+ m4af_track_t *track = &ctx->track[track_idx];
+ return track->duration > UINT32_MAX
+ || track->creation_time > UINT32_MAX
+ || track->modification_time > UINT32_MAX;
+}
+
+static
+void m4af_descriptor(m4af_writer_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_ftyp_box(m4af_writer_t *ctx)
+{
+ m4af_write(ctx, "\0\0\0\040""ftypM4A \0\0\0\0M4A mp42isom\0\0\0\0", 32);
+}
+
+static
+void m4af_free_box(m4af_writer_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_writer_t *ctx)
+{
+ m4af_ftyp_box(ctx);
+ m4af_free_box(ctx, 0);
+ m4af_write(ctx, "\0\0\0\0mdat", 8);
+ ctx->mdat_pos = m4af_tell(ctx);
+ return ctx->last_error ? -1 : 0;
+}
+
+static
+void m4af_stco_box(m4af_writer_t *ctx, int track_idx)
+{
+ m4af_track_t *track = &ctx->track[track_idx];
+ uint32_t i;
+ m4af_chunk_entry_t *index = track->chunk_table;
+ int is_co64 = (ctx->mdat_pos + ctx->mdat_size > UINT32_MAX);
+ 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_size(ctx, pos);
+}
+
+static
+void m4af_stsz_box(m4af_writer_t *ctx, int 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_size(ctx, pos);
+}
+
+static
+void m4af_stsc_box(m4af_writer_t *ctx, int 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_size(ctx, pos);
+}
+
+static
+void m4af_stts_box(m4af_writer_t *ctx, int 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_size(ctx, pos);
+}
+
+static
+void m4af_esds_box(m4af_writer_t *ctx, int 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_descriptor(ctx, 3, 32 + track->decSpecificInfoSize);
+ m4af_write(ctx, "\0\0\0", 3);
+ /* DecoderConfigDescriptor */
+ m4af_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->avgBitrate);
+ /* DecoderSpecificInfo */
+ m4af_descriptor(ctx, 5, track->decSpecificInfoSize);
+ m4af_write(ctx, track->decSpecificInfo, track->decSpecificInfoSize);
+ /* SLConfigDescriptor */
+ m4af_descriptor(ctx, 6, 1);
+ m4af_write(ctx, "\002", 1); /* predefined */
+
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_alac_box(m4af_writer_t *ctx, int 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_size(ctx, pos);
+}
+
+static
+void m4af_mp4a_box(m4af_writer_t *ctx, int 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] */
+ "\0\002" /* channelcount: 2 */
+ "\0\020" /* samplesize: 16 */
+ "\0\0" /* pre_defined */
+ "\0\0" /* reserved */
+ ,24);
+ if (track->codec == M4AF_FOURCC('m','p','4','a')) {
+ m4af_write32(ctx, track->timescale << 16);
+ m4af_esds_box(ctx, track_idx);
+ } else {
+ m4af_write32(ctx, 44100);
+ m4af_alac_box(ctx, track_idx);
+ }
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_stsd_box(m4af_writer_t *ctx, int 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_mp4a_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_stbl_box(m4af_writer_t *ctx, int track_idx)
+{
+ int64_t pos = m4af_tell(ctx);
+ m4af_write(ctx, "\0\0\0\0stbl", 8);
+ m4af_stsd_box(ctx, track_idx);
+ m4af_stts_box(ctx, track_idx);
+ m4af_stsc_box(ctx, track_idx);
+ m4af_stsz_box(ctx, track_idx);
+ m4af_stco_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_url_box(m4af_writer_t *ctx, int 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_dref_box(m4af_writer_t *ctx, int 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_url_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_dinf_box(m4af_writer_t *ctx, int track_idx)
+{
+ int64_t pos = m4af_tell(ctx);
+ m4af_write(ctx, "\0\0\0\0dinf", 8);
+ m4af_dref_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_smhd_box(m4af_writer_t *ctx, int 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_minf_box(m4af_writer_t *ctx, int 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_smhd_box(ctx, track_idx);
+ m4af_dinf_box(ctx, track_idx);
+ m4af_stbl_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_mdhd_box(m4af_writer_t *ctx, int track_idx)
+{
+ m4af_track_t *track = &ctx->track[track_idx];
+ int64_t pos = m4af_tell(ctx);
+ uint8_t version = m4af_head_version(ctx, track_idx);
+
+ 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_size(ctx, pos);
+}
+
+static
+void m4af_hdlr_box(m4af_writer_t *ctx, int 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, (pos & 1) ? 9 : 10);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_mdia_box(m4af_writer_t *ctx, int 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_mdhd_box(ctx, track_idx);
+ m4af_hdlr_box(ctx, track_idx, hdlr);
+ m4af_minf_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_tkhd_box(m4af_writer_t *ctx, int track_idx)
+{
+ m4af_track_t *track = &ctx->track[track_idx];
+ int64_t pos = m4af_tell(ctx);
+ uint8_t version = m4af_head_version(ctx, track_idx);
+ 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, track->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, track->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_size(ctx, pos);
+}
+
+static
+void m4af_trak_box(m4af_writer_t *ctx, int track_idx)
+{
+ int64_t pos = m4af_tell(ctx);
+ m4af_write(ctx, "\0\0\0\0trak", 8);
+ m4af_tkhd_box(ctx, track_idx);
+ m4af_mdia_box(ctx, track_idx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+int64_t m4af_movie_duration(m4af_writer_t *ctx)
+{
+ int64_t movie_duration = 0;
+ unsigned i;
+ for (i = 0; i < ctx->num_tracks; ++i) {
+ double x = ctx->track[i].duration;
+ int64_t duration = x * ctx->track[i].timescale / ctx->timescale + .5;
+ if (duration > movie_duration)
+ movie_duration = duration;
+ }
+ return movie_duration;
+}
+
+static
+void m4af_mvhd_box(m4af_writer_t *ctx)
+{
+ int64_t pos = m4af_tell(ctx);
+ uint8_t version = m4af_head_version(ctx, 0);
+ int64_t movie_duration = m4af_movie_duration(ctx);
+
+ 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_size(ctx, pos);
+}
+
+static
+void m4af_mean_box(m4af_writer_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_name_box(m4af_writer_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_size(ctx, pos);
+}
+
+static
+void m4af_data_box(m4af_writer_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_size(ctx, pos);
+}
+
+static
+void m4af_write_metadata(m4af_writer_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->type);
+ if (entry->type != M4AF_FOURCC('-','-','-','-'))
+ m4af_data_box(ctx, entry->u.type_code, entry->data, entry->data_size);
+ else {
+ m4af_mean_box(ctx);
+ m4af_name_box(ctx, entry->u.name);
+ m4af_data_box(ctx, 1, entry->data, entry->data_size);
+ }
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_ilst_box(m4af_writer_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_size(ctx, pos);
+}
+
+static
+void m4af_meta_box(m4af_writer_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_hdlr_box(ctx, 0, "mdir");
+ m4af_ilst_box(ctx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_udta_box(m4af_writer_t *ctx)
+{
+ int64_t pos = m4af_tell(ctx);
+ m4af_write(ctx, "\0\0\0\0udta", 8);
+ m4af_meta_box(ctx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_moov_box(m4af_writer_t *ctx)
+{
+ unsigned i;
+ int64_t pos = m4af_tell(ctx);
+ m4af_write(ctx, "\0\0\0\0moov", 8);
+ m4af_mvhd_box(ctx);
+ for (i = 0; i < ctx->num_tracks; ++i)
+ m4af_trak_box(ctx, i);
+ if (ctx->num_tags)
+ m4af_udta_box(ctx);
+ m4af_update_size(ctx, pos);
+}
+
+static
+void m4af_finalize_mdat(m4af_writer_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);
+}
+
+int m4af_finalize(m4af_writer_t *ctx)
+{
+ unsigned i;
+ m4af_track_t *track;
+
+ 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);
+ }
+ if (ctx->track[0].encoder_delay || ctx->track[0].padding)
+ m4af_set_iTunSMPB(ctx);
+ m4af_finalize_mdat(ctx);
+ m4af_moov_box(ctx);
+ return ctx->last_error ? -1 : 0;
+}
+
+static
+void m4af_free_itmf_table(m4af_writer_t *ctx)
+{
+ uint32_t i;
+ m4af_itmf_entry_t *entry = ctx->itmf_table;
+ for (i = 0; i < ctx->num_tags; ++i, ++entry) {
+ if (entry->type == M4AF_FOURCC('-','-','-','-'))
+ m4af_free(entry->u.name);
+ m4af_free(entry->data);
+ }
+ m4af_free(ctx->itmf_table);
+}
+
+void m4af_teardown(m4af_writer_t **ctxp)
+{
+ unsigned i;
+ m4af_writer_t *ctx = *ctxp;
+ m4af_track_t *track;
+ for (i = 0; i < ctx->num_tracks; ++i) {
+ track = ctx->track + i;
+ 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);
+ }
+ if (ctx->itmf_table)
+ m4af_free_itmf_table(ctx);
+ m4af_free(ctx);
+ *ctxp = 0;
+}