#ifndef MINIMP4_H
#define MINIMP4_H
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MINIMP4_MIN(x, y) ((x) < (y) ? (x) : (y))
#define FIX_BAD_ANDROID_META_BOX 1
#define MAX_CHUNKS_DEPTH 64
#define MINIMP4_MAX_SPS 32
#define MINIMP4_MAX_PPS 256
#define MINIMP4_TRANSCODE_SPS_ID 1
#define MINIMP4_ALLOW_64BIT 1
#define MP4D_TRACE_SUPPORTED 0
#define MP4D_TRACE_TIMESTAMPS 1
#define MP4D_INFO_SUPPORTED 1
#define MP4D_PRINT_INFO_SUPPORTED 0
#define MP4D_AVC_SUPPORTED 1
#define MP4D_HEVC_SUPPORTED 1
#define MP4D_TIMESTAMPS_SUPPORTED 1
#define MP4D_TFDT_SUPPORT 0
#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 0x40
#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE 0x66
#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE 0x67
#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE 0x68
#define MP4_OBJECT_TYPE_AVC 0x21
#define MP4_OBJECT_TYPE_HEVC 0x23
#define MP4_OBJECT_TYPE_USER_PRIVATE 0xC0
#define MP4E_STATUS_OK 0
#define MP4E_STATUS_BAD_ARGUMENTS -1
#define MP4E_STATUS_NO_MEMORY -2
#define MP4E_STATUS_FILE_WRITE_ERROR -3
#define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4
#define MP4E_SAMPLE_DEFAULT 0
#define MP4E_SAMPLE_RANDOM_ACCESS 1
#define MP4E_SAMPLE_CONTINUATION 2
#if MINIMP4_ALLOW_64BIT
typedef uint64_t boxsize_t;
#else
typedef unsigned int boxsize_t;
#endif
typedef boxsize_t MP4D_file_offset_t;
#define MP4D_HANDLER_TYPE_VIDE 0x76696465
#define MP4D_HANDLER_TYPE_SOUN 0x736F756E
#define MP4E_HANDLER_TYPE_GESM 0x6765736D
#define HEVC_NAL_VPS 32
#define HEVC_NAL_SPS 33
#define HEVC_NAL_PPS 34
#define HEVC_NAL_BLA_W_LP 16
#define HEVC_NAL_CRA_NUT 21
typedef struct MP4E_mux_tag MP4E_mux_t;
typedef enum
{
e_audio,
e_video,
e_private
} track_media_kind_t;
typedef struct
{
unsigned object_type_indication;
unsigned char language[4];
track_media_kind_t track_media_kind;
unsigned time_scale;
unsigned default_duration;
union
{
struct
{
unsigned channelcount;
} a;
struct
{
int width;
int height;
} v;
} u;
} MP4E_track_t;
typedef struct MP4D_sample_to_chunk_t_tag MP4D_sample_to_chunk_t;
typedef struct
{
unsigned sample_count;
unsigned char *dsi;
unsigned dsi_bytes;
unsigned object_type_indication;
#if MP4D_INFO_SUPPORTED
unsigned handler_type;
unsigned duration_hi;
unsigned duration_lo;
unsigned timescale;
unsigned avg_bitrate_bps;
unsigned char language[4];
unsigned stream_type;
union
{
struct
{
unsigned channelcount;
unsigned samplerate_hz;
} audio;
struct
{
unsigned width;
unsigned height;
} video;
} SampleDescription;
#endif
unsigned *entry_size;
unsigned sample_to_chunk_count;
struct MP4D_sample_to_chunk_t_tag *sample_to_chunk;
unsigned chunk_count;
MP4D_file_offset_t *chunk_offset;
#if MP4D_TIMESTAMPS_SUPPORTED
unsigned *timestamp;
unsigned *duration;
#endif
} MP4D_track_t;
typedef struct MP4D_demux_tag
{
int64_t read_pos;
int64_t read_size;
MP4D_track_t *track;
int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token);
void *token;
unsigned track_count;
#if MP4D_INFO_SUPPORTED
unsigned duration_hi;
unsigned duration_lo;
unsigned timescale;
struct
{
unsigned char *title;
unsigned char *artist;
unsigned char *album;
unsigned char *year;
unsigned char *comment;
unsigned char *genre;
} tag;
#endif
} MP4D_demux_t;
struct MP4D_sample_to_chunk_t_tag
{
unsigned first_chunk;
unsigned samples_per_chunk;
};
typedef struct
{
void *sps_cache[MINIMP4_MAX_SPS];
void *pps_cache[MINIMP4_MAX_PPS];
int sps_bytes[MINIMP4_MAX_SPS];
int pps_bytes[MINIMP4_MAX_PPS];
int map_sps[MINIMP4_MAX_SPS];
int map_pps[MINIMP4_MAX_PPS];
} h264_sps_id_patcher_t;
typedef struct mp4_h26x_writer_tag
{
#if MINIMP4_TRANSCODE_SPS_ID
h264_sps_id_patcher_t sps_patcher;
#endif
MP4E_mux_t *mux;
int mux_track_id, is_hevc, need_vps, need_sps, need_pps, need_idr;
} mp4_h26x_writer_t;
int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc);
void mp4_h26x_write_close(mp4_h26x_writer_t *h);
int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next);
int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size);
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);
void MP4D_close(MP4D_demux_t *mp4);
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
void MP4D_printf_info(const MP4D_demux_t *mp4);
#endif
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));
int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data);
int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, int kind);
int MP4E_close(MP4E_mux_t *mux);
int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes);
int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes);
int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes);
int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes);
int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment);
#ifdef __cplusplus
}
#endif
#endif
#if defined(MINIMP4_IMPLEMENTATION) && !defined(MINIMP4_IMPLEMENTATION_GUARD)
#define MINIMP4_IMPLEMENTATION_GUARD
#define FOUR_CHAR_INT(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
enum
{
BOX_co64 = FOUR_CHAR_INT( 'c', 'o', '6', '4' ), BOX_stco = FOUR_CHAR_INT( 's', 't', 'c', 'o' ), BOX_crhd = FOUR_CHAR_INT( 'c', 'r', 'h', 'd' ), BOX_ctts = FOUR_CHAR_INT( 'c', 't', 't', 's' ), BOX_cprt = FOUR_CHAR_INT( 'c', 'p', 'r', 't' ), BOX_url_ = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ), BOX_urn_ = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ), BOX_dinf = FOUR_CHAR_INT( 'd', 'i', 'n', 'f' ), BOX_dref = FOUR_CHAR_INT( 'd', 'r', 'e', 'f' ), BOX_stdp = FOUR_CHAR_INT( 's', 't', 'd', 'p' ), BOX_edts = FOUR_CHAR_INT( 'e', 'd', 't', 's' ), BOX_elst = FOUR_CHAR_INT( 'e', 'l', 's', 't' ), BOX_uuid = FOUR_CHAR_INT( 'u', 'u', 'i', 'd' ), BOX_free = FOUR_CHAR_INT( 'f', 'r', 'e', 'e' ), BOX_hdlr = FOUR_CHAR_INT( 'h', 'd', 'l', 'r' ), BOX_hmhd = FOUR_CHAR_INT( 'h', 'm', 'h', 'd' ), BOX_hint = FOUR_CHAR_INT( 'h', 'i', 'n', 't' ), BOX_mdia = FOUR_CHAR_INT( 'm', 'd', 'i', 'a' ), BOX_mdat = FOUR_CHAR_INT( 'm', 'd', 'a', 't' ), BOX_mdhd = FOUR_CHAR_INT( 'm', 'd', 'h', 'd' ), BOX_minf = FOUR_CHAR_INT( 'm', 'i', 'n', 'f' ), BOX_moov = FOUR_CHAR_INT( 'm', 'o', 'o', 'v' ), BOX_mvhd = FOUR_CHAR_INT( 'm', 'v', 'h', 'd' ), BOX_stsd = FOUR_CHAR_INT( 's', 't', 's', 'd' ), BOX_stsz = FOUR_CHAR_INT( 's', 't', 's', 'z' ), BOX_stz2 = FOUR_CHAR_INT( 's', 't', 'z', '2' ), BOX_stbl = FOUR_CHAR_INT( 's', 't', 'b', 'l' ), BOX_stsc = FOUR_CHAR_INT( 's', 't', 's', 'c' ), BOX_stsh = FOUR_CHAR_INT( 's', 't', 's', 'h' ), BOX_skip = FOUR_CHAR_INT( 's', 'k', 'i', 'p' ), BOX_smhd = FOUR_CHAR_INT( 's', 'm', 'h', 'd' ), BOX_stss = FOUR_CHAR_INT( 's', 't', 's', 's' ), BOX_stts = FOUR_CHAR_INT( 's', 't', 't', 's' ), BOX_trak = FOUR_CHAR_INT( 't', 'r', 'a', 'k' ), BOX_tkhd = FOUR_CHAR_INT( 't', 'k', 'h', 'd' ), BOX_tref = FOUR_CHAR_INT( 't', 'r', 'e', 'f' ), BOX_udta = FOUR_CHAR_INT( 'u', 'd', 't', 'a' ), BOX_vmhd = FOUR_CHAR_INT( 'v', 'm', 'h', 'd' ), BOX_url = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),
BOX_urn = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),
BOX_gnrv = FOUR_CHAR_INT( 'g', 'n', 'r', 'v' ), BOX_gnra = FOUR_CHAR_INT( 'g', 'n', 'r', 'a' ),
BOX_ftyp = FOUR_CHAR_INT( 'f', 't', 'y', 'p' ), BOX_padb = FOUR_CHAR_INT( 'p', 'a', 'd', 'b' ),
BOX_sdhd = FOUR_CHAR_INT( 's', 'd', 'h', 'd' ), BOX_dpnd = FOUR_CHAR_INT( 'd', 'p', 'n', 'd' ), BOX_iods = FOUR_CHAR_INT( 'i', 'o', 'd', 's' ), BOX_odhd = FOUR_CHAR_INT( 'o', 'd', 'h', 'd' ), BOX_mpod = FOUR_CHAR_INT( 'm', 'p', 'o', 'd' ), BOX_nmhd = FOUR_CHAR_INT( 'n', 'm', 'h', 'd' ), BOX_esds = FOUR_CHAR_INT( 'e', 's', 'd', 's' ), BOX_sync = FOUR_CHAR_INT( 's', 'y', 'n', 'c' ), BOX_ipir = FOUR_CHAR_INT( 'i', 'p', 'i', 'r' ), BOX_mp4s = FOUR_CHAR_INT( 'm', 'p', '4', 's' ), BOX_mp4a = FOUR_CHAR_INT( 'm', 'p', '4', 'a' ), BOX_mp4v = FOUR_CHAR_INT( 'm', 'p', '4', 'v' ),
BOX_avc1 = FOUR_CHAR_INT( 'a', 'v', 'c', '1' ),
BOX_avc2 = FOUR_CHAR_INT( 'a', 'v', 'c', '2' ),
BOX_svc1 = FOUR_CHAR_INT( 's', 'v', 'c', '1' ),
BOX_avcC = FOUR_CHAR_INT( 'a', 'v', 'c', 'C' ),
BOX_svcC = FOUR_CHAR_INT( 's', 'v', 'c', 'C' ),
BOX_btrt = FOUR_CHAR_INT( 'b', 't', 'r', 't' ),
BOX_m4ds = FOUR_CHAR_INT( 'm', '4', 'd', 's' ),
BOX_seib = FOUR_CHAR_INT( 's', 'e', 'i', 'b' ),
BOX_hev1 = FOUR_CHAR_INT( 'h', 'e', 'v', '1' ),
BOX_hvc1 = FOUR_CHAR_INT( 'h', 'v', 'c', '1' ),
BOX_hvcC = FOUR_CHAR_INT( 'h', 'v', 'c', 'C' ),
BOX_samr = FOUR_CHAR_INT( 's', 'a', 'm', 'r' ), BOX_sawb = FOUR_CHAR_INT( 's', 'a', 'w', 'b' ), BOX_damr = FOUR_CHAR_INT( 'd', 'a', 'm', 'r' ), BOX_s263 = FOUR_CHAR_INT( 's', '2', '6', '3' ), BOX_d263 = FOUR_CHAR_INT( 'd', '2', '6', '3' ),
BOX_mvex = FOUR_CHAR_INT( 'm', 'v', 'e', 'x' ), BOX_trex = FOUR_CHAR_INT( 't', 'r', 'e', 'x' ), BOX_moof = FOUR_CHAR_INT( 'm', 'o', 'o', 'f' ), BOX_mfhd = FOUR_CHAR_INT( 'm', 'f', 'h', 'd' ), BOX_traf = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ), BOX_tfhd = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ), BOX_tfdt = FOUR_CHAR_INT( 't', 'f', 'd', 't' ), BOX_trun = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ), BOX_mehd = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),
OD_BASE = FOUR_CHAR_INT( '$', '$', '$', '0' ), OD_ESD = FOUR_CHAR_INT( '$', '$', '$', '3' ), OD_DCD = FOUR_CHAR_INT( '$', '$', '$', '4' ), OD_DSI = FOUR_CHAR_INT( '$', '$', '$', '5' ), OD_SLC = FOUR_CHAR_INT( '$', '$', '$', '6' ),
BOX_meta = FOUR_CHAR_INT( 'm', 'e', 't', 'a' ),
BOX_ilst = FOUR_CHAR_INT( 'i', 'l', 's', 't' ),
BOX_calb = FOUR_CHAR_INT( '\xa9', 'a', 'l', 'b'), BOX_cart = FOUR_CHAR_INT( '\xa9', 'a', 'r', 't'), BOX_aART = FOUR_CHAR_INT( 'a', 'A', 'R', 'T' ), BOX_ccmt = FOUR_CHAR_INT( '\xa9', 'c', 'm', 't'), BOX_cday = FOUR_CHAR_INT( '\xa9', 'd', 'a', 'y'), BOX_cnam = FOUR_CHAR_INT( '\xa9', 'n', 'a', 'm'), BOX_cgen = FOUR_CHAR_INT( '\xa9', 'g', 'e', 'n'), BOX_trkn = FOUR_CHAR_INT( 't', 'r', 'k', 'n'), BOX_disk = FOUR_CHAR_INT( 'd', 'i', 's', 'k'), BOX_cwrt = FOUR_CHAR_INT( '\xa9', 'w', 'r', 't'), BOX_ctoo = FOUR_CHAR_INT( '\xa9', 't', 'o', 'o'), BOX_tmpo = FOUR_CHAR_INT( 't', 'm', 'p', 'o'), BOX_cpil = FOUR_CHAR_INT( 'c', 'p', 'i', 'l'), BOX_covr = FOUR_CHAR_INT( 'c', 'o', 'v', 'r'), BOX_rtng = FOUR_CHAR_INT( 'r', 't', 'n', 'g'), BOX_cgrp = FOUR_CHAR_INT( '\xa9', 'g', 'r', 'p'), BOX_stik = FOUR_CHAR_INT( 's', 't', 'i', 'k'), BOX_pcst = FOUR_CHAR_INT( 'p', 'c', 's', 't'), BOX_catg = FOUR_CHAR_INT( 'c', 'a', 't', 'g'), BOX_keyw = FOUR_CHAR_INT( 'k', 'e', 'y', 'w'), BOX_purl = FOUR_CHAR_INT( 'p', 'u', 'r', 'l'), BOX_egid = FOUR_CHAR_INT( 'e', 'g', 'i', 'd'), BOX_desc = FOUR_CHAR_INT( 'd', 'e', 's', 'c'), BOX_clyr = FOUR_CHAR_INT( '\xa9', 'l', 'y', 'r'), BOX_tven = FOUR_CHAR_INT( 't', 'v', 'e', 'n'), BOX_tves = FOUR_CHAR_INT( 't', 'v', 'e', 's'), BOX_tvnn = FOUR_CHAR_INT( 't', 'v', 'n', 'n'), BOX_tvsh = FOUR_CHAR_INT( 't', 'v', 's', 'h'), BOX_tvsn = FOUR_CHAR_INT( 't', 'v', 's', 'n'), BOX_purd = FOUR_CHAR_INT( 'p', 'u', 'r', 'd'), BOX_pgap = FOUR_CHAR_INT( 'p', 'g', 'a', 'p'),
BOX_cART = FOUR_CHAR_INT( '\xa9', 'A', 'R', 'T'), BOX_gnre = FOUR_CHAR_INT( 'g', 'n', 'r', 'e'),
BOX_auth = FOUR_CHAR_INT( 'a', 'u', 't', 'h'), BOX_titl = FOUR_CHAR_INT( 't', 'i', 't', 'l'), BOX_dscp = FOUR_CHAR_INT( 'd', 's', 'c', 'p'), BOX_perf = FOUR_CHAR_INT( 'p', 'e', 'r', 'f'), BOX_mean = FOUR_CHAR_INT( 'm', 'e', 'a', 'n'), BOX_name = FOUR_CHAR_INT( 'n', 'a', 'm', 'e'), BOX_data = FOUR_CHAR_INT( 'd', 'a', 't', 'a'),
BOX_albm = FOUR_CHAR_INT( 'a', 'l', 'b', 'm'), BOX_yrrc = FOUR_CHAR_INT( 'y', 'r', 'r', 'c') };
#define MP4E_HANDLER_TYPE_VIDE 0x76696465
#define MP4E_HANDLER_TYPE_SOUN 0x736F756E
#define MP4E_HANDLER_TYPE_GESM 0x6765736D
typedef struct
{
boxsize_t size;
boxsize_t offset;
unsigned duration;
unsigned flag_random_access;
} sample_t;
typedef struct {
unsigned char *data;
int bytes;
int capacity;
} minimp4_vector_t;
typedef struct
{
MP4E_track_t info;
minimp4_vector_t smpl; minimp4_vector_t pending_sample;
minimp4_vector_t vsps; minimp4_vector_t vpps; minimp4_vector_t vvps;
} track_t;
typedef struct MP4E_mux_tag
{
minimp4_vector_t tracks;
int64_t write_pos;
int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token);
void *token;
char *text_comment;
int sequential_mode_flag;
int enable_fragmentation; int fragments_count;
} MP4E_mux_t;
static const unsigned char box_ftyp[] = {
#if 1
0,0,0,0x18,'f','t','y','p',
'm','p','4','2',0,0,0,0,
'm','p','4','2','i','s','o','m',
#else#endif
};
#define WR(x, n) *p++ = (unsigned char)((x) >> 8*n)
#define WRITE_1(x) WR(x, 0);
#define WRITE_2(x) WR(x, 1); WR(x, 0);
#define WRITE_3(x) WR(x, 2); WR(x, 1); WR(x, 0);
#define WRITE_4(x) WR(x, 3); WR(x, 2); WR(x, 1); WR(x, 0);
#define WR4(p, x) (p)[0] = (char)((x) >> 8*3); (p)[1] = (char)((x) >> 8*2); (p)[2] = (char)((x) >> 8*1); (p)[3] = (char)((x));
#define END_ATOM --stack; WR4((unsigned char*)*stack, p - *stack);
#define ATOM(x) *stack++ = p; p += 4; WRITE_4(x);
#define ATOM_FULL(x, flag) ATOM(x); WRITE_4(flag);
#define ERR(func) { int err = func; if (err) return err; }
static int minimp4_vector_init(minimp4_vector_t *h, int capacity)
{
h->bytes = 0;
h->capacity = capacity;
h->data = capacity ? (unsigned char *)malloc(capacity) : NULL;
return !capacity || !!h->data;
}
static void minimp4_vector_reset(minimp4_vector_t *h)
{
if (h->data)
free(h->data);
memset(h, 0, sizeof(minimp4_vector_t));
}
static int minimp4_vector_grow(minimp4_vector_t *h, int bytes)
{
void *p;
int new_size = h->capacity*2 + 1024;
if (new_size < h->capacity + bytes)
new_size = h->capacity + bytes + 1024;
p = realloc(h->data, new_size);
if (!p)
return 0;
h->data = (unsigned char*)p;
h->capacity = new_size;
return 1;
}
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))
return NULL;
assert(h->data);
assert((h->capacity - h->bytes) >= bytes);
p = h->data + h->bytes;
h->bytes += bytes;
return p;
}
static unsigned char *minimp4_vector_put(minimp4_vector_t *h, const void *buf, int bytes)
{
unsigned char *tail = minimp4_vector_alloc_tail(h, bytes);
if (tail)
memcpy(tail, buf, bytes);
return tail;
}
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))
{
if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) 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);
if (!mux->sequential_mode_flag)
{ if (mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token))
{
free(mux);
return 0;
}
mux->write_pos += 16; }
minimp4_vector_init(&mux->tracks, 2*sizeof(track_t));
return mux;
}
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);
return ntr;
}
static const unsigned char *next_dsi(const unsigned char *p, const unsigned char *end, int *bytes)
{
if (p < end + 2)
{
*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)
{
int i;
unsigned char size[2];
const unsigned char *p = v->data;
for (i = 0; i + 2 < v->bytes;)
{
int cb = p[i]*256 + p[i + 1];
if (cb == bytes && !memcmp(p + i + 2, mem, cb))
return 1;
i += 2 + cb;
}
size[0] = bytes >> 8;
size[1] = bytes;
return minimp4_vector_put(v, size, 2) && minimp4_vector_put(v, mem, bytes);
}
static int items_count(minimp4_vector_t *v)
{
int i, count = 0;
const unsigned char *p = v->data;
for (i = 0; i + 2 < v->bytes;)
{
int cb = p[i]*256 + p[i + 1];
count++;
i += 2 + cb;
}
return count;
}
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 ||
tr->info.track_media_kind == e_private);
if (tr->vsps.bytes)
return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; 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)
{
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)
{
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)
{
track_t* tr = ((track_t*)mux->tracks.data) + track_id;
assert(tr->info.track_media_kind == e_video);
return append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
}
static unsigned get_duration(const track_t *tr)
{
unsigned i, sum_duration = 0;
const sample_t *s = (const sample_t *)tr->smpl.data;
for (i = 0; i < tr->smpl.bytes/sizeof(sample_t); i++)
{
sum_duration += s[i].duration;
}
return sum_duration;
}
static int write_pending_data(MP4E_mux_t *mux, track_t *tr)
{
if (tr->pending_sample.bytes > 0 && tr->smpl.bytes >= sizeof(sample_t))
{
sample_t *smpl_desc;
unsigned char base[8], *p = base;
assert(mux->sequential_mode_flag);
assert(mux->sequential_mode_flag); WRITE_4(tr->pending_sample.bytes + 8);
WRITE_4(BOX_mdat);
ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
mux->write_pos += p - base;
smpl_desc = ((sample_t*)minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1;
smpl_desc->size = tr->pending_sample.bytes;
smpl_desc->offset = (boxsize_t)mux->write_pos;
ERR(mux->write_callback(mux->write_pos, tr->pending_sample.data, tr->pending_sample.bytes, mux->token));
mux->write_pos += tr->pending_sample.bytes;
tr->pending_sample.bytes = 0;
}
return MP4E_STATUS_OK;
}
static int add_sample_descriptor(MP4E_mux_t *mux, track_t *tr, int data_bytes, int duration, int kind)
{
sample_t smp;
smp.size = data_bytes;
smp.offset = (boxsize_t)mux->write_pos;
smp.duration = (duration ? duration : tr->info.default_duration);
smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS);
return NULL != minimp4_vector_put(&tr->smpl, &smp, sizeof(sample_t));
}
static int mp4e_flush_index(MP4E_mux_t *mux);
static int mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int duration, int kind
#if MP4D_TFDT_SUPPORT
, uint64_t timestamp
#endif
)
{
unsigned char base[888], *p = base;
unsigned char *stack_base[20]; 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); END_ATOM
ATOM(BOX_traf)
flags = 0;
if (tr->info.track_media_kind == e_video)
flags |= 0x20; else
flags |= 0x08; flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008;
ATOM_FULL(BOX_tfhd, flags)
WRITE_4(track_num + 1); if (tr->info.track_media_kind == e_video)
{
WRITE_4(0x1010000); } else
{
WRITE_4(duration);
}
END_ATOM
#if MP4D_TFDT_SUPPORT
ATOM_FULL(BOX_tfdt, 0x01000000) WRITE_4(timestamp >> 32); WRITE_4(timestamp & 0xffffffff); END_ATOM
#endif
if (tr->info.track_media_kind == e_audio)
{
flags = 0;
flags |= 0x001; flags |= 0x200; ATOM_FULL(BOX_trun, flags)
WRITE_4(1); pdata_offset = p; p += 4; WRITE_4(data_bytes); END_ATOM
} else if (kind == MP4E_SAMPLE_RANDOM_ACCESS)
{
flags = 0;
flags |= 0x001; flags |= 0x004; flags |= 0x100; flags |= 0x200; ATOM_FULL(BOX_trun, flags)
WRITE_4(1); pdata_offset = p; p += 4; WRITE_4(0x2000000); WRITE_4(duration); WRITE_4(data_bytes); END_ATOM
} else
{
flags = 0;
flags |= 0x001; flags |= 0x100; flags |= 0x200; ATOM_FULL(BOX_trun, flags)
WRITE_4(1); pdata_offset = p; p += 4; WRITE_4(duration); WRITE_4(data_bytes); END_ATOM
}
END_ATOM
END_ATOM
WR4(pdata_offset, (p - base) + 8);
ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
mux->write_pos += p - base;
return MP4E_STATUS_OK;
}
static int mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size)
{
unsigned char base[8], *p = base;
WRITE_4(size);
WRITE_4(BOX_mdat);
ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
mux->write_pos += p - base;
return MP4E_STATUS_OK;
}
int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const 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 MP4D_TFDT_SUPPORT
uint64_t timestamp = (uint64_t)mux->fragments_count * duration;
#endif
if (!mux->fragments_count++)
ERR(mp4e_flush_index(mux)); #if MP4D_TFDT_SUPPORT
ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind, timestamp));
#else
ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind));
#endif
ERR(mp4e_write_mdat_box(mux, data_bytes + 8));
ERR(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)
ERR(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))
return MP4E_STATUS_NO_MEMORY; smpl_desc = (sample_t*)(tr->smpl.data + tr->smpl.bytes) - 1;
smpl_desc->size += data_bytes;
}
}
if (mux->sequential_mode_flag)
{
if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes))
return MP4E_STATUS_NO_MEMORY;
} else
{
ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token));
mux->write_pos += data_bytes;
}
return MP4E_STATUS_OK;
}
static int od_size_of_size(int size)
{
int i, size_of_size = 1;
for (i = size; i > 0x7F; i -= 0x7F)
size_of_size++;
return size_of_size;
}
int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment)
{
if (!mux || !comment)
return MP4E_STATUS_BAD_ARGUMENTS;
if (mux->text_comment)
free(mux->text_comment);
mux->text_comment = strdup(comment);
if (!mux->text_comment)
return MP4E_STATUS_NO_MEMORY;
return MP4E_STATUS_OK;
}
static int mp4e_flush_index(MP4E_mux_t *mux)
{
unsigned char *stack_base[20]; unsigned char **stack = stack_base;
unsigned char *base, *p;
unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t);
int i, err;
#define FILE_HEADER_BYTES 256
#define TRACK_HEADER_BYTES 512
index_bytes = FILE_HEADER_BYTES;
if (mux->text_comment)
index_bytes += 128 + strlen(mux->text_comment);
for (ntr = 0; ntr < ntracks; ntr++)
{
track_t *tr = ((track_t*)mux->tracks.data) + ntr;
index_bytes += TRACK_HEADER_BYTES; index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t);
index_bytes += tr->vsps.bytes;
index_bytes += tr->vpps.bytes;
ERR(write_pending_data(mux, tr));
}
base = (unsigned char*)malloc(index_bytes);
if (!base)
return MP4E_STATUS_NO_MEMORY;
p = base;
if (!mux->sequential_mode_flag)
{
int64_t size = mux->write_pos - sizeof(box_ftyp);
const int64_t size_limit = (int64_t)(uint64_t)0xfffffffe;
if (size > size_limit)
{
WRITE_4(1);
WRITE_4(BOX_mdat);
WRITE_4((size >> 32) & 0xffffffff);
WRITE_4(size & 0xffffffff);
} else
{
WRITE_4(8);
WRITE_4(BOX_free);
WRITE_4(size - 8);
WRITE_4(BOX_mdat);
}
ERR(mux->write_callback(sizeof(box_ftyp), base, p - base, mux->token));
p = base;
}
#define MOOV_TIMESCALE 1000
ATOM(BOX_moov);
ATOM_FULL(BOX_mvhd, 0);
WRITE_4(0); WRITE_4(0);
if (ntracks)
{
track_t *tr = ((track_t*)mux->tracks.data) + 0; unsigned duration = get_duration(tr);
duration = (unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale);
WRITE_4(MOOV_TIMESCALE); WRITE_4(duration); }
WRITE_4(0x00010000); WRITE_2(0x0100); WRITE_2(0); WRITE_4(0); WRITE_4(0);
WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0);
WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0);
WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000);
WRITE_4(0); WRITE_4(0); WRITE_4(0);
WRITE_4(0); WRITE_4(0); WRITE_4(0);
WRITE_4(ntracks + 1);
END_ATOM;
for (ntr = 0; ntr < ntracks; ntr++)
{
track_t *tr = ((track_t*)mux->tracks.data) + ntr;
unsigned duration = get_duration(tr);
int samples_count = tr->smpl.bytes / sizeof(sample_t);
const sample_t *sample = (const sample_t *)tr->smpl.data;
unsigned handler_type;
const char *handler_ascii = NULL;
if (mux->enable_fragmentation)
samples_count = 0;
else if (samples_count <= 0)
continue;
switch (tr->info.track_media_kind)
{
case e_audio:
handler_type = MP4E_HANDLER_TYPE_SOUN;
handler_ascii = "SoundHandler";
break;
case e_video:
handler_type = MP4E_HANDLER_TYPE_VIDE;
handler_ascii = "VideoHandler";
break;
case e_private:
handler_type = MP4E_HANDLER_TYPE_GESM;
break;
default:
return MP4E_STATUS_BAD_ARGUMENTS;
}
ATOM(BOX_trak);
ATOM_FULL(BOX_tkhd, 7); WRITE_4(0); WRITE_4(0); WRITE_4(ntr + 1); WRITE_4(0); WRITE_4((unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale));
WRITE_4(0); WRITE_4(0); WRITE_2(0); WRITE_2(0); WRITE_2(0x0100); WRITE_2(0);
WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0);
WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0);
WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000);
if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private)
{
WRITE_4(0); WRITE_4(0); } else
{
WRITE_4(tr->info.u.v.width*0x10000); WRITE_4(tr->info.u.v.height*0x10000); }
END_ATOM;
ATOM(BOX_mdia);
ATOM_FULL(BOX_mdhd, 0);
WRITE_4(0); WRITE_4(0); WRITE_4(tr->info.time_scale);
WRITE_4(duration); {
int lang_code = ((tr->info.language[0] & 31) << 10) | ((tr->info.language[1] & 31) << 5) | (tr->info.language[2] & 31);
WRITE_2(lang_code); }
WRITE_2(0); END_ATOM;
ATOM_FULL(BOX_hdlr, 0);
WRITE_4(0); WRITE_4(handler_type); WRITE_4(0); WRITE_4(0); WRITE_4(0); if (handler_ascii)
{
for (i = 0; i < (int)strlen(handler_ascii) + 1; i++)
{
WRITE_1(handler_ascii[i]);
}
} else
{
WRITE_4(0);
}
END_ATOM;
ATOM(BOX_minf);
if (tr->info.track_media_kind == e_audio)
{
ATOM_FULL(BOX_smhd, 0);
WRITE_2(0); WRITE_2(0); END_ATOM;
}
if (tr->info.track_media_kind == e_video)
{
ATOM_FULL(BOX_vmhd, 1);
WRITE_2(0); WRITE_2(0); WRITE_2(0); WRITE_2(0); END_ATOM;
}
ATOM(BOX_dinf);
ATOM_FULL(BOX_dref, 0);
WRITE_4(1);
ATOM_FULL(BOX_url, 1);
END_ATOM;
END_ATOM;
END_ATOM;
ATOM(BOX_stbl);
ATOM_FULL(BOX_stsd, 0);
WRITE_4(1);
if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private)
{
if (tr->info.track_media_kind == e_audio)
{
ATOM(BOX_mp4a);
} else
{
ATOM(BOX_mp4s);
}
WRITE_4(0); WRITE_2(0); WRITE_2(1);
if (tr->info.track_media_kind == e_audio)
{
WRITE_4(0); WRITE_4(0); WRITE_2(tr->info.u.a.channelcount); WRITE_2(16); WRITE_4(0); WRITE_4((tr->info.time_scale << 16)); }
ATOM_FULL(BOX_esds, 0);
if (tr->vsps.bytes > 0)
{
int dsi_bytes = tr->vsps.bytes - 2; int dsi_size_size = od_size_of_size(dsi_bytes);
int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1 + 1 + 3 + 4 + 4);
int dcd_size_size = od_size_of_size(dcd_bytes);
int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3;
#define WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WRITE_1(0x00ff); } while (size > 0x7F); WRITE_1(size)
WRITE_1(3); WRITE_OD_LEN(esd_bytes);
WRITE_2(0); WRITE_1(0);
WRITE_1(4); WRITE_OD_LEN(dcd_bytes);
if (tr->info.track_media_kind == e_audio)
{
WRITE_1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); WRITE_1(5 << 2); } else
{
WRITE_1(208); WRITE_1(32 << 2); }
WRITE_3(tr->info.u.a.channelcount * 6144/8); WRITE_4(0); WRITE_4(0);
WRITE_1(5); WRITE_OD_LEN(dsi_bytes);
for (i = 0; i < dsi_bytes; i++)
{
WRITE_1(tr->vsps.data[2 + i]);
}
}
END_ATOM;
END_ATOM;
}
if (tr->info.track_media_kind == e_video && (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication || MP4_OBJECT_TYPE_HEVC == tr->info.object_type_indication))
{
int numOfSequenceParameterSets = items_count(&tr->vsps);
int numOfPictureParameterSets = items_count(&tr->vpps);
if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication)
{
ATOM(BOX_avc1);
} else
{
ATOM(BOX_hvc1);
}
WRITE_2(0); WRITE_2(0); WRITE_2(0); WRITE_2(1);
WRITE_2(0); WRITE_2(0); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_2(tr->info.u.v.width);
WRITE_2(tr->info.u.v.height);
WRITE_4(0x00480000); WRITE_4(0x00480000); WRITE_4(0); WRITE_2(1); for (i = 0; i < 32; i++)
{
WRITE_1(0); }
WRITE_2(24); WRITE_2(-1);
if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication)
{
ATOM(BOX_avcC);
WRITE_1(1); WRITE_1(tr->vsps.data[2 + 1]);
WRITE_1(tr->vsps.data[2 + 2]);
WRITE_1(tr->vsps.data[2 + 3]);
WRITE_1(255); WRITE_1(0xe0 | numOfSequenceParameterSets);
for (i = 0; i < tr->vsps.bytes; i++)
{
WRITE_1(tr->vsps.data[i]);
}
WRITE_1(numOfPictureParameterSets);
for (i = 0; i < tr->vpps.bytes; i++)
{
WRITE_1(tr->vpps.data[i]);
}
} else
{
int numOfVPS = items_count(&tr->vpps);
ATOM(BOX_hvcC);
WRITE_1(1); WRITE_1(1); WRITE_4(0x60000000); WRITE_2(0); WRITE_4(0); WRITE_1(0); WRITE_2(0xf000); WRITE_1(0xfc); WRITE_1(0xfc); WRITE_1(0xf8); WRITE_1(0xf8); WRITE_2(0); WRITE_1(3);
WRITE_1(3); WRITE_1((1 << 7) | (HEVC_NAL_VPS & 0x3f)); WRITE_2(numOfVPS);
for (i = 0; i < tr->vvps.bytes; i++)
{
WRITE_1(tr->vvps.data[i]);
}
WRITE_1((1 << 7) | (HEVC_NAL_SPS & 0x3f));
WRITE_2(numOfSequenceParameterSets);
for (i = 0; i < tr->vsps.bytes; i++)
{
WRITE_1(tr->vsps.data[i]);
}
WRITE_1((1 << 7) | (HEVC_NAL_PPS & 0x3f));
WRITE_2(numOfPictureParameterSets);
for (i = 0; i < tr->vpps.bytes; i++)
{
WRITE_1(tr->vpps.data[i]);
}
}
END_ATOM;
END_ATOM;
}
END_ATOM;
ATOM_FULL(BOX_stts, 0);
{
unsigned char *pentry_count = p;
int cnt = 1, entry_count = 0;
WRITE_4(0);
for (i = 0; i < samples_count; i++, cnt++)
{
if (i == (samples_count - 1) || sample[i].duration != sample[i + 1].duration)
{
WRITE_4(cnt);
WRITE_4(sample[i].duration);
cnt = 0;
entry_count++;
}
}
WR4(pentry_count, entry_count);
}
END_ATOM;
ATOM_FULL(BOX_stsc, 0);
if (mux->enable_fragmentation)
{
WRITE_4(0); } else
{
WRITE_4(1); WRITE_4(1); WRITE_4(1); WRITE_4(1); }
END_ATOM;
ATOM_FULL(BOX_stsz, 0);
WRITE_4(0); WRITE_4(samples_count); for (i = 0; i < samples_count; i++)
{
WRITE_4(sample[i].size);
}
END_ATOM;
int is_64_bit = 0;
if (samples_count && sample[samples_count - 1].offset > 0xffffffff)
is_64_bit = 1;
if (!is_64_bit)
{
ATOM_FULL(BOX_stco, 0);
WRITE_4(samples_count);
for (i = 0; i < samples_count; i++)
{
WRITE_4(sample[i].offset);
}
} else
{
ATOM_FULL(BOX_co64, 0);
WRITE_4(samples_count);
for (i = 0; i < samples_count; i++)
{
WRITE_4((sample[i].offset >> 32) & 0xffffffff);
WRITE_4(sample[i].offset & 0xffffffff);
}
}
END_ATOM;
{
int ra_count = 0;
for (i = 0; i < samples_count; i++)
{
ra_count += !!sample[i].flag_random_access;
}
if (ra_count != samples_count)
{
ATOM_FULL(BOX_stss, 0);
WRITE_4(ra_count);
for (i = 0; i < samples_count; i++)
{
if (sample[i].flag_random_access)
{
WRITE_4(i + 1);
}
}
END_ATOM;
}
}
END_ATOM;
END_ATOM;
END_ATOM;
END_ATOM;
}
if (mux->text_comment)
{
ATOM(BOX_udta);
ATOM_FULL(BOX_meta, 0);
ATOM_FULL(BOX_hdlr, 0);
WRITE_4(0); #define MP4E_HANDLER_TYPE_MDIR 0x6d646972
WRITE_4(MP4E_HANDLER_TYPE_MDIR); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0); END_ATOM;
ATOM(BOX_ilst);
ATOM(BOX_ccmt);
ATOM(BOX_data);
WRITE_4(1); WRITE_4(0); for (i = 0; i < (int)strlen(mux->text_comment) + 1; i++)
{
WRITE_1(mux->text_comment[i]);
}
END_ATOM;
END_ATOM;
END_ATOM;
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); END_ATOM;
for (ntr = 0; ntr < ntracks; ntr++)
{
ATOM_FULL(BOX_trex, 0);
WRITE_4(ntr + 1); WRITE_4(1); WRITE_4(0); WRITE_4(0); WRITE_4(0); END_ATOM;
}
END_ATOM;
}
END_ATOM;
assert((unsigned)(p - base) <= index_bytes);
err = mux->write_callback(mux->write_pos, base, p - base, mux->token);
mux->write_pos += p - base;
free(base);
return err;
}
int MP4E_close(MP4E_mux_t *mux)
{
int err = MP4E_STATUS_OK;
unsigned ntr, ntracks;
if (!mux)
return MP4E_STATUS_BAD_ARGUMENTS;
if (!mux->enable_fragmentation)
err = mp4e_flush_index(mux);
if (mux->text_comment)
free(mux->text_comment);
ntracks = mux->tracks.bytes / sizeof(track_t);
for (ntr = 0; ntr < ntracks; ntr++)
{
track_t *tr = ((track_t*)mux->tracks.data) + ntr;
minimp4_vector_reset(&tr->vsps);
minimp4_vector_reset(&tr->vpps);
minimp4_vector_reset(&tr->smpl);
minimp4_vector_reset(&tr->pending_sample);
}
minimp4_vector_reset(&mux->tracks);
free(mux);
return err;
}
typedef uint32_t bs_item_t;
#define BS_BITS 32
typedef struct
{
unsigned int cache;
int cache_free_bits;
const uint16_t *buf;
const uint16_t *origin;
unsigned origin_bytes;
} bit_reader_t;
#define LOAD_SHORT(x) ((uint16_t)(x << 8) | (x >> 8))
static unsigned int show_bits(bit_reader_t *bs, int n)
{
unsigned int retval;
assert(n > 0 && n <= 16);
retval = (unsigned int)(bs->cache >> (32 - n));
return retval;
}
static void flush_bits(bit_reader_t *bs, int n)
{
assert(n >= 0 && n <= 16);
bs->cache <<= n;
bs->cache_free_bits += n;
if (bs->cache_free_bits >= 0)
{
bs->cache |= ((uint32_t)LOAD_SHORT(*bs->buf)) << bs->cache_free_bits;
bs->buf++;
bs->cache_free_bits -= 16;
}
}
static unsigned int get_bits(bit_reader_t *bs, int n)
{
unsigned int retval = show_bits(bs, n);
flush_bits(bs, n);
return retval;
}
static void set_pos_bits(bit_reader_t *bs, unsigned pos_bits)
{
assert((int)pos_bits >= 0);
bs->buf = bs->origin + pos_bits/16;
bs->cache = 0;
bs->cache_free_bits = 16;
flush_bits(bs, 0);
flush_bits(bs, pos_bits & 15);
}
static unsigned get_pos_bits(const bit_reader_t *bs)
{
unsigned pos_bits = (unsigned)(bs->buf - bs->origin)*16;
pos_bits -= 16 - bs->cache_free_bits;
assert((int)pos_bits >= 0);
return pos_bits;
}
static int remaining_bits(const bit_reader_t *bs)
{
return bs->origin_bytes * 8 - get_pos_bits(bs);
}
static void init_bits(bit_reader_t *bs, const void *data, unsigned data_bytes)
{
bs->origin = (const uint16_t *)data;
bs->origin_bytes = data_bytes;
set_pos_bits(bs, 0);
}
#define GetBits(n) get_bits(bs, n)
static int ue_bits(bit_reader_t *bs)
{
int clz;
int val;
for (clz = 0; !get_bits(bs, 1); clz++) {}
val = (1 << clz) - 1 + (clz ? get_bits(bs, clz) : 0);
return val;
}
#if MINIMP4_TRANSCODE_SPS_ID
typedef struct
{
int shift; uint32_t cache; bs_item_t *buf; bs_item_t *origin; } bs_t;
#define SWAP32(x) (uint32_t)((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x & 0xFF) << 24))
static void h264e_bs_put_bits(bs_t *bs, unsigned n, unsigned val)
{
assert(!(val >> n));
bs->shift -= n;
assert((unsigned)n <= 32);
if (bs->shift < 0)
{
assert(-bs->shift < 32);
bs->cache |= val >> -bs->shift;
*bs->buf++ = SWAP32(bs->cache);
bs->shift = 32 + bs->shift;
bs->cache = 0;
}
bs->cache |= val << bs->shift;
}
static void h264e_bs_flush(bs_t *bs)
{
*bs->buf = SWAP32(bs->cache);
}
static unsigned h264e_bs_get_pos_bits(const bs_t *bs)
{
unsigned pos_bits = (unsigned)((bs->buf - bs->origin)*BS_BITS);
pos_bits += BS_BITS - bs->shift;
assert((int)pos_bits >= 0);
return pos_bits;
}
static unsigned h264e_bs_byte_align(bs_t *bs)
{
int pos = h264e_bs_get_pos_bits(bs);
h264e_bs_put_bits(bs, -pos & 7, 0);
return pos + (-pos & 7);
}
static void h264e_bs_put_golomb(bs_t *bs, unsigned val)
{
int size = 0;
unsigned t = val + 1;
do
{
size++;
} while (t >>= 1);
h264e_bs_put_bits(bs, 2*size - 1, val + 1);
}
static void h264e_bs_init_bits(bs_t *bs, void *data)
{
bs->origin = (bs_item_t*)data;
bs->buf = bs->origin;
bs->shift = BS_BITS;
bs->cache = 0;
}
static int find_mem_cache(void *cache[], int cache_bytes[], int cache_size, void *mem, int bytes)
{
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; }
for (i = 0; i < cache_size; i++)
{
if (!cache_bytes[i])
{
cache[i] = malloc(bytes);
if (cache[i])
{
memcpy(cache[i], mem, bytes);
cache_bytes[i] = bytes;
}
return i; }
}
return -1; }
static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int h264_data_bytes)
{
int i = 0, j = 0, zero_cnt = 0;
for (j = 0; j < h264_data_bytes; j++)
{
if (zero_cnt == 2 && src[j] <= 3)
{
if (src[j] == 3)
{
if (j == h264_data_bytes - 1)
{
} else if (src[j + 1] <= 3)
{
j++;
zero_cnt = 0;
} else
{
}
} else
return 0;
}
dst[i++] = src[j];
if (src[j])
zero_cnt = 0;
else
zero_cnt++;
}
return i;
}
static int nal_put_esc(uint8_t *d, const uint8_t *s, int n)
{
int i, j = 4, cntz = 0;
d[0] = d[1] = d[2] = 0; d[3] = 1; for (i = 0; i < n; i++)
{
uint8_t byte = *s++;
if (cntz == 2 && byte <= 3)
{
d[j++] = 3;
cntz = 0;
}
if (byte)
cntz = 0;
else
cntz++;
d[j++] = byte;
}
return j;
}
static void copy_bits(bit_reader_t *bs, bs_t *bd)
{
unsigned cb, bits;
int bit_count = remaining_bits(bs);
while (bit_count > 7)
{
cb = MINIMP4_MIN(bit_count - 7, 8);
bits = GetBits(cb);
h264e_bs_put_bits(bd, cb, bits);
bit_count -= cb;
}
bits = GetBits(bit_count);
for (; bit_count && ~bits & 1; bit_count--)
{
bits >>= 1;
}
if (bit_count)
{
h264e_bs_put_bits(bd, bit_count, bits);
}
}
static int change_sps_id(bit_reader_t *bs, bs_t *bd, int new_id, int *old_id)
{
unsigned bits, sps_id, i, bytes;
for (i = 0; i < 3; i++)
{
bits = GetBits(8);
h264e_bs_put_bits(bd, 8, bits);
}
sps_id = ue_bits(bs);
*old_id = sps_id;
sps_id = new_id;
assert(sps_id <= 31);
h264e_bs_put_golomb(bd, sps_id);
copy_bits(bs, bd);
bytes = h264e_bs_byte_align(bd) / 8;
h264e_bs_flush(bd);
return bytes;
}
static int patch_pps(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd, int new_pps_id, int *old_id)
{
int bytes;
unsigned pps_id = ue_bits(bs); unsigned sps_id = ue_bits(bs);
*old_id = pps_id;
sps_id = h->map_sps[sps_id];
pps_id = new_pps_id;
assert(sps_id <= 31);
assert(pps_id <= 255);
h264e_bs_put_golomb(bd, pps_id);
h264e_bs_put_golomb(bd, sps_id);
copy_bits(bs, bd);
bytes = h264e_bs_byte_align(bd) / 8;
h264e_bs_flush(bd);
return bytes;
}
static void patch_slice_header(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd)
{
unsigned first_mb_in_slice = ue_bits(bs);
unsigned slice_type = ue_bits(bs);
unsigned pps_id = ue_bits(bs);
pps_id = h->map_pps[pps_id];
assert(pps_id <= 255);
h264e_bs_put_golomb(bd, first_mb_in_slice);
h264e_bs_put_golomb(bd, slice_type);
h264e_bs_put_golomb(bd, pps_id);
copy_bits(bs, bd);
}
static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, int nalu_bytes, unsigned char *dst)
{
int old_id;
bit_reader_t bst[1];
bs_t bdt[1];
bit_reader_t bs[1];
bs_t bd[1];
int payload_type = src[0] & 31;
*dst = *src;
h264e_bs_init_bits(bd, dst + 1);
init_bits(bs, src + 1, nalu_bytes - 1);
h264e_bs_init_bits(bdt, dst + 1);
init_bits(bst, src + 1, nalu_bytes - 1);
switch(payload_type)
{
case 7:
{
int cb = change_sps_id(bst, bdt, 0, &old_id);
int id = find_mem_cache(h->sps_cache, h->sps_bytes, MINIMP4_MAX_SPS, dst + 1, cb);
if (id == -1)
return 0;
h->map_sps[old_id] = id;
change_sps_id(bs, bd, id, &old_id);
}
break;
case 8:
{
int cb = patch_pps(h, bst, bdt, 0, &old_id);
int id = find_mem_cache(h->pps_cache, h->pps_bytes, MINIMP4_MAX_PPS, dst + 1, cb);
if (id == -1)
return 0;
h->map_pps[old_id] = id;
patch_pps(h, bs, bd, id, &old_id);
}
break;
case 1:
case 2:
case 5:
patch_slice_header(h, bs, bd);
break;
default:
memcpy(dst, src, nalu_bytes);
return nalu_bytes;
}
nalu_bytes = 1 + h264e_bs_byte_align(bd) / 8;
h264e_bs_flush(bd);
return nalu_bytes;
}
#endif
static const uint8_t *find_start_code(const uint8_t *h264_data, int h264_data_bytes, int *zcount)
{
const uint8_t *eof = h264_data + h264_data_bytes;
const uint8_t *p = h264_data;
do
{
int zero_cnt = 1;
const uint8_t* found = (uint8_t*)memchr(p, 0, eof - p);
p = found ? found : eof;
while (p + zero_cnt < eof && !p[zero_cnt]) zero_cnt++;
if (zero_cnt >= 2 && p[zero_cnt] == 1)
{
*zcount = zero_cnt + 1;
return p + zero_cnt + 1;
}
p += zero_cnt;
} while (p < eof);
*zcount = 0;
return eof;
}
static const uint8_t *find_nal_unit(const uint8_t *h264_data, int h264_data_bytes, int *pnal_unit_bytes)
{
const uint8_t *eof = h264_data + h264_data_bytes;
int zcount;
const uint8_t *start = find_start_code(h264_data, h264_data_bytes, &zcount);
const uint8_t *stop = start;
if (start)
{
stop = find_start_code(start, (int)(eof - start), &zcount);
while (stop > start && !stop[-1])
{
stop--;
}
}
*pnal_unit_bytes = (int)(stop - start - zcount);
return start;
}
int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc)
{
MP4E_track_t tr;
tr.track_media_kind = e_video;
tr.language[0] = 'u';
tr.language[1] = 'n';
tr.language[2] = 'd';
tr.language[3] = 0;
tr.object_type_indication = is_hevc ? MP4_OBJECT_TYPE_HEVC : MP4_OBJECT_TYPE_AVC;
tr.time_scale = 90000;
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 = mux;
h->is_hevc = is_hevc;
h->need_vps = is_hevc;
h->need_sps = 1;
h->need_pps = 1;
h->need_idr = 1;
#if MINIMP4_TRANSCODE_SPS_ID
memset(&h->sps_patcher, 0, sizeof(h264_sps_id_patcher_t));
#endif
return MP4E_STATUS_OK;
}
void mp4_h26x_write_close(mp4_h26x_writer_t *h)
{
#if MINIMP4_TRANSCODE_SPS_ID
h264_sps_id_patcher_t *p = &h->sps_patcher;
int i;
for (i = 0; i < MINIMP4_MAX_SPS; i++)
{
if (p->sps_cache[i])
free(p->sps_cache[i]);
}
for (i = 0; i < MINIMP4_MAX_PPS; i++)
{
if (p->pps_cache[i])
free(p->pps_cache[i]);
}
#endif
memset(h, 0, sizeof(*h));
}
static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int sizeof_nal, unsigned timeStamp90kHz_next)
{
int payload_type = (nal[0] >> 1) & 0x3f;
int is_intra = payload_type >= HEVC_NAL_BLA_W_LP && payload_type <= HEVC_NAL_CRA_NUT;
int err = MP4E_STATUS_OK;
if (is_intra && !h->need_sps && !h->need_pps && !h->need_vps)
h->need_idr = 0;
switch (payload_type)
{
case HEVC_NAL_VPS:
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);
h->need_sps = 0;
break;
case HEVC_NAL_PPS:
MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal);
h->need_pps = 0;
break;
default:
if (h->need_vps || h->need_sps || h->need_pps || h->need_idr)
return MP4E_STATUS_BAD_ARGUMENTS;
{
unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal);
if (!tmp)
return MP4E_STATUS_NO_MEMORY;
int sample_kind = MP4E_SAMPLE_DEFAULT;
tmp[0] = (unsigned char)(sizeof_nal >> 24);
tmp[1] = (unsigned char)(sizeof_nal >> 16);
tmp[2] = (unsigned char)(sizeof_nal >> 8);
tmp[3] = (unsigned char)(sizeof_nal);
memcpy(tmp + 4, nal, sizeof_nal);
if (is_intra)
sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind);
free(tmp);
}
break;
}
return err;
}
int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next)
{
const unsigned char *eof = nal + length;
int payload_type, sizeof_nal, err = MP4E_STATUS_OK;
for (;;nal++)
{
#if MINIMP4_TRANSCODE_SPS_ID
unsigned char *nal1, *nal2;
#endif
nal = find_nal_unit(nal, (int)(eof - nal), &sizeof_nal);
if (!sizeof_nal)
break;
if (h->is_hevc)
{
ERR(mp4_h265_write_nal(h, nal, sizeof_nal, timeStamp90kHz_next));
continue;
}
payload_type = nal[0] & 31;
if (9 == payload_type)
continue; #if MINIMP4_TRANSCODE_SPS_ID
nal1 = (unsigned char *)malloc(sizeof_nal*17/16 + 32);
if (!nal1)
return MP4E_STATUS_NO_MEMORY;
nal2 = (unsigned char *)malloc(sizeof_nal*17/16 + 32);
if (!nal2)
{
free(nal1);
return MP4E_STATUS_NO_MEMORY;
}
sizeof_nal = remove_nal_escapes(nal2, nal, sizeof_nal);
if (!sizeof_nal)
{
exit_with_free:
free(nal1);
free(nal2);
return MP4E_STATUS_BAD_ARGUMENTS;
}
sizeof_nal = transcode_nalu(&h->sps_patcher, nal2, sizeof_nal, nal1);
sizeof_nal = nal_put_esc(nal2, nal1, sizeof_nal);
switch (payload_type) {
case 7:
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)
goto exit_with_free;
MP4E_set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4);
h->need_pps = 0;
break;
case 5:
if (h->need_sps)
goto exit_with_free;
h->need_idr = 0;
default:
if (h->need_sps)
goto exit_with_free;
if (!h->need_pps && !h->need_idr)
{
bit_reader_t bs[1];
init_bits(bs, nal + 1, sizeof_nal - 4 - 1);
unsigned first_mb_in_slice = ue_bits(bs);
int sample_kind = MP4E_SAMPLE_DEFAULT;
nal2[0] = (unsigned char)((sizeof_nal - 4) >> 24);
nal2[1] = (unsigned char)((sizeof_nal - 4) >> 16);
nal2[2] = (unsigned char)((sizeof_nal - 4) >> 8);
nal2[3] = (unsigned char)((sizeof_nal - 4));
if (first_mb_in_slice)
sample_kind = MP4E_SAMPLE_CONTINUATION;
else if (payload_type == 5)
sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
err = MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind);
}
break;
}
free(nal1);
free(nal2);
#else
switch (payload_type) {
case 7:
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);
h->need_pps = 0;
break;
case 5:
if (h->need_sps)
return MP4E_STATUS_BAD_ARGUMENTS;
h->need_idr = 0;
default:
if (h->need_sps)
return MP4E_STATUS_BAD_ARGUMENTS;
if (!h->need_pps && !h->need_idr)
{
bit_reader_t bs[1];
unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal);
if (!tmp)
return MP4E_STATUS_NO_MEMORY;
init_bits(bs, nal + 1, sizeof_nal - 1);
unsigned first_mb_in_slice = ue_bits(bs);
int sample_kind = MP4E_SAMPLE_DEFAULT;
tmp[0] = (unsigned char)(sizeof_nal >> 24);
tmp[1] = (unsigned char)(sizeof_nal >> 16);
tmp[2] = (unsigned char)(sizeof_nal >> 8);
tmp[3] = (unsigned char)(sizeof_nal);
memcpy(tmp + 4, nal, sizeof_nal);
if (first_mb_in_slice)
sample_kind = MP4E_SAMPLE_CONTINUATION;
else if (payload_type == 5)
sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind);
free(tmp);
}
break;
}
#endif
if (err)
break;
}
return err;
}
#if MP4D_TRACE_SUPPORTED
# define TRACE(x) printf x
#else
# define TRACE(x)
#endif
#define NELEM(x) (sizeof(x) / sizeof((x)[0]))
static int minimp4_fgets(MP4D_demux_t *mp4)
{
uint8_t c;
if (mp4->read_callback(mp4->read_pos, &c, 1, mp4->token))
return -1;
mp4->read_pos++;
return c;
}
static unsigned minimp4_read(MP4D_demux_t *mp4, int nb, int *eof_flag)
{
uint32_t v = 0; int last_byte;
switch (nb)
{
case 4: v = (v << 8) | minimp4_fgets(mp4);
case 3: v = (v << 8) | minimp4_fgets(mp4);
case 2: v = (v << 8) | minimp4_fgets(mp4);
default:
case 1: v = (v << 8) | (last_byte = minimp4_fgets(mp4));
}
if (last_byte < 0)
{
*eof_flag = 1;
}
return v;
}
static uint32_t read_payload(MP4D_demux_t *mp4, unsigned nb, boxsize_t *payload_bytes, int *eof_flag)
{
if (*payload_bytes < nb)
{
*eof_flag = 1;
nb = (int)*payload_bytes;
}
*payload_bytes -= nb;
return minimp4_read(mp4, nb, eof_flag);
}
static void my_fseek(MP4D_demux_t *mp4, boxsize_t pos, int *eof_flag)
{
mp4->read_pos += pos;
if (mp4->read_pos >= mp4->read_size)
*eof_flag = 1;
}
#define READ(n) read_payload(mp4, n, &payload_bytes, &eof_flag)
#define SKIP(n) { boxsize_t t = MINIMP4_MIN(payload_bytes, n); my_fseek(mp4, t, &eof_flag); payload_bytes -= t; }
#define MALLOC(t, p, size) p = (t)malloc(size); if (!(p)) { ERROR("out of memory"); }
#define RETURN_ERROR(mess) { \
TRACE(("\nMP4 ERROR: " mess)); \
MP4D_close(mp4); \
return 0; \
}
#define ERROR(mess) \
if (!depth) \
break; \
else \
RETURN_ERROR(mess);
typedef enum { BOX_ATOM, BOX_OD } boxtype_t;
int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size)
{
int depth = 0;
struct
{
boxsize_t bytes;
boxtype_t format;
} stack[MAX_CHUNKS_DEPTH];
#if MP4D_TRACE_SUPPORTED
uint32_t box_path[MAX_CHUNKS_DEPTH];
#endif
int eof_flag = 0;
unsigned i;
MP4D_track_t *tr = NULL;
if (!mp4 || !read_callback)
{
TRACE(("\nERROR: invlaid arguments!"));
return 0;
}
memset(mp4, 0, sizeof(MP4D_demux_t));
mp4->read_callback = read_callback;
mp4->token = token;
mp4->read_size = file_size;
stack[0].format = BOX_ATOM; stack[0].bytes = 0;
do
{
static const struct
{
uint32_t name;
unsigned max_version;
unsigned use_track_flag;
} g_fullbox[] =
{
#if MP4D_INFO_SUPPORTED
{BOX_mdhd, 1, 1},
{BOX_mvhd, 1, 0},
{BOX_hdlr, 0, 0},
{BOX_meta, 0, 0}, #endif
#if MP4D_TRACE_TIMESTAMPS
{BOX_stts, 0, 0},
{BOX_ctts, 0, 0},
#endif
{BOX_stz2, 0, 1},
{BOX_stsz, 0, 1},
{BOX_stsc, 0, 1},
{BOX_stco, 0, 1},
{BOX_co64, 0, 1},
{BOX_stsd, 0, 0},
{BOX_esds, 0, 1} };
static const struct
{
uint32_t name;
boxtype_t type;
} g_envelope_box[] =
{
{BOX_esds, BOX_OD}, {OD_ESD, BOX_OD},
{OD_DCD, BOX_OD},
{OD_DSI, BOX_OD},
{BOX_trak, BOX_ATOM},
{BOX_moov, BOX_ATOM},
{BOX_mdia, BOX_ATOM},
{BOX_tref, BOX_ATOM},
{BOX_minf, BOX_ATOM},
{BOX_dinf, BOX_ATOM},
{BOX_stbl, BOX_ATOM},
{BOX_stsd, BOX_ATOM},
{BOX_mp4a, BOX_ATOM},
{BOX_mp4s, BOX_ATOM},
#if MP4D_AVC_SUPPORTED
{BOX_mp4v, BOX_ATOM},
{BOX_avc1, BOX_ATOM},
#endif
#if MP4D_HEVC_SUPPORTED
{BOX_hvc1, BOX_ATOM},
#endif
{BOX_udta, BOX_ATOM},
{BOX_meta, BOX_ATOM},
{BOX_ilst, BOX_ATOM}
};
uint32_t FullAtomVersionAndFlags = 0;
boxsize_t payload_bytes;
boxsize_t box_bytes;
uint32_t box_name;
#if MP4D_INFO_SUPPORTED
unsigned char **ptag = NULL;
#endif
int read_bytes = 0;
if (stack[depth].format == BOX_ATOM)
{
box_bytes = minimp4_read(mp4, 4, &eof_flag);
#if FIX_BAD_ANDROID_META_BOX
broken_android_meta_hack:
#endif
if (eof_flag)
break;
if (box_bytes >= 2 && box_bytes < 8)
{
ERROR("invalid box size (broken file?)");
}
box_name = minimp4_read(mp4, 4, &eof_flag);
read_bytes = 8;
if (box_bytes == 0 || box_bytes == (boxsize_t)0xFFFFFFFFU )
{
box_bytes = ~(boxsize_t)0;
}
payload_bytes = box_bytes - 8;
if (box_bytes == 1) {
TRACE(("\n64-bit chunk encountered"));
box_bytes = minimp4_read(mp4, 4, &eof_flag);
#if MP4D_64BIT_SUPPORTED
box_bytes <<= 32;
box_bytes |= minimp4_read(mp4, 4, &eof_flag);
#else
if (box_bytes)
{
ERROR("UNSUPPORTED FEATURE: MP4BoxHeader(): 64-bit boxes not supported!");
}
box_bytes = minimp4_read(mp4, 4, &eof_flag);
#endif
if (box_bytes < 16)
{
ERROR("invalid box size (broken file?)");
}
payload_bytes = box_bytes - 16;
}
for (i = 0; i < NELEM(g_fullbox); i++)
{
if (box_name == g_fullbox[i].name)
{
FullAtomVersionAndFlags = READ(4);
read_bytes += 4;
#if FIX_BAD_ANDROID_META_BOX
if (box_name == BOX_meta)
{
if (FullAtomVersionAndFlags >= 8 && FullAtomVersionAndFlags < payload_bytes)
{
if (box_bytes > stack[depth].bytes)
{
ERROR("broken file structure!");
}
stack[depth].bytes -= box_bytes;;
depth++;
stack[depth].bytes = payload_bytes + 4; stack[depth].format = BOX_ATOM;
box_bytes = FullAtomVersionAndFlags;
TRACE(("Bad metadata box detected (Android bug?)!\n"));
goto broken_android_meta_hack;
}
}
#endif
if ((FullAtomVersionAndFlags >> 24) > g_fullbox[i].max_version)
{
ERROR("unsupported box version!");
}
if (g_fullbox[i].use_track_flag && !tr)
{
ERROR("broken file structure!");
}
}
}
} else {
int val;
box_name = OD_BASE + minimp4_read(mp4, 1, &eof_flag); read_bytes += 1;
if (eof_flag)
break;
payload_bytes = 0;
box_bytes = 1;
do
{
val = minimp4_read(mp4, 1, &eof_flag);
read_bytes += 1;
if (eof_flag)
{
ERROR("premature EOF!");
}
payload_bytes = (payload_bytes << 7) | (val & 0x7F);
box_bytes++;
} while (val & 0x80);
box_bytes += payload_bytes;
}
#if MP4D_TRACE_SUPPORTED
box_path[depth] = (box_name >> 24) | (box_name << 24) | ((box_name >> 8) & 0x0000FF00) | ((box_name << 8) & 0x00FF0000);
TRACE(("%2d %8d %.*s (%d bytes remains for sibilings) \n", depth, (int)box_bytes, depth*4, (char*)box_path, (int)stack[depth].bytes));
#endif
if (depth)
{
assert(box_bytes > 0);
if (box_bytes > stack[depth].bytes)
{
TRACE(("Wrong %c%c%c%c box size: broken file?\n", (box_name >> 24)&255, (box_name >> 16)&255, (box_name >> 8)&255, box_name&255));
box_bytes = stack[depth].bytes;
box_name = 0;
payload_bytes = box_bytes - read_bytes;
}
stack[depth].bytes -= box_bytes;
}
switch(box_name)
{
case BOX_stz2: case BOX_stsz:
{
int size = 0;
uint32_t sample_size = READ(4);
tr->sample_count = READ(4);
MALLOC(unsigned int*, tr->entry_size, tr->sample_count*4);
for (i = 0; i < tr->sample_count; i++)
{
if (box_name == BOX_stsz)
{
tr->entry_size[i] = (sample_size?sample_size:READ(4));
} else
{
switch (sample_size & 0xFF)
{
case 16:
tr->entry_size[i] = READ(2);
break;
case 8:
tr->entry_size[i] = READ(1);
break;
case 4:
if (i & 1)
{
tr->entry_size[i] = size & 15;
} else
{
size = READ(1);
tr->entry_size[i] = (size >> 4);
}
break;
}
}
}
}
break;
case BOX_stsc: tr->sample_to_chunk_count = READ(4);
MALLOC(MP4D_sample_to_chunk_t*, tr->sample_to_chunk, tr->sample_to_chunk_count*sizeof(tr->sample_to_chunk[0]));
for (i = 0; i < tr->sample_to_chunk_count; i++)
{
tr->sample_to_chunk[i].first_chunk = READ(4);
tr->sample_to_chunk[i].samples_per_chunk = READ(4);
SKIP(4); }
break;
#if MP4D_TRACE_TIMESTAMPS || MP4D_TIMESTAMPS_SUPPORTED
case BOX_stts:
{
unsigned count = READ(4);
unsigned j, k = 0, ts = 0, ts_count = count;
#if MP4D_TIMESTAMPS_SUPPORTED
MALLOC(unsigned int*, tr->timestamp, ts_count*4);
MALLOC(unsigned int*, tr->duration, ts_count*4);
#endif
for (i = 0; i < count; i++)
{
unsigned sc = READ(4);
int d = READ(4);
TRACE(("sample %8d count %8d duration %8d\n", i, sc, d));
#if MP4D_TIMESTAMPS_SUPPORTED
if (k + sc > ts_count)
{
ts_count = k + sc;
tr->timestamp = (unsigned int*)realloc(tr->timestamp, ts_count * sizeof(unsigned));
tr->duration = (unsigned int*)realloc(tr->duration, ts_count * sizeof(unsigned));
}
for (j = 0; j < sc; j++)
{
tr->duration[k] = d;
tr->timestamp[k++] = ts;
ts += d;
}
#endif
}
}
break;
case BOX_ctts:
{
unsigned count = READ(4);
for (i = 0; i < count; i++)
{
int sc = READ(4);
int d = READ(4);
(void)sc;
(void)d;
TRACE(("sample %8d count %8d decoding to composition offset %8d\n", i, sc, d));
}
}
break;
#endif
case BOX_stco: case BOX_co64:
tr->chunk_count = READ(4);
MALLOC(MP4D_file_offset_t*, tr->chunk_offset, tr->chunk_count*sizeof(MP4D_file_offset_t));
for (i = 0; i < tr->chunk_count; i++)
{
tr->chunk_offset[i] = READ(4);
if (box_name == BOX_co64)
{
#if !MP4D_64BIT_SUPPORTED
if (tr->chunk_offset[i])
{
ERROR("UNSUPPORTED FEATURE: 64-bit chunk_offset not supported!");
}
#endif
tr->chunk_offset[i] <<= 32;
tr->chunk_offset[i] |= READ(4);
}
}
break;
#if MP4D_INFO_SUPPORTED
case BOX_mvhd:
SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4);
mp4->timescale = READ(4);
mp4->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0;
mp4->duration_lo = READ(4);
SKIP(4 + 2 + 2 + 4*2 + 4*9 + 4*6 + 4);
break;
case BOX_mdhd:
SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4);
tr->timescale = READ(4);
tr->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0;
tr->duration_lo = READ(4);
{
int ISO_639_2_T = READ(2);
tr->language[2] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5;
tr->language[1] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5;
tr->language[0] = (ISO_639_2_T & 31) + 0x60;
}
break;
case BOX_hdlr:
if (tr) {
SKIP(4); tr->handler_type = READ(4);
}
break;
case BOX_btrt:
if (!tr)
{
ERROR("broken file structure!");
}
SKIP(4 + 4);
tr->avg_bitrate_bps = READ(4);
break;
case BOX_calb: ptag = &mp4->tag.album; break;
case BOX_cART: ptag = &mp4->tag.artist; break;
case BOX_cnam: ptag = &mp4->tag.title; break;
case BOX_cday: ptag = &mp4->tag.year; break;
case BOX_ccmt: ptag = &mp4->tag.comment; break;
case BOX_cgen: ptag = &mp4->tag.genre; break;
#endif
case BOX_stsd:
SKIP(4); break;
case BOX_mp4s: if (!tr)
{
ERROR("broken file structure!");
}
SKIP(6*1 + 2);
break;
case BOX_mp4a:
if (!tr)
{
ERROR("broken file structure!");
}
#if MP4D_INFO_SUPPORTED
SKIP(6*1+2 + 4*2);
tr->SampleDescription.audio.channelcount = READ(2);
SKIP(2 + 2 + 2);
tr->SampleDescription.audio.samplerate_hz = READ(4) >> 16;
#else
SKIP(28);
#endif
break;
#if MP4D_AVC_SUPPORTED
case BOX_avc1: case BOX_mp4v:
if (!tr)
{
ERROR("broken file structure!");
}
#if MP4D_INFO_SUPPORTED
SKIP(6*1 + 2 + 2 + 2 + 4*3);
tr->SampleDescription.video.width = READ(2);
tr->SampleDescription.video.height = READ(2);
SKIP(4 + 4 + 4 + 2 + 32 + 2 + 2);
#else
SKIP(78);
#endif
break;
case BOX_avcC: tr->object_type_indication = MP4_OBJECT_TYPE_AVC;
tr->dsi = (unsigned char*)malloc((size_t)box_bytes);
tr->dsi_bytes = (unsigned)box_bytes;
{
int spspps;
unsigned char *p = tr->dsi;
unsigned int configurationVersion = READ(1);
unsigned int AVCProfileIndication = READ(1);
unsigned int profile_compatibility = READ(1);
unsigned int AVCLevelIndication = READ(1);
unsigned int lengthSizeMinusOne = READ(1) & 3;
(void)configurationVersion;
(void)AVCProfileIndication;
(void)profile_compatibility;
(void)AVCLevelIndication;
(void)lengthSizeMinusOne;
for (spspps = 0; spspps < 2; spspps++)
{
unsigned int numOfSequenceParameterSets= READ(1);
if (!spspps)
{
numOfSequenceParameterSets &= 31; }
*p++ = numOfSequenceParameterSets;
for (i = 0; i < numOfSequenceParameterSets; i++)
{
unsigned k, sequenceParameterSetLength = READ(2);
*p++ = sequenceParameterSetLength >> 8;
*p++ = sequenceParameterSetLength ;
for (k = 0; k < sequenceParameterSetLength; k++)
{
*p++ = READ(1);
}
}
}
}
break;
#endif
case OD_ESD:
{
unsigned flags = READ(3);
if (flags & 0x80) {
SKIP(2); }
if (flags & 0x40) {
unsigned bytecount = READ(1);
SKIP(bytecount); }
if (flags & 0x20) {
SKIP(2); }
break;
}
case OD_DCD: assert(tr); tr->object_type_indication = READ(1);
#if MP4D_INFO_SUPPORTED
tr->stream_type = READ(1) >> 2;
SKIP(3 + 4);
tr->avg_bitrate_bps = READ(4);
#else
SKIP(1+3+4+4);
#endif
break;
case OD_DSI: assert(tr); if (!tr->dsi && payload_bytes)
{
MALLOC(unsigned char*, tr->dsi, (int)payload_bytes);
for (i = 0; i < payload_bytes; i++)
{
tr->dsi[i] = minimp4_read(mp4, 1, &eof_flag); }
tr->dsi_bytes = i;
payload_bytes -= i;
break;
}
default:
TRACE(("[%c%c%c%c] %d\n", box_name >> 24, box_name >> 16, box_name >> 8, box_name, (int)payload_bytes));
}
#if MP4D_INFO_SUPPORTED
if (ptag && !*ptag && payload_bytes > 16)
{
#if 0#else
SKIP(4 + 4 + 4 + 4);
#endif
MALLOC(unsigned char*, *ptag, (unsigned)payload_bytes + 1);
for (i = 0; payload_bytes != 0; i++)
{
(*ptag)[i] = READ(1);
}
(*ptag)[i] = 0; }
#endif
if (box_name == BOX_trak)
{
void *mem = realloc(mp4->track, (mp4->track_count + 1)*sizeof(MP4D_track_t));
if (!mem)
{
ERROR("out of memory");
}
mp4->track = (MP4D_track_t*)mem;
tr = mp4->track + mp4->track_count++;
memset(tr, 0, sizeof(MP4D_track_t));
} else if (box_name == BOX_meta)
{
tr = NULL; }
for (i = 0; i < NELEM(g_envelope_box); i++)
{
if (box_name == g_envelope_box[i].name)
{
if (++depth >= MAX_CHUNKS_DEPTH)
{
ERROR("too deep atoms nesting!");
}
stack[depth].bytes = payload_bytes;
stack[depth].format = g_envelope_box[i].type;
break;
}
}
if (i == NELEM(g_envelope_box))
{
if (payload_bytes > file_size)
{
eof_flag = 1;
} else
{
SKIP(payload_bytes);
}
}
while (depth > 0 && !stack[depth].bytes)
{
depth--;
}
} while(!eof_flag);
if (!mp4->track_count)
{
RETURN_ERROR("no tracks found");
}
return 1;
}
static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_sample_in_chunk)
{
unsigned chunk_group = 0, nc;
unsigned sum = 0;
*nfirst_sample_in_chunk = 0;
if (tr->chunk_count <= 1)
{
return 0;
}
for (nc = 0; nc < tr->chunk_count; nc++)
{
if (chunk_group + 1 < tr->sample_to_chunk_count && nc + 1 == tr->sample_to_chunk[chunk_group + 1].first_chunk) {
chunk_group++;
}
sum += tr->sample_to_chunk[chunk_group].samples_per_chunk;
if (nsample < sum)
return nc;
*nfirst_sample_in_chunk = sum;
}
return -1;
}
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;
int nchunk = sample_to_chunk(tr, nsample, &ns);
MP4D_file_offset_t offset;
if (nchunk < 0)
{
*frame_bytes = 0;
return 0;
}
offset = tr->chunk_offset[nchunk];
for (; ns < nsample; ns++)
{
offset += tr->entry_size[ns];
}
*frame_bytes = tr->entry_size[ns];
if (timestamp)
{
#if MP4D_TIMESTAMPS_SUPPORTED
*timestamp = tr->timestamp[ns];
#else
*timestamp = 0;
#endif
}
if (duration)
{
#if MP4D_TIMESTAMPS_SUPPORTED
*duration = tr->duration[ns];
#else
*duration = 0;
#endif
}
return offset;
}
#define FREE(x) if (x) {free(x); x = NULL;}
void MP4D_close(MP4D_demux_t *mp4)
{
while (mp4->track_count)
{
MP4D_track_t *tr = mp4->track + --mp4->track_count;
FREE(tr->entry_size);
#if MP4D_TIMESTAMPS_SUPPORTED
FREE(tr->timestamp);
FREE(tr->duration);
#endif
FREE(tr->sample_to_chunk);
FREE(tr->chunk_offset);
FREE(tr->dsi);
}
FREE(mp4->track);
#if MP4D_INFO_SUPPORTED
FREE(mp4->tag.title);
FREE(mp4->tag.artist);
FREE(mp4->tag.album);
FREE(mp4->tag.year);
FREE(mp4->tag.comment);
FREE(mp4->tag.genre);
#endif
}
static int skip_spspps(const unsigned char *p, int nbytes, int nskip)
{
int i, k = 0;
for (i = 0; i < nskip; i++)
{
unsigned segmbytes;
if (k > nbytes - 2)
return -1;
segmbytes = p[k]*256 + p[k+1];
k += 2 + segmbytes;
}
return k;
}
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;
unsigned char *p = mp4->track[ntrack].dsi;
if (ntrack >= mp4->track_count)
return NULL;
if (mp4->track[ntrack].object_type_indication != MP4_OBJECT_TYPE_AVC)
return NULL;
if (pps_flag)
{
sps_count = p[bytepos++];
skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, sps_count);
if (skip_bytes < 0)
return NULL;
bytepos += skip_bytes;
}
sps_count = p[bytepos++];
if (nsps >= sps_count)
return NULL;
skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, nsps);
if (skip_bytes < 0)
return NULL;
bytepos += skip_bytes;
*sps_bytes = p[bytepos]*256 + p[bytepos+1];
return p + bytepos + 2;
}
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);
}
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);
}
#if MP4D_PRINT_INFO_SUPPORTED
static const char *GetMP4StreamTypeName(int streamType)
{
switch (streamType)
{
case 0x00: return "Forbidden";
case 0x01: return "ObjectDescriptorStream";
case 0x02: return "ClockReferenceStream";
case 0x03: return "SceneDescriptionStream";
case 0x04: return "VisualStream";
case 0x05: return "AudioStream";
case 0x06: return "MPEG7Stream";
case 0x07: return "IPMPStream";
case 0x08: return "ObjectContentInfoStream";
case 0x09: return "MPEGJStream";
default:
if (streamType >= 0x20 && streamType <= 0x3F)
{
return "User private";
} else
{
return "Reserved for ISO use";
}
}
}
static const char *GetMP4ObjectTypeName(int objectTypeIndication)
{
switch (objectTypeIndication)
{
case 0x00: return "Forbidden";
case 0x01: return "Systems ISO/IEC 14496-1";
case 0x02: return "Systems ISO/IEC 14496-1";
case 0x20: return "Visual ISO/IEC 14496-2";
case 0x40: return "Audio ISO/IEC 14496-3";
case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile";
case 0x61: return "Visual ISO/IEC 13818-2 Main Profile";
case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile";
case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile";
case 0x64: return "Visual ISO/IEC 13818-2 High Profile";
case 0x65: return "Visual ISO/IEC 13818-2 422 Profile";
case 0x66: return "Audio ISO/IEC 13818-7 Main Profile";
case 0x67: return "Audio ISO/IEC 13818-7 LC Profile";
case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile";
case 0x69: return "Audio ISO/IEC 13818-3";
case 0x6A: return "Visual ISO/IEC 11172-2";
case 0x6B: return "Audio ISO/IEC 11172-3";
case 0x6C: return "Visual ISO/IEC 10918-1";
case 0xFF: return "no object type specified";
default:
if (objectTypeIndication >= 0xC0 && objectTypeIndication <= 0xFE)
return "User private";
else
return "Reserved for ISO use";
}
}
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);
#define STR_TAG(name) if (mp4->tag.name) printf("%10s = %s\n", #name, mp4->tag.name)
STR_TAG(title);
STR_TAG(artist);
STR_TAG(album);
STR_TAG(year);
STR_TAG(comment);
STR_TAG(genre);
printf("\nNo|type|lng| duration | bitrate| %-23s| Object type", "Stream type");
for (i = 0; i < mp4->track_count; i++)
{
MP4D_track_t *tr = mp4->track + i;
printf("\n%2d|%c%c%c%c|%c%c%c|%7.2f s %6d frm| %7d|", i,
(tr->handler_type >> 24), (tr->handler_type >> 16), (tr->handler_type >> 8), (tr->handler_type >> 0),
tr->language[0], tr->language[1], tr->language[2],
(65536.0*65536.0*tr->duration_hi + tr->duration_lo) / tr->timescale,
tr->sample_count,
tr->avg_bitrate_bps);
printf(" %-23s|", GetMP4StreamTypeName(tr->stream_type));
printf(" %-23s", GetMP4ObjectTypeName(tr->object_type_indication));
if (tr->handler_type == MP4D_HANDLER_TYPE_SOUN)
{
printf(" - %d ch %d hz", tr->SampleDescription.audio.channelcount, tr->SampleDescription.audio.samplerate_hz);
} else if (tr->handler_type == MP4D_HANDLER_TYPE_VIDE)
{
printf(" - %dx%d", tr->SampleDescription.video.width, tr->SampleDescription.video.height);
}
}
printf("\n");
}
#endif #endif