From ef9e0d1c9ef51ce6e272be14286596f79dc6f943 Mon Sep 17 00:00:00 2001 From: Anton Mitrofanov Date: Tue, 6 Dec 2011 22:11:54 +0400 Subject: [PATCH] Use L-SMASH for mp4 muxing --- Makefile | 7 +- configure | 31 +- output/mp4.c | 509 ++-- output/mp4/box.h | 2116 ++++++++++++ output/mp4/importer.c | 5449 +++++++++++++++++++++++++++++++ output/mp4/importer.h | 48 + output/mp4/internal.h | 37 + output/mp4/isom.c | 8513 +++++++++++++++++++++++++++++++++++++++++++++++++ output/mp4/isom.h | 35 + output/mp4/lsmash.h | 1328 ++++++++ output/mp4/mp4a.c | 901 ++++++ output/mp4/mp4a.h | 130 + output/mp4/mp4sys.c | 977 ++++++ output/mp4/mp4sys.h | 205 ++ output/mp4/summary.c | 121 + output/mp4/utils.c | 792 +++++ output/mp4/utils.h | 189 ++ output/mp4/write.c | 1724 ++++++++++ output/mp4/write.h | 39 + x264.c | 20 +- 20 files changed, 22912 insertions(+), 259 deletions(-) create mode 100644 output/mp4/box.h create mode 100644 output/mp4/importer.c create mode 100644 output/mp4/importer.h create mode 100644 output/mp4/internal.h create mode 100644 output/mp4/isom.c create mode 100644 output/mp4/isom.h create mode 100644 output/mp4/lsmash.h create mode 100644 output/mp4/mp4a.c create mode 100644 output/mp4/mp4a.h create mode 100644 output/mp4/mp4sys.c create mode 100644 output/mp4/mp4sys.h create mode 100644 output/mp4/summary.c create mode 100644 output/mp4/utils.c create mode 100644 output/mp4/utils.h create mode 100644 output/mp4/write.c create mode 100644 output/mp4/write.h diff --git a/Makefile b/Makefile index c9505d3..190bc37 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,9 @@ SRCCLI = x264.c input/input.c input/timecode.c input/raw.c input/y4m.c \ filters/video/resize.c filters/video/cache.c filters/video/fix_vfr_pts.c \ filters/video/select_every.c filters/video/crop.c filters/video/depth.c +SRCCLI += output/mp4.c +SRCCLI += $(addprefix output/mp4/, isom.c utils.c mp4sys.c mp4a.c summary.c importer.c write.c) + SRCSO = OBJCHK = tools/checkasm.o @@ -53,10 +56,6 @@ ifneq ($(findstring HAVE_FFMS 1, $(CONFIG)),) SRCCLI += input/ffms.c endif -ifneq ($(findstring HAVE_GPAC 1, $(CONFIG)),) -SRCCLI += output/mp4.c -endif - # Visualization sources ifneq ($(findstring HAVE_VISUALIZE 1, $(CONFIG)),) SRCS += common/visualize.c common/display-x11.c diff --git a/configure b/configure index 4e474ec..c7ffdcc 100755 --- a/configure +++ b/configure @@ -49,7 +49,6 @@ External library support: --disable-swscale disable swscale support --disable-lavf disable libavformat support --disable-ffms disable ffmpegsource support - --disable-gpac disable gpac support EOF exit 1 @@ -104,7 +103,6 @@ icl_ldflags() { arg=${arg/pthreadGC/pthreadVC} [ "$arg" = avifil32.lib ] && arg=vfw32.lib - [ "$arg" = gpac_static.lib ] && arg=libgpac_static.lib [ -n "$arg" ] && echo -n "$arg " done @@ -226,7 +224,6 @@ static="no" avs="auto" lavf="auto" ffms="auto" -gpac="auto" gpl="yes" thread="auto" swscale="auto" @@ -251,7 +248,7 @@ cross_prefix="" EXE="" # list of all preprocessor HAVE values we can define -CONFIG_HAVE="MALLOC_H ALTIVEC ALTIVEC_H MMX ARMV6 ARMV6T2 NEON BEOSTHREAD POSIXTHREAD WIN32THREAD THREAD LOG2F VISUALIZE SWSCALE LAVF FFMS GPAC GF_MALLOC AVS GPL VECTOREXT INTERLACED CPU_COUNT" +CONFIG_HAVE="MALLOC_H ALTIVEC ALTIVEC_H MMX ARMV6 ARMV6T2 NEON BEOSTHREAD POSIXTHREAD WIN32THREAD THREAD LOG2F VISUALIZE SWSCALE LAVF FFMS AVS GPL VECTOREXT INTERLACED CPU_COUNT" # parse options @@ -300,9 +297,6 @@ for opt do --disable-ffms) ffms="no" ;; - --disable-gpac) - gpac="no" - ;; --disable-gpl) gpl="no" ;; @@ -838,28 +832,6 @@ if [ "$swscale" = "yes" ]; then fi fi -if [ "$gpac" = "auto" ] ; then - gpac="no" - cc_check "" -lz && GPAC_LIBS="-lgpac_static -lz" || GPAC_LIBS="-lgpac_static" - if [ "$SYS" = "WINDOWS" ] ; then - GPAC_LIBS="$GPAC_LIBS -lwinmm" - fi - if cc_check gpac/isomedia.h "$GPAC_LIBS" ; then - if cc_check gpac/isomedia.h "$GPAC_LIBS" "gf_isom_set_pixel_aspect_ratio(0,0,0,0,0);" ; then - gpac="yes" - else - echo "Warning: gpac is too old, update to 2007-06-21 UTC or later" - fi - fi -fi -if [ "$gpac" = "yes" ] ; then - define HAVE_GPAC - if cc_check gpac/isomedia.h "-Werror $GPAC_LIBS" "gf_malloc(1); gf_free(NULL);" ; then - define HAVE_GF_MALLOC - fi - LDFLAGSCLI="$GPAC_LIBS $LDFLAGSCLI" -fi - if [ "$avs" = "auto" ] ; then avs="no" # cygwin can use avisynth if it can use LoadLibrary @@ -1121,7 +1093,6 @@ interlaced: $interlaced avs: $avs lavf: $lavf ffms: $ffms -gpac: $gpac gpl: $gpl thread: $thread filters: $filters diff --git a/output/mp4.c b/output/mp4.c index c0fb685..d811ae0 100644 --- a/output/mp4.c +++ b/output/mp4.c @@ -1,10 +1,13 @@ /***************************************************************************** - * mp4.c: mp4 muxer + * mp4.c: mp4 muxer using L-SMASH ***************************************************************************** * Copyright (C) 2003-2011 x264 project * * Authors: Laurent Aimar * Loren Merritt + * Yusuke Nakamura + * Takashi Hirata + * golgol7777 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,88 +28,84 @@ *****************************************************************************/ #include "output.h" -#include +#include "mp4/lsmash.h" +#include "mp4/importer.h" + +/*******************/ + +#define MP4_LOG_ERROR( ... ) x264_cli_log( "mp4", X264_LOG_ERROR, __VA_ARGS__ ) +#define MP4_LOG_WARNING( ... ) x264_cli_log( "mp4", X264_LOG_WARNING, __VA_ARGS__ ) +#define MP4_LOG_INFO( ... ) x264_cli_log( "mp4", X264_LOG_INFO, __VA_ARGS__ ) +//#define MP4_RETURN_IF_ERR( cond, ret, ... ) RETURN_IF_ERR( cond, "mp4", ret, __VA_ARGS__ ) +#define MP4_FAIL_IF_ERR( cond, ... ) FAIL_IF_ERR( cond, "mp4", __VA_ARGS__ ) + +/* For close_file() */ +#define MP4_LOG_IF_ERR( cond, ... )\ +if( cond )\ +{\ + MP4_LOG_ERROR( __VA_ARGS__ );\ +} + +/* For open_file() */ +#define MP4_FAIL_IF_ERR_EX( cond, ... )\ +if( cond )\ +{\ + remove_mp4_hnd( p_mp4 );\ + MP4_LOG_ERROR( __VA_ARGS__ );\ + return -1;\ +} -#if HAVE_GF_MALLOC -#undef malloc -#undef free -#undef realloc -#define malloc gf_malloc -#define free gf_free -#define realloc gf_realloc -#endif +/*******************/ typedef struct { - GF_ISOFile *p_file; - GF_AVCConfig *p_config; - GF_ISOSample *p_sample; - int i_track; - uint32_t i_descidx; - uint64_t i_time_res; - int64_t i_time_inc; - int64_t i_delay_time; - int64_t i_init_delta; + lsmash_root_t *p_root; + lsmash_brand_type major_brand; + int i_brand_3gpp; + int b_brand_qt; + int b_stdout; + uint32_t i_movie_timescale; + uint32_t i_video_timescale; + uint32_t i_track; + uint32_t i_sample_entry; + uint64_t i_time_inc; + int64_t i_start_offset; + uint64_t i_first_cts; + uint64_t i_prev_dts; + uint32_t i_sei_size; + uint8_t *p_sei_buffer; int i_numframe; + int64_t i_init_delta; int i_delay_frames; int b_dts_compress; int i_dts_compress_multiplier; - int i_data_size; + int b_use_recovery; + int b_no_pasp; + int b_fragments; } mp4_hnd_t; -static void recompute_bitrate_mp4( GF_ISOFile *p_file, int i_track ) -{ - u32 count, di, timescale, time_wnd, rate; - u64 offset; - Double br; - GF_ESD *esd; +/*******************/ - esd = gf_isom_get_esd( p_file, i_track, 1 ); - if( !esd ) +static void remove_mp4_hnd( hnd_t handle ) +{ + mp4_hnd_t *p_mp4 = handle; + if( !p_mp4 ) return; - - esd->decoderConfig->avgBitrate = 0; - esd->decoderConfig->maxBitrate = 0; - rate = time_wnd = 0; - - timescale = gf_isom_get_media_timescale( p_file, i_track ); - count = gf_isom_get_sample_count( p_file, i_track ); - for( u32 i = 0; i < count; i++ ) + if( p_mp4->p_sei_buffer ) { - GF_ISOSample *samp = gf_isom_get_sample_info( p_file, i_track, i+1, &di, &offset ); - if( !samp ) - { - x264_cli_log( "mp4", X264_LOG_ERROR, "failure reading back frame %u\n", i ); - break; - } - - if( esd->decoderConfig->bufferSizeDB < samp->dataLength ) - esd->decoderConfig->bufferSizeDB = samp->dataLength; - - esd->decoderConfig->avgBitrate += samp->dataLength; - rate += samp->dataLength; - if( samp->DTS > time_wnd + timescale ) - { - if( rate > esd->decoderConfig->maxBitrate ) - esd->decoderConfig->maxBitrate = rate; - time_wnd = samp->DTS; - rate = 0; - } - - gf_isom_sample_del( &samp ); + free( p_mp4->p_sei_buffer ); + p_mp4->p_sei_buffer = NULL; } - - br = (Double)(s64)gf_isom_get_media_duration( p_file, i_track ); - br /= timescale; - esd->decoderConfig->avgBitrate = (u32)(esd->decoderConfig->avgBitrate / br); - /*move to bps*/ - esd->decoderConfig->avgBitrate *= 8; - esd->decoderConfig->maxBitrate *= 8; - - gf_isom_change_mpeg4_description( p_file, i_track, 1, esd ); - gf_odf_desc_del( (GF_Descriptor*)esd ); + if( p_mp4->p_root ) + { + lsmash_destroy_root( p_mp4->p_root ); + p_mp4->p_root = NULL; + } + free( p_mp4 ); } +/*******************/ + static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts ) { mp4_hnd_t *p_mp4 = handle; @@ -114,56 +113,47 @@ static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest if( !p_mp4 ) return 0; - if( p_mp4->p_config ) - gf_odf_avc_cfg_del( p_mp4->p_config ); - - if( p_mp4->p_sample ) - { - if( p_mp4->p_sample->data ) - free( p_mp4->p_sample->data ); - - p_mp4->p_sample->dataLength = 0; - gf_isom_sample_del( &p_mp4->p_sample ); - } - - if( p_mp4->p_file ) + if( p_mp4->p_root ) { + double actual_duration = 0; /* FIXME: This may be inside block of "if( p_mp4->i_track )" if audio does not use this. */ if( p_mp4->i_track ) { - /* The mdhd duration is defined as CTS[final] - CTS[0] + duration of last frame. - * The mdhd duration (in seconds) should be able to be longer than the tkhd duration since the track is managed by edts. - * So, if mdhd duration is equal to the last DTS or less, we give the last composition time delta to the last sample duration. - * And then, the mdhd duration is updated, but it time-wise doesn't give the actual duration. - * The tkhd duration is the actual track duration. */ - uint64_t mdhd_duration = (2 * largest_pts - second_largest_pts) * p_mp4->i_time_inc; - if( mdhd_duration != gf_isom_get_media_duration( p_mp4->p_file, p_mp4->i_track ) ) - { - uint64_t last_dts = gf_isom_get_sample_dts( p_mp4->p_file, p_mp4->i_track, p_mp4->i_numframe ); - uint32_t last_duration = (uint32_t)( mdhd_duration > last_dts ? mdhd_duration - last_dts : (largest_pts - second_largest_pts) * p_mp4->i_time_inc ); - gf_isom_set_last_sample_duration( p_mp4->p_file, p_mp4->i_track, last_duration ); - } + /* Flush the rest of samples and add the last sample_delta. */ + uint32_t last_delta = largest_pts - second_largest_pts; + MP4_LOG_IF_ERR( lsmash_flush_pooled_samples( p_mp4->p_root, p_mp4->i_track, (last_delta ? last_delta : 1) * p_mp4->i_time_inc ), + "failed to flush the rest of samples.\n" ); + + if( p_mp4->i_movie_timescale != 0 && p_mp4->i_video_timescale != 0 ) /* avoid zero division */ + actual_duration = ((double)((largest_pts + last_delta) * p_mp4->i_time_inc) / p_mp4->i_video_timescale) * p_mp4->i_movie_timescale; + else + MP4_LOG_ERROR( "timescale is broken.\n" ); - /* Write an Edit Box if the first CTS offset is positive. - * A media_time is given by not the mvhd timescale but rather the mdhd timescale. - * The reason is that an Edit Box maps the presentation time-line to the media time-line. - * Any demuxers should follow the Edit Box if it exists. */ - GF_ISOSample *sample = gf_isom_get_sample_info( p_mp4->p_file, p_mp4->i_track, 1, NULL, NULL ); - if( sample && sample->CTS_Offset > 0 ) + if( !p_mp4->b_fragments ) { - uint32_t mvhd_timescale = gf_isom_get_timescale( p_mp4->p_file ); - uint64_t tkhd_duration = (uint64_t)( mdhd_duration * ( (double)mvhd_timescale / p_mp4->i_time_res ) ); - gf_isom_append_edit_segment( p_mp4->p_file, p_mp4->i_track, tkhd_duration, sample->CTS_Offset, GF_ISOM_EDIT_NORMAL ); + /* + * Declare the explicit time-line mapping. + * A segment_duration is given by movie timescale, while a media_time that is the start time of this segment + * is given by not the movie timescale but rather the media timescale. + * The reason is that ISO media have two time-lines, presentation and media time-line, + * and an edit maps the presentation time-line to the media time-line. + * According to QuickTime file format specification and the actual playback in QuickTime Player, + * if the Edit Box doesn't exist in the track, the ratio of the summation of sample durations and track's duration becomes + * the track's media_rate so that the entire media can be used by the track. + * So, we add Edit Box here to avoid this implicit media_rate could distort track's presentation timestamps slightly. + * Note: Any demuxers should follow the Edit List Box if it exists. + */ + MP4_LOG_IF_ERR( lsmash_create_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, actual_duration, p_mp4->i_first_cts, ISOM_EDIT_MODE_NORMAL ), + "failed to set timeline map for video.\n" ); } - gf_isom_sample_del( &sample ); - - recompute_bitrate_mp4( p_mp4->p_file, p_mp4->i_track ); + else if( !p_mp4->b_stdout ) + MP4_LOG_IF_ERR( lsmash_modify_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, 1, actual_duration, p_mp4->i_first_cts, ISOM_EDIT_MODE_NORMAL ), + "failed to update timeline map for video.\n" ); } - gf_isom_set_pl_indication( p_mp4->p_file, GF_ISOM_PL_VISUAL, 0x15 ); - gf_isom_set_storage_mode( p_mp4->p_file, GF_ISOM_STORE_FLAT ); - gf_isom_close( p_mp4->p_file ); + + MP4_LOG_IF_ERR( lsmash_finish_movie( p_mp4->p_root, NULL ), "failed to finish movie.\n" ); } - free( p_mp4 ); + remove_mp4_hnd( p_mp4 ); /* including lsmash_destroy_root( p_mp4->p_root ); */ return 0; } @@ -173,27 +163,48 @@ static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt mp4_hnd_t *p_mp4; *p_handle = NULL; - FILE *fh = fopen( psz_filename, "w" ); - if( !fh ) - return -1; - FAIL_IF_ERR( !x264_is_regular_file( fh ), "mp4", "MP4 output is incompatible with non-regular file `%s'\n", psz_filename ) - fclose( fh ); - if( !(p_mp4 = malloc( sizeof(mp4_hnd_t) )) ) - return -1; + int b_regular = strcmp( psz_filename, "-" ); + b_regular = b_regular && x264_is_regular_file_path( psz_filename ); + if( b_regular ) + { + FILE *fh = fopen( psz_filename, "wb" ); + MP4_FAIL_IF_ERR( !fh, "cannot open output file `%s'.\n", psz_filename ); + b_regular = x264_is_regular_file( fh ); + fclose( fh ); + } + p_mp4 = malloc( sizeof(mp4_hnd_t) ); + MP4_FAIL_IF_ERR( !p_mp4, "failed to allocate memory for muxer information.\n" ); memset( p_mp4, 0, sizeof(mp4_hnd_t) ); - p_mp4->p_file = gf_isom_open( psz_filename, GF_ISOM_OPEN_WRITE, NULL ); p_mp4->b_dts_compress = opt->use_dts_compress; + p_mp4->b_use_recovery = 0; + p_mp4->b_no_pasp = 0; + p_mp4->b_fragments = !b_regular; + p_mp4->b_stdout = !strcmp( psz_filename, "-" ); - if( !(p_mp4->p_sample = gf_isom_sample_new()) ) + char* ext = get_filename_extension( psz_filename ); + if( !strcmp( ext, "mov" ) || !strcmp( ext, "qt" ) ) + { + p_mp4->major_brand = ISOM_BRAND_TYPE_QT; + p_mp4->b_brand_qt = 1; + } + else if( !strcmp( ext, "3gp" ) ) { - close_file( p_mp4, 0, 0 ); - return -1; + p_mp4->major_brand = ISOM_BRAND_TYPE_3GP6; + p_mp4->i_brand_3gpp = 1; } + else if( !strcmp( ext, "3g2" ) ) + { + p_mp4->major_brand = ISOM_BRAND_TYPE_3G2A; + p_mp4->i_brand_3gpp = 2; + } + else + p_mp4->major_brand = ISOM_BRAND_TYPE_MP42; - gf_isom_set_brand_info( p_mp4->p_file, GF_ISOM_BRAND_AVC1, 0 ); + p_mp4->p_root = lsmash_open_movie( psz_filename, p_mp4->b_fragments ? LSMASH_FILE_MODE_WRITE_FRAGMENTED : LSMASH_FILE_MODE_WRITE ); + MP4_FAIL_IF_ERR_EX( !p_mp4->p_root, "failed to create root.\n" ); *p_handle = p_mp4; @@ -203,111 +214,161 @@ static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt static int set_param( hnd_t handle, x264_param_t *p_param ) { mp4_hnd_t *p_mp4 = handle; + uint64_t i_media_timescale; p_mp4->i_delay_frames = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0; p_mp4->i_dts_compress_multiplier = p_mp4->b_dts_compress * p_mp4->i_delay_frames + 1; - p_mp4->i_time_res = p_param->i_timebase_den * p_mp4->i_dts_compress_multiplier; + i_media_timescale = p_param->i_timebase_den * p_mp4->i_dts_compress_multiplier; p_mp4->i_time_inc = p_param->i_timebase_num * p_mp4->i_dts_compress_multiplier; - FAIL_IF_ERR( p_mp4->i_time_res > UINT32_MAX, "mp4", "MP4 media timescale %"PRIu64" exceeds maximum\n", p_mp4->i_time_res ) - - p_mp4->i_track = gf_isom_new_track( p_mp4->p_file, 0, GF_ISOM_MEDIA_VISUAL, - p_mp4->i_time_res ); - - p_mp4->p_config = gf_odf_avc_cfg_new(); - gf_isom_avc_config_new( p_mp4->p_file, p_mp4->i_track, p_mp4->p_config, - NULL, NULL, &p_mp4->i_descidx ); + MP4_FAIL_IF_ERR( i_media_timescale > UINT32_MAX, "MP4 media timescale %"PRIu64" exceeds maximum\n", i_media_timescale ); - gf_isom_set_track_enabled( p_mp4->p_file, p_mp4->i_track, 1 ); - - gf_isom_set_visual_info( p_mp4->p_file, p_mp4->i_track, p_mp4->i_descidx, - p_param->i_width, p_param->i_height ); + /* Select brands. */ + lsmash_brand_type brands[11] = { 0 }; + uint32_t minor_version = 0; + uint32_t brand_count = 0; + if( p_mp4->b_brand_qt ) + { + brands[brand_count++] = ISOM_BRAND_TYPE_QT; + p_mp4->i_brand_3gpp = 0; + p_mp4->b_use_recovery = 0; /* Disable sample grouping. */ + } + else + { + if( p_mp4->i_brand_3gpp >= 1 ) + brands[brand_count++] = ISOM_BRAND_TYPE_3GP6; + if( p_mp4->i_brand_3gpp == 2 ) + { + brands[brand_count++] = ISOM_BRAND_TYPE_3G2A; + minor_version = 0x00010000; + } + brands[brand_count++] = ISOM_BRAND_TYPE_MP42; + brands[brand_count++] = ISOM_BRAND_TYPE_MP41; + brands[brand_count++] = ISOM_BRAND_TYPE_ISOM; + if( p_mp4->b_use_recovery ) + { + brands[brand_count++] = ISOM_BRAND_TYPE_AVC1; /* sdtp, sgpd, sbgp and visual roll recovery grouping */ + if( p_param->b_open_gop ) + { + brands[brand_count++] = ISOM_BRAND_TYPE_ISO6; /* cslg and visual random access grouping */ + brands[brand_count++] = ISOM_BRAND_TYPE_QT; /* tapt, cslg, stps and sdtp */ + p_mp4->b_brand_qt = 1; + } + } + } + /* Set movie parameters. */ + lsmash_movie_parameters_t movie_param; + lsmash_initialize_movie_parameters( &movie_param ); + movie_param.major_brand = p_mp4->major_brand; + movie_param.brands = brands; + movie_param.number_of_brands = brand_count; + movie_param.minor_version = minor_version; + MP4_FAIL_IF_ERR( lsmash_set_movie_parameters( p_mp4->p_root, &movie_param ), + "failed to set movie parameters.\n" ); + p_mp4->i_movie_timescale = lsmash_get_movie_timescale( p_mp4->p_root ); + MP4_FAIL_IF_ERR( !p_mp4->i_movie_timescale, "movie timescale is broken.\n" ); + + /* Create a video track. */ + p_mp4->i_track = lsmash_create_track( p_mp4->p_root, ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ); + MP4_FAIL_IF_ERR( !p_mp4->i_track, "failed to create a video track.\n" ); + + lsmash_video_summary_t summary; + memset( &summary, 0, sizeof(lsmash_video_summary_t) ); + summary.width = p_param->i_width; + summary.height = p_param->i_height; + uint32_t i_display_width = p_param->i_width << 16; + uint32_t i_display_height = p_param->i_height << 16; if( p_param->vui.i_sar_width && p_param->vui.i_sar_height ) { - uint64_t dw = p_param->i_width << 16; - uint64_t dh = p_param->i_height << 16; double sar = (double)p_param->vui.i_sar_width / p_param->vui.i_sar_height; if( sar > 1.0 ) - dw *= sar ; + i_display_width *= sar; else - dh /= sar; - gf_isom_set_pixel_aspect_ratio( p_mp4->p_file, p_mp4->i_track, p_mp4->i_descidx, p_param->vui.i_sar_width, p_param->vui.i_sar_height ); - gf_isom_set_track_layout_info( p_mp4->p_file, p_mp4->i_track, dw, dh, 0, 0, 0 ); + i_display_height /= sar; + if( !p_mp4->b_no_pasp ) + { + summary.par_h = p_param->vui.i_sar_width; + summary.par_v = p_param->vui.i_sar_height; + if( p_mp4->major_brand != ISOM_BRAND_TYPE_QT ) + summary.scaling_method = ISOM_SCALING_METHOD_MEET; + } } - - p_mp4->i_data_size = p_param->i_width * p_param->i_height * 3 / 2; - p_mp4->p_sample->data = malloc( p_mp4->i_data_size ); - if( !p_mp4->p_sample->data ) + if( p_mp4->b_brand_qt ) { - p_mp4->i_data_size = 0; - return -1; + summary.primaries = p_param->vui.i_colorprim; + summary.transfer = p_param->vui.i_transfer; + summary.matrix = p_param->vui.i_colmatrix; } - return 0; -} - -static int check_buffer( mp4_hnd_t *p_mp4, int needed_size ) -{ - if( needed_size > p_mp4->i_data_size ) + /* Set video track parameters. */ + lsmash_track_parameters_t track_param; + lsmash_initialize_track_parameters( &track_param ); + lsmash_track_mode track_mode = ISOM_TRACK_ENABLED | ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; + if( p_mp4->b_brand_qt ) + track_mode |= QT_TRACK_IN_POSTER; + track_param.mode = track_mode; + track_param.display_width = i_display_width; + track_param.display_height = i_display_height; + track_param.aperture_modes = p_mp4->b_brand_qt && !p_mp4->b_no_pasp; + MP4_FAIL_IF_ERR( lsmash_set_track_parameters( p_mp4->p_root, p_mp4->i_track, &track_param ), + "failed to set track parameters for video.\n" ); + + /* Set video media parameters. */ + lsmash_media_parameters_t media_param; + lsmash_initialize_media_parameters( &media_param ); + media_param.timescale = i_media_timescale; + media_param.media_handler_name = "L-SMASH Video Media Handler"; + if( p_mp4->b_brand_qt ) + media_param.data_handler_name = "L-SMASH URL Data Handler"; + if( p_mp4->b_use_recovery ) { - void *ptr = realloc( p_mp4->p_sample->data, needed_size ); - if( !ptr ) - return -1; - p_mp4->p_sample->data = ptr; - p_mp4->i_data_size = needed_size; + media_param.roll_grouping = p_param->b_intra_refresh; + media_param.rap_grouping = p_param->b_open_gop; } + MP4_FAIL_IF_ERR( lsmash_set_media_parameters( p_mp4->p_root, p_mp4->i_track, &media_param ), + "failed to set media parameters for video.\n" ); + p_mp4->i_video_timescale = lsmash_get_media_timescale( p_mp4->p_root, p_mp4->i_track ); + MP4_FAIL_IF_ERR( !p_mp4->i_video_timescale, "media timescale for video is broken.\n" ); + + /* Add a sample entry. */ + p_mp4->i_sample_entry = lsmash_add_sample_entry( p_mp4->p_root, p_mp4->i_track, ISOM_CODEC_TYPE_AVC1_VIDEO, &summary ); + MP4_FAIL_IF_ERR( !p_mp4->i_sample_entry, + "failed to add sample entry for video.\n" ); + + if( p_mp4->major_brand != ISOM_BRAND_TYPE_QT ) + MP4_FAIL_IF_ERR( lsmash_add_btrt( p_mp4->p_root, p_mp4->i_track, p_mp4->i_sample_entry ), + "failed to add btrt.\n" ); + return 0; } static int write_headers( hnd_t handle, x264_nal_t *p_nal ) { mp4_hnd_t *p_mp4 = handle; - GF_AVCConfigSlot *p_slot; - int sps_size = p_nal[0].i_payload - 4; - int pps_size = p_nal[1].i_payload - 4; - int sei_size = p_nal[2].i_payload; + uint32_t sps_size = p_nal[0].i_payload - 4; + uint32_t pps_size = p_nal[1].i_payload - 4; + uint32_t sei_size = p_nal[2].i_payload; uint8_t *sps = p_nal[0].p_payload + 4; uint8_t *pps = p_nal[1].p_payload + 4; uint8_t *sei = p_nal[2].p_payload; - // SPS - - p_mp4->p_config->configurationVersion = 1; - p_mp4->p_config->AVCProfileIndication = sps[1]; - p_mp4->p_config->profile_compatibility = sps[2]; - p_mp4->p_config->AVCLevelIndication = sps[3]; - p_slot = malloc( sizeof(GF_AVCConfigSlot) ); - if( !p_slot ) - return -1; - p_slot->size = sps_size; - p_slot->data = malloc( p_slot->size ); - if( !p_slot->data ) - return -1; - memcpy( p_slot->data, sps, sps_size ); - gf_list_add( p_mp4->p_config->sequenceParameterSets, p_slot ); - - // PPS - - p_slot = malloc( sizeof(GF_AVCConfigSlot) ); - if( !p_slot ) - return -1; - p_slot->size = pps_size; - p_slot->data = malloc( p_slot->size ); - if( !p_slot->data ) - return -1; - memcpy( p_slot->data, pps, pps_size ); - gf_list_add( p_mp4->p_config->pictureParameterSets, p_slot ); - gf_isom_avc_config_update( p_mp4->p_file, p_mp4->i_track, 1, p_mp4->p_config ); - - // SEI - - if( check_buffer( p_mp4, p_mp4->p_sample->dataLength + sei_size ) ) - return -1; - memcpy( p_mp4->p_sample->data + p_mp4->p_sample->dataLength, sei, sei_size ); - p_mp4->p_sample->dataLength += sei_size; + MP4_FAIL_IF_ERR( lsmash_set_avc_config( p_mp4->p_root, p_mp4->i_track, p_mp4->i_sample_entry, 1, sps[1], sps[2], sps[3], 3, 1, X264_BIT_DEPTH-8, X264_BIT_DEPTH-8 ), + "failed to set avc config.\n" ); + /* SPS */ + MP4_FAIL_IF_ERR( lsmash_add_sps_entry( p_mp4->p_root, p_mp4->i_track, p_mp4->i_sample_entry, sps, sps_size ), + "failed to add sps.\n" ); + /* PPS */ + MP4_FAIL_IF_ERR( lsmash_add_pps_entry( p_mp4->p_root, p_mp4->i_track, p_mp4->i_sample_entry, pps, pps_size ), + "failed to add pps.\n" ); + /* SEI */ + p_mp4->p_sei_buffer = malloc( sei_size ); + MP4_FAIL_IF_ERR( !p_mp4->p_sei_buffer, + "failed to allocate sei transition buffer.\n" ); + memcpy( p_mp4->p_sei_buffer, sei, sei_size ); + p_mp4->i_sei_size = sei_size; return sei_size + sps_size + pps_size; } @@ -315,21 +376,35 @@ static int write_headers( hnd_t handle, x264_nal_t *p_nal ) static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture ) { mp4_hnd_t *p_mp4 = handle; - int64_t dts; - int64_t cts; - - if( check_buffer( p_mp4, p_mp4->p_sample->dataLength + i_size ) ) - return -1; - memcpy( p_mp4->p_sample->data + p_mp4->p_sample->dataLength, p_nalu, i_size ); - p_mp4->p_sample->dataLength += i_size; + uint64_t dts, cts; if( !p_mp4->i_numframe ) - p_mp4->i_delay_time = p_picture->i_dts * -1; + { + p_mp4->i_start_offset = p_picture->i_dts * -1; + p_mp4->i_first_cts = p_mp4->b_dts_compress ? 0 : p_mp4->i_start_offset * p_mp4->i_time_inc; + if( p_mp4->b_fragments ) + MP4_LOG_IF_ERR( lsmash_create_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, UINT32_MAX, p_mp4->i_first_cts, ISOM_EDIT_MODE_NORMAL ), + "failed to set timeline map for video.\n" ); + } + + lsmash_sample_t *p_sample = lsmash_create_sample( i_size + p_mp4->i_sei_size ); + MP4_FAIL_IF_ERR( !p_sample, + "failed to create a video sample data.\n" ); + + if( p_mp4->p_sei_buffer ) + { + memcpy( p_sample->data, p_mp4->p_sei_buffer, p_mp4->i_sei_size ); + free( p_mp4->p_sei_buffer ); + p_mp4->p_sei_buffer = NULL; + } + + memcpy( p_sample->data + p_mp4->i_sei_size, p_nalu, i_size ); + p_mp4->i_sei_size = 0; if( p_mp4->b_dts_compress ) { if( p_mp4->i_numframe == 1 ) - p_mp4->i_init_delta = (p_picture->i_dts + p_mp4->i_delay_time) * p_mp4->i_time_inc; + p_mp4->i_init_delta = (p_picture->i_dts + p_mp4->i_start_offset) * p_mp4->i_time_inc; dts = p_mp4->i_numframe > p_mp4->i_delay_frames ? p_picture->i_dts * p_mp4->i_time_inc : p_mp4->i_numframe * (p_mp4->i_init_delta / p_mp4->i_dts_compress_multiplier); @@ -337,16 +412,28 @@ static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_ } else { - dts = (p_picture->i_dts + p_mp4->i_delay_time) * p_mp4->i_time_inc; - cts = (p_picture->i_pts + p_mp4->i_delay_time) * p_mp4->i_time_inc; + dts = (p_picture->i_dts + p_mp4->i_start_offset) * p_mp4->i_time_inc; + cts = (p_picture->i_pts + p_mp4->i_start_offset) * p_mp4->i_time_inc; + } + + p_sample->dts = dts; + p_sample->cts = cts; + p_sample->index = p_mp4->i_sample_entry; + p_sample->prop.random_access_type = p_picture->b_keyframe ? ISOM_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP : ISOM_SAMPLE_RANDOM_ACCESS_TYPE_NONE; + + if( p_mp4->b_fragments && p_mp4->i_numframe && p_sample->prop.random_access_type ) + { + MP4_FAIL_IF_ERR( lsmash_flush_pooled_samples( p_mp4->p_root, p_mp4->i_track, p_sample->dts - p_mp4->i_prev_dts ), + "failed to flush the rest of samples.\n" ); + MP4_FAIL_IF_ERR( lsmash_create_fragment_movie( p_mp4->p_root ), + "failed to create a movie fragment.\n" ); } - p_mp4->p_sample->IsRAP = p_picture->b_keyframe; - p_mp4->p_sample->DTS = dts; - p_mp4->p_sample->CTS_Offset = (uint32_t)(cts - dts); - gf_isom_add_sample( p_mp4->p_file, p_mp4->i_track, p_mp4->i_descidx, p_mp4->p_sample ); + /* Append data per sample. */ + MP4_FAIL_IF_ERR( lsmash_append_sample( p_mp4->p_root, p_mp4->i_track, p_sample ), + "failed to append a video frame.\n" ); - p_mp4->p_sample->dataLength = 0; + p_mp4->i_prev_dts = dts; p_mp4->i_numframe++; return i_size; diff --git a/output/mp4/box.h b/output/mp4/box.h new file mode 100644 index 0000000..d8848e6 --- /dev/null +++ b/output/mp4/box.h @@ -0,0 +1,2116 @@ +/***************************************************************************** + * box.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_BOX_H +#define LSMASH_BOX_H + +/* For generating creation_time and modification_time. + * According to ISO/IEC-14496-5-2001, the difference between Unix time and Mac OS time is 2082758400. + * However this is wrong and 2082844800 is correct. */ +#include +#define ISOM_MAC_EPOCH_OFFSET 2082844800 + +#include "utils.h" + +typedef struct isom_box_tag isom_box_t; + +/* If size is 1, then largesize is actual size. + * If size is 0, then this box is the last one in the file. + * usertype is for uuid. */ +#define ISOM_BASEBOX_COMMON \ + lsmash_root_t *root; /* pointer of root */ \ + isom_box_t *parent; /* pointer of the parent box of this box */ \ + uint8_t manager; /* flags for L-SMASH */ \ + uint64_t pos; /* starting position of this box in the file */ \ + uint64_t size; /* the number of bytes in this box */ \ + uint32_t type; /* four characters codes that identify box type */ \ + uint8_t *usertype + +#define ISOM_FULLBOX_COMMON \ + ISOM_BASEBOX_COMMON; \ + uint8_t version; /* Basically, version is either 0 or 1 */ \ + uint32_t flags /* In the actual structure of box, flags is 24 bits. */ + +#define ISOM_BASEBOX_COMMON_SIZE 8 +#define ISOM_FULLBOX_COMMON_SIZE 12 +#define ISOM_LIST_FULLBOX_COMMON_SIZE 16 + +#define LSMASH_UNKNOWN_BOX 0x01 +#define LSMASH_ABSENT_IN_ROOT 0x02 +#define LSMASH_QTFF_BASE 0x04 + +struct isom_box_tag +{ + ISOM_FULLBOX_COMMON; +}; + +/* File Type Box + * This box identifies the specifications to which this file complies. + * This box shall occur before any variable-length box. + * In the absence of this box, the file is QuickTime file format or MP4 version 1 file format. + * In MP4 version 1 file format, Object Descriptor Box is mandatory. + * In QuickTime file format, Object Descriptor Box isn't defined. + * Therefore, if this box and an Object Descriptor Box are absent in the file, the file shall be QuickTime file format. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t major_brand; /* brand identifier */ + uint32_t minor_version; /* the minor version of the major brand */ + uint32_t *compatible_brands; /* a list, to the end of the box, of brands */ + + uint32_t brand_count; /* the number of factors in compatible_brands array */ +} isom_ftyp_t; + +/* Track Header Box + * This box specifies the characteristics of a single track. */ +typedef struct +{ + /* version is either 0 or 1 + * flags + * 0x000001: Indicates that the track is enabled. + * A disabled track is treated as if it were not present. + * 0x000002: Indicates that the track is used in the presentation. + * 0x000004: Indicates that the track is used when previewing the presentation. + * 0x000008: Indicates that the track is used in the movie's poster. (only defined in QuickTime file format) + * ISOM: If in a presentation all tracks have neither track_in_movie nor track_in_preview set, + * then all tracks shall be treated as if both flags were set on all tracks. */ + ISOM_FULLBOX_COMMON; + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of this track (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the track was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t track_ID; /* an integer that uniquely identifies the track + * Track IDs are never re-used and cannot be zero. */ + uint32_t reserved1; + uint64_t duration; /* the duration of this track expressed in the movie timescale units */ + /* The following fields are treated as + * ISOM: template fields. + * MP41: reserved fields. + * MP42: ignored fileds since compositions are done using BIFS system. + * 3GPP: ignored fields except for alternate_group. + * QTFF: usable fields. */ + uint32_t reserved2[2]; + int16_t layer; /* the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer. */ + int16_t alternate_group; /* an integer that specifies a group or collection of tracks + * If this field is not 0, it should be the same for tracks that contain alternate data for one another + * and different for tracks belonging to different such groups. + * Only one track within an alternate group should be played or streamed at any one time. */ + int16_t volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + uint16_t reserved3; + int32_t matrix[9]; /* transformation matrix for the video */ + /* track's visual presentation size + * All images in the sequence are scaled to this size, before any overall transformation of the track represented by the matrix. + * Note: these fields are treated as reserved in MP4 version 1. */ + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ + /* */ +} isom_tkhd_t; + +/* Track Clean Aperture Dimensions Box + * A presentation mode where clap and pasp are reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_clef_t; + +/* Track Production Aperture Dimensions Box + * A presentation mode where pasp is reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_prof_t; + +/* Track Encoded Pixels Dimensions Box + * A presentation mode where clap and pasp are not reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_enof_t; + +/* Track Aperture Mode Dimensions Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_clef_t *clef; /* Track Clean Aperture Dimensions Box */ + isom_prof_t *prof; /* Track Production Aperture Dimensions Box */ + isom_enof_t *enof; /* Track Encoded Pixels Dimensions Box */ +} isom_tapt_t; + +/* Edit List Box + * This box contains an explicit timeline map. + * Each entry defines part of the track timeline: by mapping part of the media timeline, or by indicating 'empty' time, + * or by defining a 'dwell', where a single time-point in the media is held for a period. + * The last edit in a track shall never be an empty edit. + * Any difference between the duration in the Movie Header Box, and the track's duration is expressed as an implicit empty edit at the end. + * It is recommended that any edits, explicit or implied, not select any portion of the composition timeline that doesn't map to a sample. + * Therefore, if the first sample in the track has non-zero CTS, then this track should have at least one edit and the start time in it should + * correspond to the value of the CTS the first sample has or more not to exceed the largest CTS in this track. */ +typedef struct +{ + /* version == 0: 64bits -> 32bits */ + uint64_t segment_duration; /* the duration of this edit expressed in the movie timescale units */ + int64_t media_time; /* the starting composition time within the media of this edit segment + * If this field is set to -1, it is an empty edit. */ + int32_t media_rate; /* the relative rate at which to play the media corresponding to this edit segment + * If this value is 0, then the edit is specifying a 'dwell': + * the media at media_time is presented for the segment_duration. + * This field is expressed as 16.16 fixed-point number. */ +} isom_elst_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + lsmash_entry_list_t *list; +} isom_elst_t; + +/* Edit Box + * This optional box maps the presentation time-line to the media time-line as it is stored in the file. + * In the absence of this box, there is an implicit one-to-one mapping of these time-lines, + * and the presentation of a track starts at the beginning of the presentation. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_elst_t *elst; /* Edit List Box */ +} isom_edts_t; + +/* Track Reference Box + * The Track Reference Box contains Track Reference Type Boxes. + * Track Reference Type Boxes define relationships between tracks. + * They allow one track to specify how it is related to other tracks. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t *track_ID; /* track_IDs of reference tracks / Zero value must not be used */ + + uint32_t ref_count; /* number of reference tracks */ +} isom_tref_type_t; + +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t *ref_list; /* Track Reference Type Boxes */ +} isom_tref_t; + +/* Media Header Box + * This box declares overall information that is media-independent, and relevant to characteristics of the media in a track.*/ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of the media in this track (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the media in this track was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t timescale; /* media timescale: timescale for this media */ + uint64_t duration; /* the duration of this media expressed in the timescale indicated in this box */ + /* */ + uint16_t language; /* ISOM: ISO-639-2/T language codes. Most significant 1-bit is 0. + * Each character is packed as the difference between its ASCII value and 0x60. + * QTFF: Macintosh language codes is usually used. + * Mac's value is less than 0x800 while ISO's value is 0x800 or greater. */ + int16_t quality; /* ISOM: pre_defined / QTFF: the media's playback quality */ +} isom_mdhd_t; + +/* Handler Reference Box + * In Media Box, this box is mandatory and (ISOM: should/QTFF: must) come before Media Information Box. + * ISOM: this box might be also in Meta Box. + * QTFF: this box might be also in Media Information Box. If this box is present there, it must come before Data Information Box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t componentType; /* ISOM: pre_difined = 0 + * QTFF: 'mhlr' for Media Handler Reference Box and 'dhlr' for Data Handler Reference Box */ + uint32_t componentSubtype; /* Both ISOM and QT: when present in Media Handler Reference Box, this field defines the type of media data. + * ISOM: when present in Metadata Handler Reference Box, this field defines the format of the meta box contents. + * QTFF: when present in Data Handler Reference Box, this field defines the data reference type. */ + /* The following fields are defined in QTFF however these fields aren't mentioned in QuickTime SDK and are reserved in the specification. + * In ISOM, these fields are still defined as reserved. */ + uint32_t componentManufacturer; /* vendor indentification / A value of 0 matches any manufacturer. */ + uint32_t componentFlags; /* flags describing required component capabilities + * The high-order 8 bits should be set to 0. + * The low-order 24 bits are specific to each component type. */ + uint32_t componentFlagsMask; /* This field indicates which flags in the componentFlags field are relevant to this operation. */ + /* */ + uint8_t *componentName; /* ISOM: a null-terminated string in UTF-8 characters + * QTFF: Pascal string */ + + uint32_t componentName_length; +} isom_hdlr_t; + + +/** Media Information Header Boxes + ** There is a different media information header for each track type + ** (corresponding to the media handler-type); the matching header shall be present. **/ +/* Video Media Header Box + * This box contains general presentation information, independent of the coding, for video media. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags is 1 */ + uint16_t graphicsmode; /* template: graphicsmode = 0 */ + uint16_t opcolor[3]; /* template: opcolor = { 0, 0, 0 } */ +} isom_vmhd_t; + +/* Sound Media Header Box + * This box contains general presentation information, independent of the coding, for audio media. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + int16_t balance; /* a fixed-point 8.8 number that places mono audio tracks in a stereo space. template: balance = 0 */ + uint16_t reserved; +} isom_smhd_t; + +/* Hint Media Header Box + * This box contains general information, independent of the protocol, for hint tracks. (A PDU is a Protocol Data Unit.) */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t maxPDUsize; /* the size in bytes of the largest PDU in this (hint) stream */ + uint16_t avgPDUsize; /* the average size of a PDU over the entire presentation */ + uint32_t maxbitrate; /* the maximum rate in bits/second over any window of one second */ + uint32_t avgbitrate; /* the average rate in bits/second over the entire presentation */ + uint32_t reserved; +} isom_hmhd_t; + +/* Null Media Header Box + * This box may be used for streams other than visual and audio (e.g., timed metadata streams). */ +typedef struct +{ + /* Streams other than visual and audio may use a Null Media Header Box */ + ISOM_FULLBOX_COMMON; /* flags is currently all zero */ +} isom_nmhd_t; + +/* Generic Media Information Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t graphicsmode; + uint16_t opcolor[3]; + int16_t balance; /* This field is nomally set to 0. */ + uint16_t reserved; /* Reserved for use by Apple. Set this field to 0. */ +} isom_gmin_t; + +/* Text Media Information Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + int32_t matrix[9]; /* Unkown fields. Default values are probably: + * { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 } */ +} isom_text_t; + +/* Generic Media Information Header Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_gmin_t *gmin; /* Generic Media Information Box */ + isom_text_t *text; /* Text Media Information Box */ +} isom_gmhd_t; +/** **/ + +/* Data Reference Box + * name and location fields are expressed in null-terminated string using UTF-8 characters. */ +typedef struct +{ + /* This box is DataEntryUrlBox or DataEntryUrnBox */ + ISOM_FULLBOX_COMMON; /* flags == 0x000001 means that the media data is in the same file + * as the Movie Box containing this data reference. */ + char *name; /* only for DataEntryUrnBox */ + char *location; /* a location to find the resource with the given name */ + + uint32_t name_length; + uint32_t location_length; +} isom_dref_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_dref_t; + +/* Data Information Box */ +typedef struct +{ + /* This box is in Media Information Box or Meta Box */ + ISOM_BASEBOX_COMMON; + isom_dref_t *dref; /* Data Reference Box */ +} isom_dinf_t; + +/** Sample Description **/ +/* ES Descriptor Box */ +struct mp4sys_ES_Descriptor_t; /* FIXME: I think these structs using mp4sys should be placed in isom.c */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + struct mp4sys_ES_Descriptor_t *ES; +} isom_esds_t; + +/* AVCDecoderConfigurationRecord */ +typedef struct +{ +#define ISOM_REQUIRES_AVCC_EXTENSION( x ) ((x) == 100 || (x) == 110 || (x) == 122 || (x) == 144) + ISOM_BASEBOX_COMMON; + uint8_t configurationVersion; /* 1 */ + uint8_t AVCProfileIndication; /* profile_idc in SPS */ + uint8_t profile_compatibility; + uint8_t AVCLevelIndication; /* level_idc in SPS */ + uint8_t lengthSizeMinusOne; /* in bytes of the NALUnitLength field. upper 6-bits are reserved as 111111b */ + uint8_t numOfSequenceParameterSets; /* upper 3-bits are reserved as 111b */ + lsmash_entry_list_t *sequenceParameterSets; /* SPSs */ + uint8_t numOfPictureParameterSets; + lsmash_entry_list_t *pictureParameterSets; /* PPSs */ + /* if( ISOM_REQUIRES_AVCC_EXTENSION( AVCProfileIndication ) ) */ + uint8_t chroma_format; /* chroma_format_idc in SPS / upper 6-bits are reserved as 111111b */ + uint8_t bit_depth_luma_minus8; /* shall be in the range of 0 to 4 / upper 5-bits are reserved as 11111b */ + uint8_t bit_depth_chroma_minus8; /* shall be in the range of 0 to 4 / upper 5-bits are reserved as 11111b */ + uint8_t numOfSequenceParameterSetExt; + lsmash_entry_list_t *sequenceParameterSetExt; /* SPSExts */ + /* */ +} isom_avcC_t; + +/* Parameter Set Entry */ +typedef struct +{ + uint16_t parameterSetLength; + uint8_t *parameterSetNALUnit; +} isom_avcC_ps_entry_t; + +/* MPEG-4 Bit Rate Box + * This box signals the bit rate information of the AVC video stream. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t bufferSizeDB; /* the size of the decoding buffer for the elementary stream in bytes */ + uint32_t maxBitrate; /* the maximum rate in bits/second over any window of one second */ + uint32_t avgBitrate; /* the average rate in bits/second over the entire presentation */ +} isom_btrt_t; + +/* Clean Aperture Box + * There are notionally four values in this box and these parameters are represented as a fraction N/D. + * Here, we refer to the pair of parameters fooN and fooD as foo. + * Considering the pixel dimensions as defined by the VisualSampleEntry width and height. + * If picture centre of the image is at pcX and pcY, then horizOff and vertOff are defined as follows: + * pcX = horizOff + (width - 1)/2; + * pcY = vertOff + (height - 1)/2; + * The leftmost/rightmost pixel and the topmost/bottommost line of the clean aperture fall at: + * pcX +/- (cleanApertureWidth - 1)/2; + * pcY +/- (cleanApertureHeight - 1)/2; */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t cleanApertureWidthN; + uint32_t cleanApertureWidthD; + uint32_t cleanApertureHeightN; + uint32_t cleanApertureHeightD; + int32_t horizOffN; + uint32_t horizOffD; + int32_t vertOffN; + uint32_t vertOffD; +} isom_clap_t; + +/* Pixel Aspect Ratio Box + * This box specifies the aspect ratio of a pixel, in arbitrary units. + * If a pixel appears H wide and V tall, then hSpacing/vSpacing is equal to H/V. + * When adjusting pixel aspect ratio, normally, the horizontal dimension of the video is scaled, if needed. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t hSpacing; /* horizontal spacing */ + uint32_t vSpacing; /* vertical spacing */ +} isom_pasp_t; + +/* Color Parameter Box + * This box is used to map the numerical values of pixels in the file to a common representation of color + * in which images can be correctly compared, combined, and displayed. + * This box is defined in QuickTime file format. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t color_parameter_type; /* 'nclc' or 'prof' */ + /* for 'nclc' */ + uint16_t primaries_index; /* CIE 1931 xy chromaticity coordinates */ + uint16_t transfer_function_index; /* nonlinear transfer function from RGB to ErEgEb */ + uint16_t matrix_index; /* matrix from ErEgEb to EyEcbEcr */ +} isom_colr_t; + +/* Sample Scale Box + * If this box is present and can be interpreted by the decoder, + * all samples shall be displayed according to the scaling behaviour that is specified in this box. + * Otherwise, all samples are scaled to the size that is indicated by the width and height field in the Track Header Box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t constraint_flag; /* Upper 7-bits are reserved. + * If this flag is set, all samples described by this sample entry shall be scaled + * according to the method specified by the field 'scale_method'. */ + uint8_t scale_method; /* The semantics of the values for scale_method are as specified for the 'fit' attribute of regions in SMIL 1.0. */ + int16_t display_center_x; + int16_t display_center_y; +} isom_stsl_t; + +/* Sample Entry */ +#define ISOM_SAMPLE_ENTRY \ + ISOM_BASEBOX_COMMON; \ + uint8_t reserved[6]; \ + uint16_t data_reference_index; + +typedef struct +{ + ISOM_SAMPLE_ENTRY; +} isom_sample_entry_t; + +/* Mpeg Sample Entry */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + isom_esds_t *esds; /* ES Descriptor Box */ +} isom_mp4s_entry_t; + +/* ISOM: Visual Sample Entry / QTFF: Image Description */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int16_t version; /* ISOM: pre_defined / QTFF: sample description version */ + int16_t revision_level; /* ISOM: reserved / QTFF: version of the CODEC */ + int32_t vendor; /* ISOM: pre_defined / QTFF: whose CODEC */ + uint32_t temporalQuality; /* ISOM: pre_defined / QTFF: the temporal quality factor */ + uint32_t spatialQuality; /* ISOM: pre_defined / QTFF: the spatial quality factor */ + /* The width and height are the maximum pixel counts that the codec will deliver. + * Since these are counts they do not take into account pixel aspect ratio. */ + uint16_t width; + uint16_t height; + /* */ + uint32_t horizresolution; /* 16.16 fixed-point / template: horizresolution = 0x00480000 / 72 dpi */ + uint32_t vertresolution; /* 16.16 fixed-point / template: vertresolution = 0x00480000 / 72 dpi */ + uint32_t dataSize; /* ISOM: reserved / QTFF: if known, the size of data for this descriptor */ + uint16_t frame_count; /* frame per sample / template: frame_count = 1 */ + char compressorname[33]; /* a fixed 32-byte field, with the first byte set to the number of bytes to be displayed */ + uint16_t depth; /* ISOM: template: depth = 0x0018 + * AVC : 0x0018: colour with no alpha + * 0x0028: grayscale with no alpha + * 0x0020: gray or colour with alpha + * QTFF: depth of this data (1-32) or (33-40 grayscale) */ + int16_t color_table_ID; /* ISOM: template: pre_defined = -1 + * QTFF: color table ID + * If this field is set to 0, the default color table should be used for the specified depth + * If the color table ID is set to 0, a color table is contained within the sample description itself. + * The color table immediately follows the color table ID field. */ + /* AVC specific extensions */ + isom_avcC_t *avcC; /* AVCDecoderConfigurationRecord */ + isom_btrt_t *btrt; /* MPEG-4 Bit Rate Box @ optional */ + /* MP4 specific extension */ + isom_esds_t *esds; /* ES Descriptor Box */ + /* QuickTime specific extension */ + isom_colr_t *colr; /* Color Parameter Box @ optional */ + /* ISO Base Media extension */ + isom_stsl_t *stsl; /* Sample Scale Box @ optional */ + /* common extensions + * For maximum compatibility, these boxes should follow, not precede, any boxes defined in or required by derived specifications. */ + isom_clap_t *clap; /* Clean Aperture Box @ optional */ + isom_pasp_t *pasp; /* Pixel Aspect Ratio Box @ optional */ + + uint32_t exdata_length; + void *exdata; +} isom_visual_entry_t; + +/* Format Box + * This box shows the data format of the stored sound media. + * ISO base media file format also defines the same four-character-code for the type field, + * however, that is used to indicate original sample description of the media when a protected sample entry is used. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t data_format; /* copy of sample description type */ +} isom_frma_t; + +/* Audio Endian Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + int16_t littleEndian; +} isom_enda_t; + +/* MPEG-4 Audio Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t unknown; /* always 0? */ +} isom_mp4a_t; + +/* Terminator Box + * This box is present to indicate the end of the sound description. It contains no data. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* size = 8, type = 0x00000000 */ +} isom_terminator_t; + +/* Sound Information Decompression Parameters Box + * This box provides the ability to store data specific to a given audio decompressor in the sound description. + * The contents of this box are dependent on the audio decompressor. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_frma_t *frma; /* Format Box */ + isom_enda_t *enda; /* Audio Endian Box */ + isom_mp4a_t *mp4a; /* MPEG-4 Audio Box */ + isom_esds_t *esds; /* ES Descriptor Box */ + isom_terminator_t *terminator; /* Terminator Box */ + + uint32_t exdata_length; + void *exdata; +} isom_wave_t; + +/* Channel Compositor Box */ +typedef struct +{ + uint32_t channelLabel; /* the channelLabel that describes the channel */ + uint32_t channelFlags; /* flags that control the interpretation of coordinates */ + uint32_t coordinates[3]; /* an ordered triple that specifies a precise speaker location / 32-bit floating point */ +} isom_channel_description_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t channelLayoutTag; /* the channelLayoutTag indicates the layout */ + uint32_t channelBitmap; /* If channelLayoutTag is set to 0x00010000, this field is the channel usage bitmap. */ + uint32_t numberChannelDescriptions; /* the number of items in the Channel Descriptions array */ + /* Channel Descriptions array */ + isom_channel_description_t *channelDescriptions; +} isom_chan_t; + +/* ISOM: Audio Sample Entry / QTFF: Sound Description */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int16_t version; /* ISOM: reserved + * QTFF: sample description version + * version = 0 supports only 'raw ' or 'twos' audio format. + * version = 1 is used to support out-of-band configuration settings for decompression. + * version = 2 is used to support high samplerate or 3 or more multichannel audio. */ + int16_t revision_level; /* ISOM: reserved / QTFF: version of the CODEC */ + int32_t vendor; /* ISOM: reserved / QTFF: whose CODEC */ + uint16_t channelcount; /* ISOM: template: channelcount = 2 + * QTFF: the number of audio channels + * Allowable values are 1 (mono) or 2 (stereo). + * For more than 2, set this field to 3 and use numAudioChannels instead of this field. */ + uint16_t samplesize; /* ISOM: template: samplesize = 16 + * QTFF: the number of bits in each uncompressed sample for a single channel + * Allowable values are 8 or 16. + * For non-mod8, set this field to 16 and use constBitsPerChannel instead of this field. + * For more than 16, set this field to 16 and use bytesPerPacket instead of this field. */ + int16_t compression_ID; /* ISOM: pre_defined + * QTFF: version = 0 -> must be set to 0. + * version = 2 -> must be set to -2. */ + uint16_t packet_size; /* ISOM: reserved / QTFF: must be set to 0. */ + uint32_t samplerate; /* the sampling rate expressed as a 16.16 fixed-point number + * ISOM: template: samplerate = {default samplerate of media}<<16 + * QTFF: the integer portion should match the media's timescale. + * If this field is invalid because of higher samplerate, + * then set this field to 0x00010000 and use audioSampleRate instead of this field. */ + /* version 1 fields + * These fields are for description of the compression ratio of fixed ratio audio compression algorithms. + * If these fields are not used, they are set to 0. */ + uint32_t samplesPerPacket; /* For compressed audio, be set to the number of uncompressed frames generated by a compressed frame. + * For uncompressed audio, shall be set to 1. */ + uint32_t bytesPerPacket; /* the number of bytes in a sample for a single channel */ + uint32_t bytesPerFrame; /* the number of bytes in a frame */ + uint32_t bytesPerSample; /* 8-bit audio: 1, other audio: 2 */ + /* version 2 fields + * LPCMFrame: one sample from each channel. + * AudioPacket: For uncompressed audio, an AudioPacket is simply one LPCMFrame. + * For compressed audio, an AudioPacket is the natural compressed access unit of that format. */ + uint32_t sizeOfStructOnly; /* offset to extensions */ + uint64_t audioSampleRate; /* 64-bit floating point */ + uint32_t numAudioChannels; /* any channel assignment info will be in Channel Compositor Box. */ + int32_t always7F000000; /* always 0x7F000000 */ + uint32_t constBitsPerChannel; /* only set if constant (and only for uncompressed audio) */ + uint32_t formatSpecificFlags; + uint32_t constBytesPerAudioPacket; /* only set if constant */ + uint32_t constLPCMFramesPerAudioPacket; /* only set if constant */ + /* extensions */ + isom_esds_t *esds; /* ISOM: ES Descriptor Box / QTFF: null */ + isom_wave_t *wave; /* ISOM: null / QTFF: Sound Information Decompression Parameters Box */ + isom_chan_t *chan; /* ISOM: null / QTFF: Channel Compositor Box @ optional */ + + uint32_t exdata_length; + void *exdata; + lsmash_audio_summary_t summary; +} isom_audio_entry_t; + +/* Hint Sample Entry */ +#define ISOM_HINT_SAMPLE_ENTRY \ + ISOM_SAMPLE_ENTRY; \ + uint8_t *data; + +typedef struct +{ + ISOM_HINT_SAMPLE_ENTRY; + uint32_t data_length; +} isom_hint_entry_t; + +/* Metadata Sample Entry */ +#define ISOM_METADATA_SAMPLE_ENTRY \ + ISOM_SAMPLE_ENTRY; + +typedef struct +{ + ISOM_METADATA_SAMPLE_ENTRY; +} isom_metadata_entry_t; + +/* QuickTime Text Sample Description */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int32_t displayFlags; + int32_t textJustification; + uint16_t bgColor[3]; /* background RGB color */ + /* defaultTextBox */ + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; + /* defaultStyle */ + int32_t scrpStartChar; /* starting character position */ + int16_t scrpHeight; + int16_t scrpAscent; + int16_t scrpFont; + uint16_t scrpFace; /* only first 8-bits are used */ + int16_t scrpSize; + uint16_t scrpColor[3]; /* foreground RGB color */ + /* defaultFontName is Pascal string */ + uint8_t font_name_length; + char *font_name; +} isom_text_entry_t; + +/* FontRecord */ +typedef struct +{ + uint16_t font_ID; + /* Pascal string */ + uint8_t font_name_length; + char *font_name; +} isom_font_record_t; + +/* Font Table Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* FontRecord + * entry_count is uint16_t. */ + lsmash_entry_list_t *list; +} isom_ftab_t; + +/* Timed Text Sample Entry */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + uint32_t displayFlags; + int8_t horizontal_justification; + int8_t vertical_justification; + uint8_t background_color_rgba[4]; + /* BoxRecord default_text_box */ + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; + /* StyleRecord default_style */ + uint16_t startChar; /* always 0 */ + uint16_t endChar; /* always 0 */ + uint16_t font_ID; + uint8_t face_style_flags; + uint8_t font_size; + uint8_t text_color_rgba[4]; + /* Font Table Box font_table */ + isom_ftab_t *ftab; +} isom_tx3g_entry_t; + +/* Sample Description Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stsd_t; +/** **/ + +/* Decoding Time to Sample Box + * This box contains a compact version of a table that allows indexing from decoding time to sample number. + * Each entry in the table gives the number of consecutive samples with the same time delta, and the delta of those samples. + * By adding the deltas a complete time-to-sample map may be built. + * All samples must have non-zero durations except for the last one. + * The sum of all deltas gives the media duration in the track (not mapped to the movie timescale, and not considering any edit list). + * DTS is an abbreviation of 'decoding time stamp'. */ +typedef struct +{ + uint32_t sample_count; /* number of consecutive samples that have the given sample_delta */ + uint32_t sample_delta; /* DTS[0] = 0; DTS[n+1] = DTS[n] + sample_delta[n]; */ +} isom_stts_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stts_t; + +/* Composition Time to Sample Box + * This box provides the offset between decoding time and composition time. + * CTS is an abbreviation of 'composition time stamp'. + * This box is optional and must only be present if DTS and CTS differ for any samples. + * ISOM: if version is set to 1, sample_offset is signed 32-bit integer. + * QTFF: sample_offset is always signed 32-bit integer. */ +typedef struct +{ + uint32_t sample_count; /* number of consecutive samples that have the given sample_offset */ + uint32_t sample_offset; /* CTS[n] = DTS[n] + sample_offset[n]; */ +} isom_ctts_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_ctts_t; + +/* Composition to Decode Box (Composition Shift Least Greatest Box) + * This box may be used to relate the composition and decoding timelines, + * and deal with some of the ambiguities that signed composition offsets introduce. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + int32_t compositionToDTSShift; /* If this value is added to the composition times (as calculated by the CTS offsets from the DTS), + * then for all samples, their CTS is guaranteed to be greater than or equal to their DTS, + * and the buffer model implied by the indicated profile/level will be honoured; + * if leastDecodeToDisplayDelta is positive or zero, this field can be 0; + * otherwise it should be at least (- leastDecodeToDisplayDelta). */ + int32_t leastDecodeToDisplayDelta; /* the smallest sample_offset in this track */ + int32_t greatestDecodeToDisplayDelta; /* the largest sample_offset in this track */ + int32_t compositionStartTime; /* the smallest CTS for any sample */ + int32_t compositionEndTime; /* the CTS plus the composition duration, of the sample with the largest CTS in this track */ +} isom_cslg_t; + +/* Sample Size Box + * This box contains the sample count and a table giving the size in bytes of each sample. + * The total number of samples in the media is always indicated in the sample_count. + * Note: a sample size of zero is not prohibited in general, but it must be valid and defined for the coding system, + * as defined by the sample entry, that the sample belongs to. */ +typedef struct +{ + uint32_t entry_size; /* the size of a sample */ +} isom_stsz_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t sample_size; /* If this field is set to 0, then the samples have different sizes. */ + uint32_t sample_count; /* the number of samples in the track */ + lsmash_entry_list_t *list; /* available if sample_size == 0 */ +} isom_stsz_t; + +/* Sync Sample Box + * If this box is not present, every sample is a random access point. + * In AVC streams, this box cannot point non-IDR samples. + * The table is arranged in strictly increasing order of sample number. */ +typedef struct +{ + uint32_t sample_number; /* the numbers of the samples that are random access points in the stream. */ +} isom_stss_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stss_t; + +/* Partial Sync Sample Box + * Tip from QT engineering - Open-GOP intra frames need to be marked as "partial sync samples". + * Partial sync frames perform a partial reset of inter-frame dependencies; + * decoding two partial sync frames and the non-droppable difference frames between them is + * sufficient to prepare a decompressor for correctly decoding the difference frames that follow. */ +typedef struct +{ + uint32_t sample_number; /* the numbers of the samples that are partial sync samples in the stream. */ +} isom_stps_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stps_t; + +/* Independent and Disposable Samples Box */ +typedef struct +{ + unsigned is_leading : 2; /* ISOM: leading / QTFF: samples later in decode order may have earlier display times */ + unsigned sample_depends_on : 2; /* independency */ + unsigned sample_is_depended_on : 2; /* disposable */ + unsigned sample_has_redundancy : 2; /* redundancy */ +} isom_sdtp_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + /* According to the specification, the size of the table, sample_count, doesn't exist in this box. + * Instead of this, it is taken from the sample_count in the stsz or the stz2 box. */ + lsmash_entry_list_t *list; +} isom_sdtp_t; + +/* Sample To Chunk Box + * This box can be used to find the chunk that contains a sample, its position, and the associated sample description. + * The table is compactly coded. Each entry gives the index of the first chunk of a run of chunks with the same characteristics. + * By subtracting one entry here from the previous one, you can compute how many chunks are in this run. + * You can convert this to a sample count by multiplying by the appropriate samples_per_chunk. */ +typedef struct +{ + uint32_t first_chunk; /* the index of the first chunk in this run of chunks that share the same samples_per_chunk and sample_description_index */ + uint32_t samples_per_chunk; /* the number of samples in each of these chunks */ + uint32_t sample_description_index; /* the index of the sample entry that describes the samples in this chunk */ +} isom_stsc_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stsc_t; + +/* Chunk Offset Box + * chunk_offset is the offset of the start of a chunk into its containing media file. + * Offsets are file offsets, not the offset into any box within the file. */ +typedef struct +{ + uint32_t chunk_offset; +} isom_stco_entry_t; + +typedef struct +{ + /* for large presentations */ + uint64_t chunk_offset; +} isom_co64_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* type = 'stco': 32-bit chunk offsets / type = 'co64': 64-bit chunk offsets */ + lsmash_entry_list_t *list; + + uint8_t large_presentation; /* Set 1 to this if 64-bit chunk-offset are needed. */ +} isom_stco_t; /* share with co64 box */ + +/* Sample Group Description Box + * This box gives information about the characteristics of sample groups. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* Use of version 0 entries is deprecated. */ + uint32_t grouping_type; /* an integer that identifies the sbgp that is associated with this sample group description */ + uint32_t default_length; /* the length of every group entry (if the length is constant), or zero (if it is variable) + * This field is available only if version == 1. */ + lsmash_entry_list_t *list; +} isom_sgpd_entry_t; + +/* Random Access Entry + * Samples marked by this group must be random access points, and may also be sync points. */ +typedef struct +{ + /* grouping_type is 'rap ' */ + uint32_t description_length; /* This field is available only if version == 1 and default_length == 0. */ + unsigned num_leading_samples_known : 1; /* the value of one indicates that the number of leading samples is known for each sample in this group, + * and the number is specified by num_leading_samples. */ + unsigned num_leading_samples : 7; /* the number of leading samples for each sample in this group + * Note: when num_leading_samples_known is equal to 0, this field should be ignored. */ +} isom_rap_entry_t; + +/* Roll Recovery Entry + * This grouping type is defined as that group of samples having the same roll distance. */ +typedef struct +{ + /* grouping_type is 'roll' */ + uint32_t description_length; /* This field is available only if version == 1 and default_length == 0. */ + int16_t roll_distance; /* the number of samples that must be decoded in order for a sample to be decoded correctly + * A positive value indicates post-roll, and a negative value indicates pre-roll. + * The value zero must not be used. */ +} isom_roll_entry_t; + +/* Sample to Group Box + * This box is used to find the group that a sample belongs to and the associated description of that sample group. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t grouping_type; /* Links it to its sample group description table with the same value for grouping type. */ + uint32_t grouping_type_parameter; /* an indication of the sub-type of the grouping + * This field is available only if version == 1. */ + lsmash_entry_list_t *list; +} isom_sbgp_entry_t; + +typedef struct +{ + uint32_t sample_count; /* the number of consecutive samples with the same sample group descriptor */ + uint32_t group_description_index; /* the index of the sample group entry which describes the samples in this group + * The index ranges from 1 to the number of sample group entries in the Sample Group Description Box, + * or takes the value 0 to indicate that this sample is a member of no group of this type. */ +} isom_group_assignment_entry_t; + +/* Sample Table Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_stsd_t *stsd; /* Sample Description Box */ + isom_stts_t *stts; /* Decoding Time to Sample Box */ + isom_ctts_t *ctts; /* Composition Time to Sample Box */ + isom_cslg_t *cslg; /* ISOM: Composition to Decode Box / QTFF: Composition Shift Least Greatest Box */ + isom_stss_t *stss; /* Sync Sample Box */ + isom_stps_t *stps; /* ISOM: null / QTFF: Partial Sync Sample Box */ + isom_sdtp_t *sdtp; /* Independent and Disposable Samples Box */ + isom_stsc_t *stsc; /* Sample To Chunk Box */ + isom_stsz_t *stsz; /* Sample Size Box */ + isom_stco_t *stco; /* Chunk Offset Box */ + lsmash_entry_list_t *sgpd_list; /* Sample Group Description Boxes */ + lsmash_entry_list_t *sbgp_list; /* Sample To Group Boxes */ +} isom_stbl_t; + +/* Media Information Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* Media Information Header Boxes */ + isom_vmhd_t *vmhd; /* Video Media Header Box */ + isom_smhd_t *smhd; /* Sound Media Header Box */ + isom_hmhd_t *hmhd; /* ISOM: Hint Media Header Box / QTFF: null */ + isom_nmhd_t *nmhd; /* ISOM: Null Media Header Box / QTFF: null */ + isom_gmhd_t *gmhd; /* ISOM: null / QTFF: Generic Media Information Header Box */ + /* */ + isom_hdlr_t *hdlr; /* ISOM: null / QTFF: Data Handler Reference Box + * Note: this box must come before Data Information Box. */ + isom_dinf_t *dinf; /* Data Information Box */ + isom_stbl_t *stbl; /* Sample Table Box */ +} isom_minf_t; + +/* Media Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mdhd_t *mdhd; /* Media Header Box */ + isom_hdlr_t *hdlr; /* ISOM: Handler Reference Box / QTFF: Media Handler Reference Box + * Note: this box must come before Media Information Box. */ + isom_minf_t *minf; /* Media Information Box */ +} isom_mdia_t; + +/* Movie Header Box + * This box defines overall information which is media-independent, and relevant to the entire presentation considered as a whole. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of the presentation (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the presentation was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t timescale; /* movie timescale: timescale for the entire presentation */ + uint64_t duration; /* the duration, expressed in movie timescale, of the longest track */ + /* The following fields are treated as + * ISOM: template fields. + * MP41: reserved fields. + * MP42: ignored fileds since compositions are done using BIFS system. + * 3GPP: ignored fields. + * QTFF: usable fields. */ + int32_t rate; /* fixed point 16.16 number. 0x00010000 is normal forward playback. */ + int16_t volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + int16_t reserved; + int32_t preferredLong[2]; /* ISOM: reserved / QTFF: unknown */ + int32_t matrix[9]; /* transformation matrix for the video */ + /* The following fields are defined in QuickTime file format. + * In ISO Base Media file format, these fields are treated as pre_defined. */ + int32_t previewTime; /* the time value in the movie at which the preview begins */ + int32_t previewDuration; /* the duration of the movie preview in movie timescale units */ + int32_t posterTime; /* the time value of the time of the movie poster */ + int32_t selectionTime; /* the time value for the start time of the current selection */ + int32_t selectionDuration; /* the duration of the current selection in movie timescale units */ + int32_t currentTime; /* the time value for current time position within the movie */ + /* */ + uint32_t next_track_ID; /* larger than the largest track-ID in use */ +} isom_mvhd_t; + +/* Object Descriptor Box + * Note that this box is mandatory under 14496-1:2001 (mp41) while not mandatory under 14496-14:2003 (mp42). */ +struct mp4sys_ObjectDescriptor_t; /* FIXME: I think these structs using mp4sys should be placed in isom.c */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + struct mp4sys_ObjectDescriptor_t *OD; +} isom_iods_t; + +/* Media Data Box + * This box contains the media data. + * A presentation may contain zero or more Media Data Boxes.*/ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* If size is 0, then this box is the last box. */ + + uint64_t placeholder_pos; /* placeholder position for largesize */ +} isom_mdat_t; + +/* Free Space Box + * The contents of a free-space box are irrelevant and may be ignored without affecting the presentation. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* type is 'free' or 'skip' */ + uint32_t length; + uint8_t *data; +} isom_free_t; + +typedef isom_free_t isom_skip_t; + +/* Chapter List Box + * This box is NOT defined in the ISO/MPEG-4 specs. */ +typedef struct +{ + uint64_t start_time; /* version = 0: expressed in movie timescale units + * version = 1: expressed in 100 nanoseconds */ + /* Chapter name is Pascal string */ + uint8_t chapter_name_length; + char *chapter_name; +} isom_chpl_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version = 0 is defined in F4V file format. */ + uint8_t unknown; /* only available under version = 1 */ + lsmash_entry_list_t *list; /* if version is set to 0, entry_count is uint8_t. */ +} isom_chpl_t; + +typedef struct +{ + char *chapter_name; + uint64_t start_time; +} isom_chapter_entry_t; + +/* Metadata Item Keys Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_keys_t; + +typedef struct +{ + uint32_t key_size; /* the size of the entire structure containing a key definition + * key_size = sizeof(key_size) + sizeof(key_namespace) + sizeof(key_value) */ + uint32_t key_namespace; /* a naming scheme used for metadata keys + * Location metadata keys, for example, use the 'mdta' key namespace. */ + uint8_t *key_value; /* the actual name of the metadata key + * Keys with the 'mdta' namespace use a reverse DNS naming convention. */ +} isom_keys_entry_t; + +/* Meaning Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t *meaning_string; /* to fill the box */ + + uint32_t meaning_string_length; +} isom_mean_t; + +/* Name Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t *name; /* to fill the box */ + + uint32_t name_length; +} isom_name_t; + +/* Data Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* type indicator */ + uint16_t reserved; /* always 0 */ + uint8_t type_set_identifier; /* 0: type set of the common basic data types */ + uint8_t type_code; /* type of data code */ + /* */ + uint32_t the_locale; /* reserved to be 0 */ + uint8_t *value; /* to fill the box */ + + uint32_t value_length; +} isom_data_t; + +/* Metadata Item Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mean_t *mean; /* Meaning Box */ + isom_name_t *name; /* Name Box */ + isom_data_t *data; /* Data Box */ +} isom_metaitem_t; + +/* Metadata Item List Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t *item_list; /* Metadata Item Box List + * There is no entry_count field. */ +} isom_ilst_t; + +/* Meta Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* ISOM: FullBox / QTFF: BaseBox */ + isom_hdlr_t *hdlr; /* Metadata Handler Reference Box */ + isom_dinf_t *dinf; /* ISOM: Data Information Box / QTFF: null */ + isom_keys_t *keys; /* ISOM: null / QTFF: Metadata Item Keys Box */ + isom_ilst_t *ilst; /* Metadata Item List Box only defined in Apple MPEG-4 and QTFF */ +} isom_meta_t; + +/* Window Location Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* default window location for movie */ + uint16_t x; + uint16_t y; +} isom_WLOC_t; + +/* Looping Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t looping_mode; /* 0 for none, 1 for looping, 2 for palindromic looping */ +} isom_LOOP_t; + +/* Play Selection Only Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t selection_only; /* whether only the selected area of the movie should be played */ +} isom_SelO_t; + +/* Play All Frames Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t play_all_frames; /* whether all frames of video should be played, regardless of timing */ +} isom_AllF_t; + +/* Copyright Box + * The Copyright box contains a copyright declaration which applies to the entire presentation, + * when contained within the Movie Box, or, when contained in a track, to that entire track. + * There may be multiple copyright boxes using different language codes. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t language; /* ISO-639-2/T language codes. Most significant 1-bit is 0. + * Each character is packed as the difference between its ASCII value and 0x60. */ + uint8_t *notice; /* a null-terminated string in either UTF-8 or UTF-16 characters, giving a copyright notice. + * If UTF-16 is used, the string shall start with the BYTE ORDER MARK (0xFEFF), to distinguish it from a UTF-8 string. + * This mark does not form part of the final string. */ + uint32_t notice_length; +} isom_cprt_t; + +/* User Data Box + * This box is a container box for informative user-data. + * This user data is formatted as a set of boxes with more specific box types, which declare more precisely their content. + * QTFF: for historical reasons, this box is optionally terminated by a 32-bit integer set to 0. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_chpl_t *chpl; /* Chapter List Box */ + isom_meta_t *meta; /* Meta Box extended by Apple for iTunes movie */ + /* QuickTime user data */ + isom_WLOC_t *WLOC; /* Window Location Box */ + isom_LOOP_t *LOOP; /* Looping Box */ + isom_SelO_t *SelO; /* Play Selection Only Box */ + isom_AllF_t *AllF; /* Play All Frames Box */ + /* Copyright Box List */ + lsmash_entry_list_t *cprt_list; /* Copyright Boxes is defined in ISO Base Media and 3GPP file format */ +} isom_udta_t; + +/** Caches for handling tracks **/ +typedef struct +{ + uint64_t alloc; /* total buffer size for the pool */ + uint64_t size; /* total size of samples in the pool */ + uint32_t sample_count; /* number of samples in the pool */ + uint8_t *data; /* actual data of samples in the pool */ +} isom_sample_pool_t; + +typedef struct +{ + uint32_t chunk_number; /* chunk number */ + uint32_t sample_description_index; /* sample description index */ + uint64_t first_dts; /* the first DTS in chunk */ + isom_sample_pool_t *pool; /* samples pooled to interleave */ +} isom_chunk_t; + +typedef struct +{ + uint64_t dts; + uint64_t cts; + int32_t ctd_shift; +} isom_timestamp_t; + +typedef struct +{ + isom_group_assignment_entry_t *assignment; /* the address corresponding to the entry in Sample to Group Box */ + isom_group_assignment_entry_t *prev_assignment; /* the address of the previous assignment */ + isom_rap_entry_t *random_access; /* the address corresponding to the random access entry in Sample Group Description Box */ + uint8_t is_prev_rap; /* whether the previous sample is a random access point or not */ +} isom_rap_group_t; + +typedef struct +{ + isom_group_assignment_entry_t *assignment; /* the address corresponding to the entry in Sample to Group Box */ + uint32_t first_sample; /* the number of the first sample of the group */ + uint32_t recovery_point; /* the identifier necessary for the recovery from its starting point to be completed */ + uint8_t delimited; /* the flag if the sample_count is determined */ + uint8_t described; /* the flag if the group description is determined */ +} isom_roll_group_t; + +typedef struct +{ + lsmash_entry_list_t *pool; /* grouping pooled to delimit and describe */ +} isom_grouping_t; + +typedef struct +{ + uint8_t has_samples; + uint32_t traf_number; + uint32_t last_duration; /* the last sample duration in this track fragment */ + uint64_t largest_cts; /* the largest CTS in this track fragments */ +} isom_fragment_t; + +typedef struct +{ + uint8_t all_sync; /* if all samples are sync sample */ + isom_chunk_t chunk; + isom_timestamp_t timestamp; + isom_grouping_t roll; + isom_rap_group_t *rap; + isom_fragment_t *fragment; +} isom_cache_t; + +/** Movie Fragments Boxes **/ +/* Track Fragments Flags ('tf_flags') */ +typedef enum +{ + ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT = 0x000001, /* base_data_offset field exists. */ + ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002, /* sample_description_index field exists. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008, /* default_sample_duration field exists. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010, /* default_sample_size field exists. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020, /* default_sample_flags field exists. */ + ISOM_TF_FLAGS_DURATION_IS_EMPTY = 0x010000, /* There are no samples for this time interval. */ +} isom_tf_flags_code; + +/* Track Run Flags ('tr_flags') */ +typedef enum +{ + ISOM_TR_FLAGS_DATA_OFFSET_PRESENT = 0x000001, /* data_offset field exists. */ + ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT = 0x000004, /* first_sample_flags field exists. */ + ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT = 0x000100, /* sample_duration field exists. */ + ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT = 0x000200, /* sample_size field exists. */ + ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT = 0x000400, /* sample_flags field exists. */ + ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT = 0x000800, /* sample_composition_time_offset field exists. */ +} isom_tr_flags_code; + +/* Sample Flags */ +typedef struct +{ + unsigned reserved : 4; + /* The definition of the following fields is quite the same as Independent and Disposable Samples Box. */ + unsigned is_leading : 2; + unsigned sample_depends_on : 2; + unsigned sample_is_depended_on : 2; + unsigned sample_has_redundancy : 2; + /* */ + unsigned sample_padding_value : 3; /* the number of bits at the end of this sample */ + unsigned sample_is_non_sync_sample : 1; /* 0 value means this sample is sync sample. */ + uint16_t sample_degradation_priority; +} isom_sample_flags_t; + +/* Movie Extends Header Box + * This box is omitted when used in live streaming. + * If this box is not present, the overall duration must be computed by examining each fragment. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + /* version == 0: uint64_t -> uint32_t */ + uint64_t fragment_duration; /* the duration of the longest track, in the timescale indicated in the Movie Header Box, including movie fragments. */ +} isom_mehd_t; + +/* Track Extends Box + * This box sets up default values used by the movie fragments. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t track_ID; /* identifier of the track; this shall be the track ID of a track in the Movie Box */ + uint32_t default_sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + isom_sample_flags_t default_sample_flags; +} isom_trex_entry_t; + +/* Movie Extends Box + * This box warns readers that there might be Movie Fragment Boxes in this file. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mehd_t *mehd; /* Movie Extends Header Box / omitted when used in live streaming */ + lsmash_entry_list_t *trex_list; /* Track Extends Box */ + + uint64_t placeholder_pos; /* placeholder position for Movie Extends Header Box */ +} isom_mvex_t; + +/* Movie Fragment Header Box + * This box contains a sequence number, as a safety check. + * The sequence number 'usually' starts at 1 and must increase for each movie fragment in the file, in the order in which they occur. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t sequence_number; /* the ordinal number of this fragment, in increasing order */ +} isom_mfhd_t; + +/* Track Fragment Header Box + * Each movie fragment can contain zero or more fragments for each track; + * and a track fragment can contain zero or more contiguous runs of samples. + * This box sets up information and defaults used for those runs of samples. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags field is used for 'tf_flags'. */ + uint32_t track_ID; + /* all the following are optional fields */ + uint64_t base_data_offset; /* an explicit anchor for the data offsets in each track run + * Offsets are file offsets as like as chunk_offset in Chunk Offset Box. + * If not provided, the base_data_offset for the first track in the movie fragment is the position + * of the first byte of the enclosing Movie Fragment Box, and for second and subsequent track fragments, + * the default is the end of the data defined by the preceding fragment. + * To avoid the case this field might overflow, e.g. semi-permanent live streaming and broadcasting, + * you shall not use this optional field. */ + uint32_t sample_description_index; /* override default_sample_description_index in Track Extends Box */ + uint32_t default_sample_duration; /* override default_sample_duration in Track Extends Box */ + uint32_t default_sample_size; /* override default_sample_size in Track Extends Box */ + isom_sample_flags_t default_sample_flags; /* override default_sample_flags in Track Extends Box */ +} isom_tfhd_t; + +/* Track Fragment Run Box + * Within the Track Fragment Box, there are zero or more Track Fragment Run Boxes. + * If the duration-is-empty flag is set in the tf_flags, there are no track runs. + * A track run documents a contiguous set of samples for a track. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags field is used for 'tr_flags'. */ + uint32_t sample_count; /* the number of samples being added in this run; also the number of rows in the following table */ + /* The following are optional fields. */ + int32_t data_offset; /* This value is added to the implicit or explicit data_offset established in the Track Fragment Header Box. + * If this field is not present, then the data for this run starts immediately after the data of the previous run, + * or at the base_data_offset defined by the Track Fragment Header Box if this is the first run in a track fragment. */ + isom_sample_flags_t first_sample_flags; /* a set of flags for the first sample only of this run */ + lsmash_entry_list_t *optional; /* all fields in this array are optional. */ +} isom_trun_entry_t; + +typedef struct +{ + /* If the following fields is present, each field overrides default value described in Track Fragment Header Box or Track Extends Box. */ + uint32_t sample_duration; /* override default_sample_duration */ + uint32_t sample_size; /* override default_sample_size */ + isom_sample_flags_t sample_flags; /* override default_sample_flags */ + /* */ + uint32_t sample_composition_time_offset; /* composition time offset */ +} isom_trun_optional_row_t; + +/* Track Fragment Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_tfhd_t *tfhd; /* Track Fragment Header Box */ + lsmash_entry_list_t *trun_list; /* Track Fragment Run Box List + * If the duration-is-empty flag is set in the tf_flags, there are no track runs. */ + + isom_cache_t *cache; +} isom_traf_entry_t; + +/* Movie Fragment Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mfhd_t *mfhd; /* Movie Fragment Header Box */ + lsmash_entry_list_t *traf_list; /* Track Fragment Box List */ +} isom_moof_entry_t; + +/* Track Fragment Random Access Box + * Each entry in this box contains the location and the presentation time of the random accessible sample. + * Note that not every random accessible sample in the track needs to be listed in the table. + * The absence of this box does not mean that all the samples are sync samples. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t track_ID; + unsigned int reserved : 26; + unsigned int length_size_of_traf_num : 2; /* the length in byte of the traf_number field minus one */ + unsigned int length_size_of_trun_num : 2; /* the length in byte of the trun_number field minus one */ + unsigned int length_size_of_sample_num : 2; /* the length in byte of the sample_number field minus one */ + uint32_t number_of_entry; /* the number of the entries for this track + * Value zero indicates that every sample is a random access point and no table entry follows. */ + lsmash_entry_list_t *list; /* entry_count corresponds to number_of_entry. */ +} isom_tfra_entry_t; + +typedef struct +{ + /* version == 0: 64bits -> 32bits */ + uint64_t time; /* the presentation time of the random access sample in units defined in the Media Header Box of the associated track + * According to 14496-12:2008/FPDAM 3, presentation times are composition times. */ + uint64_t moof_offset; /* the offset of the Movie Fragment Box used in this entry + * Offset is the byte-offset between the beginning of the file and the beginning of the Movie Fragment Box. */ + /* */ + uint32_t traf_number; /* the Track Fragment Box ('traf') number that contains the random accessible sample + * The number ranges from 1 in each Movie Fragment Box ('moof'). */ + uint32_t trun_number; /* the Track Fragment Run Box ('trun') number that contains the random accessible sample + * The number ranges from 1 in each Track Fragment Box ('traf'). */ + uint32_t sample_number; /* the sample number that contains the random accessible sample + * The number ranges from 1 in each Track Fragment Run Box ('trun'). */ +} isom_tfra_location_time_entry_t; + +/* Movie Fragment Random Access Offset Box + * This box provides a copy of the length field from the enclosing Movie Fragment Random Access Box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t length; /* an integer gives the number of bytes of the enclosing Movie Fragment Random Access Box + * This field is placed at the last of the enclosing box to assist readers scanning + * from the end of the file in finding the Movie Fragment Random Access Box. */ +} isom_mfro_t; + +/* Movie Fragment Random Access Box + * This box provides a table which may assist readers in finding random access points in a file using movie fragments, + * and is usually placed at or near the end of the file. + * The last box within the Movie Fragment Random Access Box, which is called Movie Fragment Random Access Offset Box, + * provides a copy of the length field from the Movie Fragment Random Access Box. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t *tfra_list; /* Track Fragment Random Access Box */ + isom_mfro_t *mfro; /* Movie Fragment Random Access Offset Box */ +} isom_mfra_t; + +/* Movie fragment manager + * The presence of this means we use the structure of movie fragments. */ +typedef struct +{ + isom_moof_entry_t *movie; /* the address corresponding to the current Movie Fragment Box */ + uint64_t fragment_count; /* the number of movie fragments we created */ + uint64_t pool_size; + lsmash_entry_list_t *pool; /* samples pooled to interleave for the current movie fragment */ +} isom_fragment_manager_t; + +/** **/ + +/* Movie Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mvhd_t *mvhd; /* Movie Header Box */ + isom_iods_t *iods; /* ISOM: Object Descriptor Box / QTFF: null */ + lsmash_entry_list_t *trak_list; /* Track Box List */ + isom_udta_t *udta; /* User Data Box */ + isom_meta_t *meta; /* Meta Box */ + isom_mvex_t *mvex; /* Movie Extends Box */ +} isom_moov_t; + +/* ROOT */ +struct lsmash_root_tag +{ + ISOM_FULLBOX_COMMON; /* the size field expresses total file size + * the flags field expresses file mode */ + isom_ftyp_t *ftyp; /* File Type Box */ + isom_moov_t *moov; /* Movie Box */ + lsmash_entry_list_t *moof_list; /* Movie Fragment Box List */ + isom_mdat_t *mdat; /* Media Data Box */ + isom_free_t *free; /* Free Space Box */ + isom_meta_t *meta; /* Meta Box */ + isom_mfra_t *mfra; /* Movie Fragment Random Access Box */ + + lsmash_bs_t *bs; /* bytestream manager */ + isom_fragment_manager_t *fragment; /* movie fragment manager */ + double max_chunk_duration; /* max duration per chunk in seconds */ + double max_async_tolerance; /* max tolerance, in seconds, for amount of interleaving asynchronization between tracks */ + uint64_t max_chunk_size; /* max size per chunk in bytes. */ + uint64_t max_read_size; /* max size of reading from a chunk at a time. */ + uint8_t file_type_written; /* whether File Type Box was written */ + uint8_t qt_compatible; /* compatibility with QuickTime file format */ + uint8_t isom_compatible; /* compatibility with ISO Base Media file format */ + uint8_t avc_extensions; /* compatibility with AVC extensions */ + uint8_t mp4_version1; /* compatibility with MP4 ver.1 file format */ + uint8_t mp4_version2; /* compatibility with MP4 ver.2 file format */ + uint8_t itunes_movie; /* compatibility with iTunes Movie */ + uint8_t max_3gpp_version; /* maximum 3GPP version */ + uint8_t max_isom_version; /* maximum ISO Base Media file format version */ + lsmash_entry_list_t *print; + lsmash_entry_list_t *timeline; +}; + +/* Track Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_tkhd_t *tkhd; /* Track Header Box */ + isom_tapt_t *tapt; /* ISOM: null / QTFF: Track Aperture Mode Dimensions Box */ + isom_edts_t *edts; /* Edit Box */ + isom_tref_t *tref; /* Track Reference Box */ + isom_mdia_t *mdia; /* Media Box */ + isom_udta_t *udta; /* User Data Box */ + isom_meta_t *meta; /* Meta Box */ + + isom_cache_t *cache; + uint32_t related_track_ID; + uint8_t is_chapter; +} isom_trak_entry_t; +/** **/ + +/* Box types */ +enum isom_box_type +{ + ISOM_BOX_TYPE_ID32 = LSMASH_4CC( 'I', 'D', '3', '2' ), + ISOM_BOX_TYPE_ALBM = LSMASH_4CC( 'a', 'l', 'b', 'm' ), + ISOM_BOX_TYPE_AUTH = LSMASH_4CC( 'a', 'u', 't', 'h' ), + ISOM_BOX_TYPE_BPCC = LSMASH_4CC( 'b', 'p', 'c', 'c' ), + ISOM_BOX_TYPE_BUFF = LSMASH_4CC( 'b', 'u', 'f', 'f' ), + ISOM_BOX_TYPE_BXML = LSMASH_4CC( 'b', 'x', 'm', 'l' ), + ISOM_BOX_TYPE_CCID = LSMASH_4CC( 'c', 'c', 'i', 'd' ), + ISOM_BOX_TYPE_CDEF = LSMASH_4CC( 'c', 'd', 'e', 'f' ), + ISOM_BOX_TYPE_CLSF = LSMASH_4CC( 'c', 'l', 's', 'f' ), + ISOM_BOX_TYPE_CMAP = LSMASH_4CC( 'c', 'm', 'a', 'p' ), + ISOM_BOX_TYPE_CO64 = LSMASH_4CC( 'c', 'o', '6', '4' ), + ISOM_BOX_TYPE_COLR = LSMASH_4CC( 'c', 'o', 'l', 'r' ), + ISOM_BOX_TYPE_CPRT = LSMASH_4CC( 'c', 'p', 'r', 't' ), + ISOM_BOX_TYPE_CSLG = LSMASH_4CC( 'c', 's', 'l', 'g' ), + ISOM_BOX_TYPE_CTTS = LSMASH_4CC( 'c', 't', 't', 's' ), + ISOM_BOX_TYPE_CVRU = LSMASH_4CC( 'c', 'v', 'r', 'u' ), + ISOM_BOX_TYPE_DCFD = LSMASH_4CC( 'd', 'c', 'f', 'D' ), + ISOM_BOX_TYPE_DINF = LSMASH_4CC( 'd', 'i', 'n', 'f' ), + ISOM_BOX_TYPE_DREF = LSMASH_4CC( 'd', 'r', 'e', 'f' ), + ISOM_BOX_TYPE_DSCP = LSMASH_4CC( 'd', 's', 'c', 'p' ), + ISOM_BOX_TYPE_DSGD = LSMASH_4CC( 'd', 's', 'g', 'd' ), + ISOM_BOX_TYPE_DSTG = LSMASH_4CC( 'd', 's', 't', 'g' ), + ISOM_BOX_TYPE_EDTS = LSMASH_4CC( 'e', 'd', 't', 's' ), + ISOM_BOX_TYPE_ELST = LSMASH_4CC( 'e', 'l', 's', 't' ), + ISOM_BOX_TYPE_FECI = LSMASH_4CC( 'f', 'e', 'c', 'i' ), + ISOM_BOX_TYPE_FECR = LSMASH_4CC( 'f', 'e', 'c', 'r' ), + ISOM_BOX_TYPE_FIIN = LSMASH_4CC( 'f', 'i', 'i', 'n' ), + ISOM_BOX_TYPE_FIRE = LSMASH_4CC( 'f', 'i', 'r', 'e' ), + ISOM_BOX_TYPE_FPAR = LSMASH_4CC( 'f', 'p', 'a', 'r' ), + ISOM_BOX_TYPE_FREE = LSMASH_4CC( 'f', 'r', 'e', 'e' ), + ISOM_BOX_TYPE_FRMA = LSMASH_4CC( 'f', 'r', 'm', 'a' ), + ISOM_BOX_TYPE_FTYP = LSMASH_4CC( 'f', 't', 'y', 'p' ), + ISOM_BOX_TYPE_GITN = LSMASH_4CC( 'g', 'i', 't', 'n' ), + ISOM_BOX_TYPE_GNRE = LSMASH_4CC( 'g', 'n', 'r', 'e' ), + ISOM_BOX_TYPE_GRPI = LSMASH_4CC( 'g', 'r', 'p', 'i' ), + ISOM_BOX_TYPE_HDLR = LSMASH_4CC( 'h', 'd', 'l', 'r' ), + ISOM_BOX_TYPE_HMHD = LSMASH_4CC( 'h', 'm', 'h', 'd' ), + ISOM_BOX_TYPE_ICNU = LSMASH_4CC( 'i', 'c', 'n', 'u' ), + ISOM_BOX_TYPE_IDAT = LSMASH_4CC( 'i', 'd', 'a', 't' ), + ISOM_BOX_TYPE_IHDR = LSMASH_4CC( 'i', 'h', 'd', 'r' ), + ISOM_BOX_TYPE_IINF = LSMASH_4CC( 'i', 'i', 'n', 'f' ), + ISOM_BOX_TYPE_ILOC = LSMASH_4CC( 'i', 'l', 'o', 'c' ), + ISOM_BOX_TYPE_IMIF = LSMASH_4CC( 'i', 'm', 'i', 'f' ), + ISOM_BOX_TYPE_INFU = LSMASH_4CC( 'i', 'n', 'f', 'u' ), + ISOM_BOX_TYPE_IODS = LSMASH_4CC( 'i', 'o', 'd', 's' ), + ISOM_BOX_TYPE_IPHD = LSMASH_4CC( 'i', 'p', 'h', 'd' ), + ISOM_BOX_TYPE_IPMC = LSMASH_4CC( 'i', 'p', 'm', 'c' ), + ISOM_BOX_TYPE_IPRO = LSMASH_4CC( 'i', 'p', 'r', 'o' ), + ISOM_BOX_TYPE_IREF = LSMASH_4CC( 'i', 'r', 'e', 'f' ), + ISOM_BOX_TYPE_JP = LSMASH_4CC( 'j', 'p', ' ', ' ' ), + ISOM_BOX_TYPE_JP2C = LSMASH_4CC( 'j', 'p', '2', 'c' ), + ISOM_BOX_TYPE_JP2H = LSMASH_4CC( 'j', 'p', '2', 'h' ), + ISOM_BOX_TYPE_JP2I = LSMASH_4CC( 'j', 'p', '2', 'i' ), + ISOM_BOX_TYPE_KYWD = LSMASH_4CC( 'k', 'y', 'w', 'd' ), + ISOM_BOX_TYPE_LOCI = LSMASH_4CC( 'l', 'o', 'c', 'i' ), + ISOM_BOX_TYPE_LRCU = LSMASH_4CC( 'l', 'r', 'c', 'u' ), + ISOM_BOX_TYPE_MDAT = LSMASH_4CC( 'm', 'd', 'a', 't' ), + ISOM_BOX_TYPE_MDHD = LSMASH_4CC( 'm', 'd', 'h', 'd' ), + ISOM_BOX_TYPE_MDIA = LSMASH_4CC( 'm', 'd', 'i', 'a' ), + ISOM_BOX_TYPE_MDRI = LSMASH_4CC( 'm', 'd', 'r', 'i' ), + ISOM_BOX_TYPE_MECO = LSMASH_4CC( 'm', 'e', 'c', 'o' ), + ISOM_BOX_TYPE_MEHD = LSMASH_4CC( 'm', 'e', 'h', 'd' ), + ISOM_BOX_TYPE_M7HD = LSMASH_4CC( 'm', '7', 'h', 'd' ), + ISOM_BOX_TYPE_MERE = LSMASH_4CC( 'm', 'e', 'r', 'e' ), + ISOM_BOX_TYPE_META = LSMASH_4CC( 'm', 'e', 't', 'a' ), + ISOM_BOX_TYPE_MFHD = LSMASH_4CC( 'm', 'f', 'h', 'd' ), + ISOM_BOX_TYPE_MFRA = LSMASH_4CC( 'm', 'f', 'r', 'a' ), + ISOM_BOX_TYPE_MFRO = LSMASH_4CC( 'm', 'f', 'r', 'o' ), + ISOM_BOX_TYPE_MINF = LSMASH_4CC( 'm', 'i', 'n', 'f' ), + ISOM_BOX_TYPE_MJHD = LSMASH_4CC( 'm', 'j', 'h', 'd' ), + ISOM_BOX_TYPE_MOOF = LSMASH_4CC( 'm', 'o', 'o', 'f' ), + ISOM_BOX_TYPE_MOOV = LSMASH_4CC( 'm', 'o', 'o', 'v' ), + ISOM_BOX_TYPE_MVCG = LSMASH_4CC( 'm', 'v', 'c', 'g' ), + ISOM_BOX_TYPE_MVCI = LSMASH_4CC( 'm', 'v', 'c', 'i' ), + ISOM_BOX_TYPE_MVEX = LSMASH_4CC( 'm', 'v', 'e', 'x' ), + ISOM_BOX_TYPE_MVHD = LSMASH_4CC( 'm', 'v', 'h', 'd' ), + ISOM_BOX_TYPE_MVRA = LSMASH_4CC( 'm', 'v', 'r', 'a' ), + ISOM_BOX_TYPE_NMHD = LSMASH_4CC( 'n', 'm', 'h', 'd' ), + ISOM_BOX_TYPE_OCHD = LSMASH_4CC( 'o', 'c', 'h', 'd' ), + ISOM_BOX_TYPE_ODAF = LSMASH_4CC( 'o', 'd', 'a', 'f' ), + ISOM_BOX_TYPE_ODDA = LSMASH_4CC( 'o', 'd', 'd', 'a' ), + ISOM_BOX_TYPE_ODHD = LSMASH_4CC( 'o', 'd', 'h', 'd' ), + ISOM_BOX_TYPE_ODHE = LSMASH_4CC( 'o', 'd', 'h', 'e' ), + ISOM_BOX_TYPE_ODRB = LSMASH_4CC( 'o', 'd', 'r', 'b' ), + ISOM_BOX_TYPE_ODRM = LSMASH_4CC( 'o', 'd', 'r', 'm' ), + ISOM_BOX_TYPE_ODTT = LSMASH_4CC( 'o', 'd', 't', 't' ), + ISOM_BOX_TYPE_OHDR = LSMASH_4CC( 'o', 'h', 'd', 'r' ), + ISOM_BOX_TYPE_PADB = LSMASH_4CC( 'p', 'a', 'd', 'b' ), + ISOM_BOX_TYPE_PAEN = LSMASH_4CC( 'p', 'a', 'e', 'n' ), + ISOM_BOX_TYPE_PCLR = LSMASH_4CC( 'p', 'c', 'l', 'r' ), + ISOM_BOX_TYPE_PDIN = LSMASH_4CC( 'p', 'd', 'i', 'n' ), + ISOM_BOX_TYPE_PERF = LSMASH_4CC( 'p', 'e', 'r', 'f' ), + ISOM_BOX_TYPE_PITM = LSMASH_4CC( 'p', 'i', 't', 'm' ), + ISOM_BOX_TYPE_RES = LSMASH_4CC( 'r', 'e', 's', ' ' ), + ISOM_BOX_TYPE_RESC = LSMASH_4CC( 'r', 'e', 's', 'c' ), + ISOM_BOX_TYPE_RESD = LSMASH_4CC( 'r', 'e', 's', 'd' ), + ISOM_BOX_TYPE_RTNG = LSMASH_4CC( 'r', 't', 'n', 'g' ), + ISOM_BOX_TYPE_SBGP = LSMASH_4CC( 's', 'b', 'g', 'p' ), + ISOM_BOX_TYPE_SCHI = LSMASH_4CC( 's', 'c', 'h', 'i' ), + ISOM_BOX_TYPE_SCHM = LSMASH_4CC( 's', 'c', 'h', 'm' ), + ISOM_BOX_TYPE_SDEP = LSMASH_4CC( 's', 'd', 'e', 'p' ), + ISOM_BOX_TYPE_SDHD = LSMASH_4CC( 's', 'd', 'h', 'd' ), + ISOM_BOX_TYPE_SDTP = LSMASH_4CC( 's', 'd', 't', 'p' ), + ISOM_BOX_TYPE_SDVP = LSMASH_4CC( 's', 'd', 'v', 'p' ), + ISOM_BOX_TYPE_SEGR = LSMASH_4CC( 's', 'e', 'g', 'r' ), + ISOM_BOX_TYPE_SGPD = LSMASH_4CC( 's', 'g', 'p', 'd' ), + ISOM_BOX_TYPE_SINF = LSMASH_4CC( 's', 'i', 'n', 'f' ), + ISOM_BOX_TYPE_SKIP = LSMASH_4CC( 's', 'k', 'i', 'p' ), + ISOM_BOX_TYPE_SMHD = LSMASH_4CC( 's', 'm', 'h', 'd' ), + ISOM_BOX_TYPE_SRMB = LSMASH_4CC( 's', 'r', 'm', 'b' ), + ISOM_BOX_TYPE_SRMC = LSMASH_4CC( 's', 'r', 'm', 'c' ), + ISOM_BOX_TYPE_SRPP = LSMASH_4CC( 's', 'r', 'p', 'p' ), + ISOM_BOX_TYPE_STBL = LSMASH_4CC( 's', 't', 'b', 'l' ), + ISOM_BOX_TYPE_STCO = LSMASH_4CC( 's', 't', 'c', 'o' ), + ISOM_BOX_TYPE_STDP = LSMASH_4CC( 's', 't', 'd', 'p' ), + ISOM_BOX_TYPE_STSC = LSMASH_4CC( 's', 't', 's', 'c' ), + ISOM_BOX_TYPE_STSD = LSMASH_4CC( 's', 't', 's', 'd' ), + ISOM_BOX_TYPE_STSH = LSMASH_4CC( 's', 't', 's', 'h' ), + ISOM_BOX_TYPE_STSS = LSMASH_4CC( 's', 't', 's', 's' ), + ISOM_BOX_TYPE_STSZ = LSMASH_4CC( 's', 't', 's', 'z' ), + ISOM_BOX_TYPE_STTS = LSMASH_4CC( 's', 't', 't', 's' ), + ISOM_BOX_TYPE_STZ2 = LSMASH_4CC( 's', 't', 'z', '2' ), + ISOM_BOX_TYPE_SUBS = LSMASH_4CC( 's', 'u', 'b', 's' ), + ISOM_BOX_TYPE_SWTC = LSMASH_4CC( 's', 'w', 't', 'c' ), + ISOM_BOX_TYPE_TFHD = LSMASH_4CC( 't', 'f', 'h', 'd' ), + ISOM_BOX_TYPE_TFRA = LSMASH_4CC( 't', 'f', 'r', 'a' ), + ISOM_BOX_TYPE_TIBR = LSMASH_4CC( 't', 'i', 'b', 'r' ), + ISOM_BOX_TYPE_TIRI = LSMASH_4CC( 't', 'i', 'r', 'i' ), + ISOM_BOX_TYPE_TITL = LSMASH_4CC( 't', 'i', 't', 'l' ), + ISOM_BOX_TYPE_TKHD = LSMASH_4CC( 't', 'k', 'h', 'd' ), + ISOM_BOX_TYPE_TRAF = LSMASH_4CC( 't', 'r', 'a', 'f' ), + ISOM_BOX_TYPE_TRAK = LSMASH_4CC( 't', 'r', 'a', 'k' ), + ISOM_BOX_TYPE_TREF = LSMASH_4CC( 't', 'r', 'e', 'f' ), + ISOM_BOX_TYPE_TREX = LSMASH_4CC( 't', 'r', 'e', 'x' ), + ISOM_BOX_TYPE_TRGR = LSMASH_4CC( 't', 'r', 'g', 'r' ), + ISOM_BOX_TYPE_TRUN = LSMASH_4CC( 't', 'r', 'u', 'n' ), + ISOM_BOX_TYPE_TSEL = LSMASH_4CC( 't', 's', 'e', 'l' ), + ISOM_BOX_TYPE_UDTA = LSMASH_4CC( 'u', 'd', 't', 'a' ), + ISOM_BOX_TYPE_UINF = LSMASH_4CC( 'u', 'i', 'n', 'f' ), + ISOM_BOX_TYPE_ULST = LSMASH_4CC( 'u', 'l', 's', 't' ), + ISOM_BOX_TYPE_URL = LSMASH_4CC( 'u', 'r', 'l', ' ' ), + ISOM_BOX_TYPE_URN = LSMASH_4CC( 'u', 'r', 'n', ' ' ), + ISOM_BOX_TYPE_UUID = LSMASH_4CC( 'u', 'u', 'i', 'd' ), + ISOM_BOX_TYPE_VMHD = LSMASH_4CC( 'v', 'm', 'h', 'd' ), + ISOM_BOX_TYPE_VWDI = LSMASH_4CC( 'v', 'w', 'd', 'i' ), + ISOM_BOX_TYPE_XML = LSMASH_4CC( 'x', 'm', 'l', ' ' ), + ISOM_BOX_TYPE_YRRC = LSMASH_4CC( 'y', 'r', 'r', 'c' ), + + ISOM_BOX_TYPE_BTRT = LSMASH_4CC( 'b', 't', 'r', 't' ), + ISOM_BOX_TYPE_CLAP = LSMASH_4CC( 'c', 'l', 'a', 'p' ), + ISOM_BOX_TYPE_PASP = LSMASH_4CC( 'p', 'a', 's', 'p' ), + ISOM_BOX_TYPE_STSL = LSMASH_4CC( 's', 't', 's', 'l' ), + + ISOM_BOX_TYPE_FTAB = LSMASH_4CC( 'f', 't', 'a', 'b' ), + + ISOM_BOX_TYPE_DATA = LSMASH_4CC( 'd', 'a', 't', 'a' ), + ISOM_BOX_TYPE_ILST = LSMASH_4CC( 'i', 'l', 's', 't' ), + ISOM_BOX_TYPE_MEAN = LSMASH_4CC( 'm', 'e', 'a', 'n' ), + ISOM_BOX_TYPE_NAME = LSMASH_4CC( 'n', 'a', 'm', 'e' ), + + ISOM_BOX_TYPE_CHPL = LSMASH_4CC( 'c', 'h', 'p', 'l' ), + + /* Decoder Specific Info */ + ISOM_BOX_TYPE_ALAC = LSMASH_4CC( 'a', 'l', 'a', 'c' ), + ISOM_BOX_TYPE_AVCC = LSMASH_4CC( 'a', 'v', 'c', 'C' ), + ISOM_BOX_TYPE_DAC3 = LSMASH_4CC( 'd', 'a', 'c', '3' ), + ISOM_BOX_TYPE_DAMR = LSMASH_4CC( 'd', 'a', 'm', 'r' ), + ISOM_BOX_TYPE_DEC3 = LSMASH_4CC( 'd', 'e', 'c', '3' ), + ISOM_BOX_TYPE_DVC1 = LSMASH_4CC( 'd', 'v', 'c', '1' ), + ISOM_BOX_TYPE_ESDS = LSMASH_4CC( 'e', 's', 'd', 's' ), +}; + +enum qt_box_type +{ + QT_BOX_TYPE_ALAC = LSMASH_4CC( 'a', 'l', 'a', 'c' ), + QT_BOX_TYPE_ALLF = LSMASH_4CC( 'A', 'l', 'l', 'F' ), + QT_BOX_TYPE_CHAN = LSMASH_4CC( 'c', 'h', 'a', 'n' ), + QT_BOX_TYPE_CLEF = LSMASH_4CC( 'c', 'l', 'e', 'f' ), + QT_BOX_TYPE_CLIP = LSMASH_4CC( 'c', 'l', 'i', 'p' ), + QT_BOX_TYPE_COLR = LSMASH_4CC( 'c', 'o', 'l', 'r' ), + QT_BOX_TYPE_CRGN = LSMASH_4CC( 'c', 'r', 'g', 'n' ), + QT_BOX_TYPE_CTAB = LSMASH_4CC( 'c', 't', 'a', 'b' ), + QT_BOX_TYPE_ENDA = LSMASH_4CC( 'e', 'n', 'd', 'a' ), + QT_BOX_TYPE_ENOF = LSMASH_4CC( 'e', 'n', 'o', 'f' ), + QT_BOX_TYPE_FRMA = LSMASH_4CC( 'f', 'r', 'm', 'a' ), + QT_BOX_TYPE_GMHD = LSMASH_4CC( 'g', 'm', 'h', 'd' ), + QT_BOX_TYPE_GMIN = LSMASH_4CC( 'g', 'm', 'i', 'n' ), + QT_BOX_TYPE_IMAP = LSMASH_4CC( 'i', 'm', 'a', 'p' ), + QT_BOX_TYPE_KEYS = LSMASH_4CC( 'k', 'e', 'y', 's' ), + QT_BOX_TYPE_KMAT = LSMASH_4CC( 'k', 'm', 'a', 't' ), + QT_BOX_TYPE_LOAD = LSMASH_4CC( 'l', 'o', 'a', 'd' ), + QT_BOX_TYPE_LOOP = LSMASH_4CC( 'L', 'O', 'O', 'P' ), + QT_BOX_TYPE_MATT = LSMASH_4CC( 'm', 'a', 't', 't' ), + QT_BOX_TYPE_META = LSMASH_4CC( 'm', 'e', 't', 'a' ), + QT_BOX_TYPE_MP4A = LSMASH_4CC( 'm', 'p', '4', 'a' ), + QT_BOX_TYPE_PNOT = LSMASH_4CC( 'p', 'n', 'o', 't' ), + QT_BOX_TYPE_PROF = LSMASH_4CC( 'p', 'r', 'o', 'f' ), + QT_BOX_TYPE_SELO = LSMASH_4CC( 'S', 'e', 'l', 'O' ), + QT_BOX_TYPE_STPS = LSMASH_4CC( 's', 't', 'p', 's' ), + QT_BOX_TYPE_TAPT = LSMASH_4CC( 't', 'a', 'p', 't' ), + QT_BOX_TYPE_TEXT = LSMASH_4CC( 't', 'e', 'x', 't' ), + QT_BOX_TYPE_WAVE = LSMASH_4CC( 'w', 'a', 'v', 'e' ), + QT_BOX_TYPE_WLOC = LSMASH_4CC( 'W', 'L', 'O', 'C' ), + + QT_BOX_TYPE_TERMINATOR = 0x00000000, +}; + +/* Track reference types */ +typedef enum +{ + ISOM_TREF_TYPE_AVCP = LSMASH_4CC( 'a', 'v', 'c', 'p' ), /* AVC parameter set stream link */ + ISOM_TREF_TYPE_CDSC = LSMASH_4CC( 'c', 'd', 's', 'c' ), /* This track describes the referenced track. */ + ISOM_TREF_TYPE_DPND = LSMASH_4CC( 'd', 'p', 'n', 'd' ), /* This track has an MPEG-4 dependency on the referenced track. */ + ISOM_TREF_TYPE_HIND = LSMASH_4CC( 'h', 'i', 'n', 'd' ), /* Hint dependency */ + ISOM_TREF_TYPE_HINT = LSMASH_4CC( 'h', 'i', 'n', 't' ), /* Links hint track to original media track */ + ISOM_TREF_TYPE_IPIR = LSMASH_4CC( 'i', 'p', 'i', 'r' ), /* This track contains IPI declarations for the referenced track. */ + ISOM_TREF_TYPE_MPOD = LSMASH_4CC( 'm', 'p', 'o', 'd' ), /* This track is an OD track which uses the referenced track as an included elementary stream track. */ + ISOM_TREF_TYPE_SBAS = LSMASH_4CC( 's', 'b', 'a', 's' ), /* Scalable base */ + ISOM_TREF_TYPE_SCAL = LSMASH_4CC( 's', 'c', 'a', 'l' ), /* Scalable extraction */ + ISOM_TREF_TYPE_SWFR = LSMASH_4CC( 's', 'w', 'f', 'r' ), /* AVC Switch from */ + ISOM_TREF_TYPE_SWTO = LSMASH_4CC( 's', 'w', 't', 'o' ), /* AVC Switch to */ + ISOM_TREF_TYPE_SYNC = LSMASH_4CC( 's', 'y', 'n', 'c' ), /* This track uses the referenced track as its synchronization source. */ + ISOM_TREF_TYPE_VDEP = LSMASH_4CC( 'v', 'd', 'e', 'p' ), /* Auxiliary video depth */ + ISOM_TREF_TYPE_VPLX = LSMASH_4CC( 'v', 'p', 'l', 'x' ), /* Auxiliary video parallax */ + + QT_TREF_TYPE_CHAP = LSMASH_4CC( 'c', 'h', 'a', 'p' ), /* Chapter or scene list. Usually references a text track. */ + QT_TREF_TYPE_SCPT = LSMASH_4CC( 's', 'c', 'p', 't' ), /* Transcript. Usually references a text track. */ + QT_TREF_TYPE_SSRC = LSMASH_4CC( 's', 's', 'r', 'c' ), /* Nonprimary source. Indicates that the referenced track should send its data to this track, rather than presenting it. */ + QT_TREF_TYPE_TMCD = LSMASH_4CC( 't', 'm', 'c', 'd' ), /* Time code. Usually references a time code track. */ +} isom_track_reference_type; + +/* Handler types */ +enum isom_handler_type +{ + QT_HANDLER_TYPE_DATA = LSMASH_4CC( 'd', 'h', 'l', 'r' ), + QT_HANDLER_TYPE_MEDIA = LSMASH_4CC( 'm', 'h', 'l', 'r' ), +}; + +enum isom_meta_type +{ + ISOM_META_HANDLER_TYPE_ITUNES_METADATA = LSMASH_4CC( 'm', 'd', 'i', 'r' ), +}; + +/* Data reference types */ +enum isom_data_reference_type +{ + ISOM_REFERENCE_HANDLER_TYPE_URL = LSMASH_4CC( 'u', 'r', 'l', ' ' ), + ISOM_REFERENCE_HANDLER_TYPE_URN = LSMASH_4CC( 'u', 'r', 'n', ' ' ), + + QT_REFERENCE_HANDLER_TYPE_ALIAS = LSMASH_4CC( 'a', 'l', 'i', 's' ), + QT_REFERENCE_HANDLER_TYPE_RESOURCE = LSMASH_4CC( 'r', 's', 'r', 'c' ), + QT_REFERENCE_HANDLER_TYPE_URL = LSMASH_4CC( 'u', 'r', 'l', ' ' ), +}; + +/* Lanuage codes */ +typedef struct +{ + uint16_t mac_value; + uint16_t iso_name; +} isom_language_t; + +static const isom_language_t isom_languages[] = +{ + { 0, ISOM_LANGUAGE_CODE_ENGLISH }, + { 1, ISOM_LANGUAGE_CODE_FRENCH }, + { 2, ISOM_LANGUAGE_CODE_GERMAN }, + { 3, ISOM_LANGUAGE_CODE_ITALIAN }, + { 4, ISOM_LANGUAGE_CODE_DUTCH_M }, + { 5, ISOM_LANGUAGE_CODE_SWEDISH }, + { 6, ISOM_LANGUAGE_CODE_SPANISH }, + { 7, ISOM_LANGUAGE_CODE_DANISH }, + { 8, ISOM_LANGUAGE_CODE_PORTUGUESE }, + { 9, ISOM_LANGUAGE_CODE_NORWEGIAN }, + { 10, ISOM_LANGUAGE_CODE_HEBREW }, + { 11, ISOM_LANGUAGE_CODE_JAPANESE }, + { 12, ISOM_LANGUAGE_CODE_ARABIC }, + { 13, ISOM_LANGUAGE_CODE_FINNISH }, + { 14, ISOM_LANGUAGE_CODE_GREEK }, + { 15, ISOM_LANGUAGE_CODE_ICELANDIC }, + { 16, ISOM_LANGUAGE_CODE_MALTESE }, + { 17, ISOM_LANGUAGE_CODE_TURKISH }, + { 18, ISOM_LANGUAGE_CODE_CROATIAN }, + { 19, ISOM_LANGUAGE_CODE_CHINESE }, + { 20, ISOM_LANGUAGE_CODE_URDU }, + { 21, ISOM_LANGUAGE_CODE_HINDI }, + { 22, ISOM_LANGUAGE_CODE_THAI }, + { 23, ISOM_LANGUAGE_CODE_KOREAN }, + { 24, ISOM_LANGUAGE_CODE_LITHUANIAN }, + { 25, ISOM_LANGUAGE_CODE_POLISH }, + { 26, ISOM_LANGUAGE_CODE_HUNGARIAN }, + { 27, ISOM_LANGUAGE_CODE_ESTONIAN }, + { 28, ISOM_LANGUAGE_CODE_LATVIAN }, + { 29, ISOM_LANGUAGE_CODE_SAMI }, + { 30, ISOM_LANGUAGE_CODE_FAROESE }, + { 32, ISOM_LANGUAGE_CODE_RUSSIAN }, + { 33, ISOM_LANGUAGE_CODE_CHINESE }, + { 34, ISOM_LANGUAGE_CODE_DUTCH }, + { 35, ISOM_LANGUAGE_CODE_IRISH }, + { 36, ISOM_LANGUAGE_CODE_ALBANIAN }, + { 37, ISOM_LANGUAGE_CODE_ROMANIAN }, + { 38, ISOM_LANGUAGE_CODE_CZECH }, + { 39, ISOM_LANGUAGE_CODE_SLOVAK }, + { 40, ISOM_LANGUAGE_CODE_SLOVENIA }, + { 41, ISOM_LANGUAGE_CODE_YIDDISH }, + { 42, ISOM_LANGUAGE_CODE_SERBIAN }, + { 43, ISOM_LANGUAGE_CODE_MACEDONIAN }, + { 44, ISOM_LANGUAGE_CODE_BULGARIAN }, + { 45, ISOM_LANGUAGE_CODE_UKRAINIAN }, + { 46, ISOM_LANGUAGE_CODE_BELARUSIAN }, + { 47, ISOM_LANGUAGE_CODE_UZBEK }, + { 48, ISOM_LANGUAGE_CODE_KAZAKH }, + { 49, ISOM_LANGUAGE_CODE_AZERBAIJANI }, + { 51, ISOM_LANGUAGE_CODE_ARMENIAN }, + { 52, ISOM_LANGUAGE_CODE_GEORGIAN }, + { 53, ISOM_LANGUAGE_CODE_MOLDAVIAN }, + { 54, ISOM_LANGUAGE_CODE_KIRGHIZ }, + { 55, ISOM_LANGUAGE_CODE_TAJIK }, + { 56, ISOM_LANGUAGE_CODE_TURKMEN }, + { 57, ISOM_LANGUAGE_CODE_MONGOLIAN }, + { 59, ISOM_LANGUAGE_CODE_PASHTO }, + { 60, ISOM_LANGUAGE_CODE_KURDISH }, + { 61, ISOM_LANGUAGE_CODE_KASHMIRI }, + { 62, ISOM_LANGUAGE_CODE_SINDHI }, + { 63, ISOM_LANGUAGE_CODE_TIBETAN }, + { 64, ISOM_LANGUAGE_CODE_NEPALI }, + { 65, ISOM_LANGUAGE_CODE_SANSKRIT }, + { 66, ISOM_LANGUAGE_CODE_MARATHI }, + { 67, ISOM_LANGUAGE_CODE_BENGALI }, + { 68, ISOM_LANGUAGE_CODE_ASSAMESE }, + { 69, ISOM_LANGUAGE_CODE_GUJARATI }, + { 70, ISOM_LANGUAGE_CODE_PUNJABI }, + { 71, ISOM_LANGUAGE_CODE_ORIYA }, + { 72, ISOM_LANGUAGE_CODE_MALAYALAM }, + { 73, ISOM_LANGUAGE_CODE_KANNADA }, + { 74, ISOM_LANGUAGE_CODE_TAMIL }, + { 75, ISOM_LANGUAGE_CODE_TELUGU }, + { 76, ISOM_LANGUAGE_CODE_SINHALESE }, + { 77, ISOM_LANGUAGE_CODE_BURMESE }, + { 78, ISOM_LANGUAGE_CODE_KHMER }, + { 79, ISOM_LANGUAGE_CODE_LAO }, + { 80, ISOM_LANGUAGE_CODE_VIETNAMESE }, + { 81, ISOM_LANGUAGE_CODE_INDONESIAN }, + { 82, ISOM_LANGUAGE_CODE_TAGALOG }, + { 83, ISOM_LANGUAGE_CODE_MALAY_ROMAN }, + { 84, ISOM_LANGUAGE_CODE_MAYAY_ARABIC }, + { 85, ISOM_LANGUAGE_CODE_AMHARIC }, + { 87, ISOM_LANGUAGE_CODE_OROMO }, + { 88, ISOM_LANGUAGE_CODE_SOMALI }, + { 89, ISOM_LANGUAGE_CODE_SWAHILI }, + { 90, ISOM_LANGUAGE_CODE_KINYARWANDA }, + { 91, ISOM_LANGUAGE_CODE_RUNDI }, + { 92, ISOM_LANGUAGE_CODE_CHEWA }, + { 93, ISOM_LANGUAGE_CODE_MALAGASY }, + { 94, ISOM_LANGUAGE_CODE_ESPERANTO }, + { 128, ISOM_LANGUAGE_CODE_WELSH }, + { 129, ISOM_LANGUAGE_CODE_BASQUE }, + { 130, ISOM_LANGUAGE_CODE_CATALAN }, + { 131, ISOM_LANGUAGE_CODE_LATIN }, + { 132, ISOM_LANGUAGE_CODE_QUECHUA }, + { 133, ISOM_LANGUAGE_CODE_GUARANI }, + { 134, ISOM_LANGUAGE_CODE_AYMARA }, + { 135, ISOM_LANGUAGE_CODE_TATAR }, + { 136, ISOM_LANGUAGE_CODE_UIGHUR }, + { 137, ISOM_LANGUAGE_CODE_DZONGKHA }, + { 138, ISOM_LANGUAGE_CODE_JAVANESE }, + { UINT16_MAX, 0 } +}; + +/* Color parameters */ +typedef struct +{ + uint16_t primaries; + uint16_t transfer; + uint16_t matrix; +} isom_color_parameter_t; + +static const isom_color_parameter_t isom_color_parameter_tbl[] = +{ + { 2, 2, 2 }, /* Not specified */ + { 2, 2, 2 }, /* ITU-R BT.470 System M */ + { 5, 2, 6 }, /* ITU-R BT.470 System B, G */ + { 1, 1, 1 }, /* ITU-R BT.709 */ + { 6, 1, 6 }, /* SMPTE 170M */ + { 6, 7, 7 }, /* SMPTE 240M */ + { 1, 1, 1 }, /* SMPTE 274M */ + { 5, 1, 6 }, /* SMPTE 293M */ + { 1, 1, 1 }, /* SMPTE 296M */ +}; + +enum qt_color_patameter_type +{ + QT_COLOR_PARAMETER_TYPE_NCLC = LSMASH_4CC( 'n', 'c', 'l', 'c' ), /* nonconstant luminance coding */ + QT_COLOR_PARAMETER_TYPE_PROF = LSMASH_4CC( 'p', 'r', 'o', 'f' ), /* ICC profile */ +}; + +/* QuickTime Audio flags */ +enum qt_compression_id +{ + QT_COMPRESSION_ID_NOT_COMPRESSED = 0, + QT_COMPRESSION_ID_FIXED_COMPRESSION = -1, + QT_COMPRESSION_ID_VARIABLE_COMPRESSION = -2, + QT_COMPRESSION_ID_TWO_TO_ONE = 1, + QT_COMPRESSION_ID_EIGHT_TO_THREE = 2, + QT_COMPRESSION_ID_THREE_TO_ONE = 3, + QT_COMPRESSION_ID_SIX_TO_ONE = 4, + QT_COMPRESSION_ID_SIX_TO_ONE_PACKET_SIZE = 8, + QT_COMPRESSION_ID_THREE_TO_ONE_PACKET_SIZE = 16, +}; + +enum qt_audio_format_flags +{ + QT_AUDIO_FORMAT_FLAG_FLOAT = 1, /* Set for floating point, clear for integer. */ + QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN = 1<<1, /* Set for big endian, clear for little endian. */ + QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER = 1<<2, /* Set for signed integer, clear for unsigned integer. + * This is only valid if QT_AUDIO_FORMAT_FLAG_FLOAT is clear. */ + QT_AUDIO_FORMAT_FLAG_PACKED = 1<<3, /* Set if the sample bits occupy the entire available bits for the channel, + * clear if they are high or low aligned within the channel. */ + QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH = 1<<4, /* Set if the sample bits are placed into the high bits of the channel, clear for low bit placement. + * This is only valid if QT_AUDIO_FORMAT_FLAG_PACKED is clear. */ + QT_AUDIO_FORMAT_FLAG_NON_INTERLEAVED = 1<<5, /* Set if the samples for each channel are located contiguously and the channels are layed out end to end, + * clear if the samples for each frame are layed out contiguously and the frames layed out end to end. */ + QT_AUDIO_FORMAT_FLAG_NON_MIXABLE = 1<<6, /* Set to indicate when a format is non-mixable. + * Note that this flag is only used when interacting with the HAL's stream format information. + * It is not a valid flag for any other uses. */ + QT_AUDIO_FORMAT_FLAG_ALL_CLEAR = 1<<31, /* Set if all the flags would be clear in order to preserve 0 as the wild card value. */ + + QT_LPCM_FORMAT_FLAG_FLOAT = QT_AUDIO_FORMAT_FLAG_FLOAT, + QT_LPCM_FORMAT_FLAG_BIG_ENDIAN = QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN, + QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER = QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER, + QT_LPCM_FORMAT_FLAG_PACKED = QT_AUDIO_FORMAT_FLAG_PACKED, + QT_LPCM_FORMAT_FLAG_ALIGNED_HIGH = QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH, + QT_LPCM_FORMAT_FLAG_NON_INTERLEAVED = QT_AUDIO_FORMAT_FLAG_NON_INTERLEAVED, + QT_LPCM_FORMAT_FLAG_NON_MIXABLE = QT_AUDIO_FORMAT_FLAG_NON_MIXABLE, + QT_LPCM_FORMAT_FLAG_ALL_CLEAR = QT_AUDIO_FORMAT_FLAG_ALL_CLEAR, + + /* These flags are set for Apple Lossless data that was sourced from N bit native endian signed integer data. */ + QT_ALAC_FORMAT_FLAG_16BIT_SOURCE_DATA = 1, + QT_ALAC_FORMAT_FLAG_20BIT_SOURCE_DATA = 2, + QT_ALAC_FORMAT_FLAG_24BIT_SOURCE_DATA = 3, + QT_ALAC_FORMAT_FLAG_32BIT_SOURCE_DATA = 4, +}; + +/* Sample grouping types */ +typedef enum +{ + ISOM_GROUP_TYPE_3GAG = LSMASH_4CC( '3', 'g', 'a', 'g' ), /* Text track3GPP PSS Annex G video buffer parameters */ + ISOM_GROUP_TYPE_ALST = LSMASH_4CC( 'a', 'l', 's', 't' ), /* Alternative startup sequence */ + ISOM_GROUP_TYPE_AVCB = LSMASH_4CC( 'a', 'v', 'c', 'b' ), /* AVC HRD parameters */ + ISOM_GROUP_TYPE_AVLL = LSMASH_4CC( 'a', 'v', 'l', 'l' ), /* AVC Layer */ + ISOM_GROUP_TYPE_AVSS = LSMASH_4CC( 'a', 'v', 's', 's' ), /* AVC Sub Sequence */ + ISOM_GROUP_TYPE_DTRT = LSMASH_4CC( 'd', 't', 'r', 't' ), /* Decode re-timing */ + ISOM_GROUP_TYPE_MVIF = LSMASH_4CC( 'm', 'v', 'i', 'f' ), /* MVC Scalability Information */ + ISOM_GROUP_TYPE_RAP = LSMASH_4CC( 'r', 'a', 'p', ' ' ), /* Random Access Point / This grouping type hasn't been published yet. */ + ISOM_GROUP_TYPE_RASH = LSMASH_4CC( 'r', 'a', 's', 'h' ), /* Rate Share */ + ISOM_GROUP_TYPE_ROLL = LSMASH_4CC( 'r', 'o', 'l', 'l' ), /* Random Access Recovery Point */ + ISOM_GROUP_TYPE_SCIF = LSMASH_4CC( 's', 'c', 'i', 'f' ), /* SVC Scalability Information */ + ISOM_GROUP_TYPE_SCNM = LSMASH_4CC( 's', 'c', 'n', 'm' ), /* AVC/SVC/MVC map groups */ + ISOM_GROUP_TYPE_VIPR = LSMASH_4CC( 'v', 'i', 'p', 'r' ), /* View priority */ +} isom_grouping_type; + +int isom_is_fullbox( void *box ); +int isom_is_lpcm_audio( uint32_t type ); + +void isom_init_box_common( void *box, void *parent, uint32_t type ); + +int isom_check_compatibility( lsmash_root_t *root ); + +char *isom_4cc2str( uint32_t fourcc ); + +isom_trak_entry_t *isom_get_trak( lsmash_root_t *root, uint32_t track_ID ); +isom_sgpd_entry_t *isom_get_sample_group_description( isom_stbl_t *stbl, uint32_t grouping_type ); +isom_sbgp_entry_t *isom_get_sample_to_group( isom_stbl_t *stbl, uint32_t grouping_type ); + +isom_avcC_ps_entry_t *isom_create_ps_entry( uint8_t *ps, uint32_t ps_size ); +void isom_remove_avcC_ps( isom_avcC_ps_entry_t *ps ); + +int isom_add_edts( isom_trak_entry_t *trak ); +int isom_add_elst( isom_edts_t *edts ); +int isom_add_clap( isom_visual_entry_t *visual ); +int isom_add_pasp( isom_visual_entry_t *visual ); +int isom_add_colr( isom_visual_entry_t *visual ); +int isom_add_stsl( isom_visual_entry_t *visual ); +int isom_add_avcC( isom_visual_entry_t *visual ); +int isom_add_btrt( isom_visual_entry_t *visual ); +int isom_add_wave( isom_audio_entry_t *audio ); +int isom_add_frma( isom_wave_t *wave ); +int isom_add_enda( isom_wave_t *wave ); +int isom_add_mp4a( isom_wave_t *wave ); +int isom_add_terminator( isom_wave_t *wave ); +int isom_add_chan( isom_audio_entry_t *audio ); +int isom_add_ftab( isom_tx3g_entry_t *tx3g ); + +void isom_remove_tapt( isom_tapt_t *tapt ); +void isom_remove_clap( isom_clap_t *clap ); +void isom_remove_pasp( isom_pasp_t *pasp ); +void isom_remove_colr( isom_colr_t *colr ); +void isom_remove_stsl( isom_stsl_t *stsl ); +void isom_remove_avcC( isom_avcC_t *avcC ); +void isom_remove_btrt( isom_btrt_t *btrt ); +void isom_remove_frma( isom_frma_t *frma ); +void isom_remove_enda( isom_enda_t *enda ); +void isom_remove_mp4a( isom_mp4a_t *mp4a ); +void isom_remove_terminator( isom_terminator_t *terminator ); +void isom_remove_wave( isom_wave_t *wave ); +void isom_remove_chan( isom_chan_t *chan ); +void isom_remove_ftab( isom_ftab_t *ftab ); +void isom_remove_sample_description( isom_sample_entry_t *sample ); + +#define isom_create_box( box_name, parent_name, box_4cc ) \ + isom_##box_name##_t *(box_name) = malloc( sizeof(isom_##box_name##_t) ); \ + if( !box_name ) \ + return -1; \ + memset( box_name, 0, sizeof(isom_##box_name##_t) ); \ + isom_init_box_common( box_name, parent_name, box_4cc ) + +#define isom_create_list_box( box_name, parent_name, box_4cc ) \ + isom_create_box( box_name, parent_name, box_4cc ); \ + box_name->list = lsmash_create_entry_list(); \ + if( !box_name->list ) \ + { \ + free( box_name ); \ + return -1; \ + } + +#define isom_copy_fields( dst, src, box_name ) \ + lsmash_root_t *root = dst->box_name->root; \ + isom_box_t *parent = dst->box_name->parent; \ + uint64_t pos = dst->box_name->pos; \ + *dst->box_name = *src->box_name; \ + dst->box_name->root = root; \ + dst->box_name->parent = parent; \ + dst->box_name->pos = pos + +#endif diff --git a/output/mp4/importer.c b/output/mp4/importer.c new file mode 100644 index 0000000..68be350 --- /dev/null +++ b/output/mp4/importer.c @@ -0,0 +1,5449 @@ +/***************************************************************************** + * importer.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include +#include +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +#include "mp4a.h" +#include "box.h" + +/*************************************************************************** + importer framework +***************************************************************************/ +struct mp4sys_importer_tag; + +typedef void ( *mp4sys_importer_cleanup ) ( struct mp4sys_importer_tag * ); +typedef int ( *mp4sys_importer_get_accessunit ) ( struct mp4sys_importer_tag *, uint32_t, lsmash_sample_t * ); +typedef int ( *mp4sys_importer_probe ) ( struct mp4sys_importer_tag * ); +typedef uint32_t ( *mp4sys_importer_get_last_duration )( struct mp4sys_importer_tag *, uint32_t ); + +typedef struct +{ + const char* name; + int detectable; + mp4sys_importer_probe probe; + mp4sys_importer_get_accessunit get_accessunit; + mp4sys_importer_get_last_duration get_last_delta; + mp4sys_importer_cleanup cleanup; +} mp4sys_importer_functions; + +typedef struct mp4sys_importer_tag +{ + FILE* stream; + int is_stdin; + void* info; /* importer internal status information. */ + mp4sys_importer_functions funcs; + lsmash_entry_list_t* summaries; +} mp4sys_importer_t; + +typedef enum +{ + MP4SYS_IMPORTER_ERROR = -1, + MP4SYS_IMPORTER_OK = 0, + MP4SYS_IMPORTER_CHANGE = 1, + MP4SYS_IMPORTER_EOF = 2, +} mp4sys_importer_status; + +/*************************************************************************** + ADTS importer +***************************************************************************/ +#define MP4SYS_ADTS_FIXED_HEADER_LENGTH 4 /* this is partly a lie. actually 28 bits. */ +#define MP4SYS_ADTS_BASIC_HEADER_LENGTH 7 +#define MP4SYS_ADTS_MAX_FRAME_LENGTH ( ( 1 << 13 ) - 1 ) +#define MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS 4 + +typedef struct +{ + uint16_t syncword; /* 12; */ + uint8_t ID; /* 1; */ + uint8_t layer; /* 2; */ + uint8_t protection_absent; /* 1; */ + uint8_t profile_ObjectType; /* 2; */ + uint8_t sampling_frequency_index; /* 4; */ +// uint8_t private_bit; /* 1; we don't care. */ + uint8_t channel_configuration; /* 3; */ +// uint8_t original_copy; /* 1; we don't care. */ +// uint8_t home; /* 1; we don't care. */ + +} mp4sys_adts_fixed_header_t; + +typedef struct +{ +// uint8_t copyright_identification_bit; /* 1; we don't care. */ +// uint8_t copyright_identification_start; /* 1; we don't care. */ + uint16_t frame_length; /* 13; */ +// uint16_t adts_buffer_fullness; /* 11; we don't care. */ + uint8_t number_of_raw_data_blocks_in_frame; /* 2; */ +// uint16_t adts_error_check; /* we don't support */ +// uint16_t raw_data_block_position[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS-1]; /* we don't use this directly, and... */ + uint16_t raw_data_block_size[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; /* use this instead of above. */ +// uint16_t adts_header_error_check; /* we don't support, actually crc_check within this */ +// uint16_t adts_raw_data_block_error_check[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; /* we don't support */ +} mp4sys_adts_variable_header_t; + +static void mp4sys_adts_parse_fixed_header( uint8_t* buf, mp4sys_adts_fixed_header_t* header ) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ + header->syncword = (buf[0] << 4) | (buf[1] >> 4); + header->ID = (buf[1] >> 3) & 0x1; + header->layer = (buf[1] >> 1) & 0x3; + header->protection_absent = buf[1] & 0x1; + header->profile_ObjectType = buf[2] >> 6; + header->sampling_frequency_index = (buf[2] >> 2) & 0xF; +// header->private_bit = (buf[2] >> 1) & 0x1; /* we don't care currently. */ + header->channel_configuration = ((buf[2] << 2) | (buf[3] >> 6)) & 0x07; +// header->original_copy = (buf[3] >> 5) & 0x1; /* we don't care currently. */ +// header->home = (buf[3] >> 4) & 0x1; /* we don't care currently. */ +} + +static int mp4sys_adts_check_fixed_header( mp4sys_adts_fixed_header_t* header ) +{ + if( header->syncword != 0xFFF ) return -1; +// if( header->ID != 0x0 ) return -1; /* we don't care. */ + if( header->layer != 0x0 ) return -1; /* must be 0b00 for any type of AAC */ +// if( header->protection_absent != 0x1 ) return -1; /* we don't care. */ + if( header->profile_ObjectType != 0x1 ) return -1; /* FIXME: 0b00=Main, 0b01=LC, 0b10=SSR, 0b11=LTP. */ + if( header->sampling_frequency_index > 0xB ) return -1; /* must not be > 0xB. */ + if( header->channel_configuration == 0x0 ) return -1; /* FIXME: we do not support 0b000 currently. */ + if( header->profile_ObjectType == 0x3 && header->ID != 0x0 ) return -1; /* LTP is valid only if ID==0. */ + return 0; +} + +static int mp4sys_adts_parse_variable_header( FILE* stream, uint8_t* buf, unsigned int protection_absent, mp4sys_adts_variable_header_t* header ) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ +// header->copyright_identification_bit = (buf[3] >> 3) & 0x1; /* we don't care. */ +// header->copyright_identification_start = (buf[3] >> 2) & 0x1; /* we don't care. */ + header->frame_length = ((buf[3] << 11) | (buf[4] << 3) | (buf[5] >> 5)) & 0x1FFF ; +// header->adts_buffer_fullness = ((buf[5] << 6) | (buf[6] >> 2)) 0x7FF ; /* we don't care. */ + header->number_of_raw_data_blocks_in_frame = buf[6] & 0x3; + + if( header->frame_length <= MP4SYS_ADTS_BASIC_HEADER_LENGTH + 2 * (protection_absent == 0) ) + return -1; /* easy error check */ + + /* protection_absent and number_of_raw_data_blocks_in_frame relatives */ + + uint8_t buf2[2]; + unsigned int number_of_blocks = header->number_of_raw_data_blocks_in_frame; + if( number_of_blocks == 0 ) + { + header->raw_data_block_size[0] = header->frame_length - MP4SYS_ADTS_BASIC_HEADER_LENGTH; + /* skip adts_error_check() and subtract that from block_size */ + if( protection_absent == 0 ) + { + header->raw_data_block_size[0] -= 2; + if( fread( buf2, 1, 2, stream ) != 2 ) + return -1; + } + return 0; + } + + /* now we have multiple raw_data_block()s, so evaluate adts_header_error_check() */ + + uint16_t raw_data_block_position[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; + uint16_t first_offset = MP4SYS_ADTS_BASIC_HEADER_LENGTH; + if( protection_absent == 0 ) + { + /* process adts_header_error_check() */ + for( int i = 0 ; i < number_of_blocks ; i++ ) /* 1-based in the spec, but we use 0-based */ + { + if( fread( buf2, 1, 2, stream ) != 2 ) + return -1; + raw_data_block_position[i] = (buf2[0] << 8) | buf2[1]; + } + /* skip crc_check in adts_header_error_check(). + Or might be sizeof( adts_error_check() ) if we share with the case number_of_raw_data_blocks_in_frame == 0 */ + if( fread( buf2, 1, 2, stream ) != 2 ) + return -1; + first_offset += ( 2 * number_of_blocks ) + 2; /* according to above */ + } + else + { + /* + * NOTE: We never support the case where number_of_raw_data_blocks_in_frame != 0 && protection_absent != 0, + * because we have to parse the raw AAC bitstream itself to find boundaries of raw_data_block()s in this case. + * Which is to say, that braindamaged spec requires us (mp4 muxer) to decode AAC once to split frames. + * L-SMASH is NOT AAC DECODER, so that we've just given up for this case. + * This is ISO/IEC 13818-7's sin which defines ADTS format originally. + */ + return -1; + } + + /* convert raw_data_block_position --> raw_data_block_size */ + + /* do conversion for first */ + header->raw_data_block_size[0] = raw_data_block_position[0] - first_offset; + /* set dummy offset to tail for loop, do coversion for rest. */ + raw_data_block_position[number_of_blocks] = header->frame_length; + for( int i = 1 ; i <= number_of_blocks ; i++ ) + header->raw_data_block_size[i] = raw_data_block_position[i] - raw_data_block_position[i-1]; + + /* adjustment for adts_raw_data_block_error_check() */ + if( protection_absent == 0 && number_of_blocks != 0 ) + for( int i = 0 ; i <= number_of_blocks ; i++ ) + header->raw_data_block_size[i] -= 2; + + return 0; +} + +static int mp4sys_adts_parse_headers( FILE* stream, uint8_t* buf, mp4sys_adts_fixed_header_t* header, mp4sys_adts_variable_header_t* variable_header ) +{ + mp4sys_adts_parse_fixed_header( buf, header ); + if( mp4sys_adts_check_fixed_header( header ) ) + return -1; + /* get payload length & skip extra(crc) header */ + return mp4sys_adts_parse_variable_header( stream, buf, header->protection_absent, variable_header ); +} + +static lsmash_audio_summary_t *mp4sys_adts_create_summary( mp4sys_adts_fixed_header_t *header ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return NULL; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + summary->max_au_length = MP4SYS_ADTS_MAX_FRAME_LENGTH; + summary->frequency = mp4a_sampling_frequency_table[header->sampling_frequency_index][1]; + summary->channels = header->channel_configuration + ( header->channel_configuration == 0x07 ); /* 0x07 means 7.1ch */ + summary->bit_depth = 16; + summary->samples_in_frame = 1024; + summary->aot = header->profile_ObjectType + MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; +#if 0 /* FIXME: This is very unstable. Many players crash with this. */ + if( header->ID != 0 ) + { + /* + * NOTE: This ADTS seems of ISO/IEC 13818-7 (MPEG-2 AAC). + * It has special object_type_indications, depending on it's profile (Legacy Interface). + * If ADIF header is not available, it should not have decoder specific information, so AudioObjectType neither. + * see ISO/IEC 14496-1, DecoderSpecificInfo and 14496-3 Subpart 9: MPEG-1/2 Audio in MPEG-4. + */ + summary->object_type_indication = header->profile_ObjectType + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; + summary->asc = NULL; + summary->asc_length = 0; + // summary->sbr_mode = MP4A_AAC_SBR_NONE; /* MPEG-2 AAC should not be HE-AAC, but we forgive them. */ + return summary; + } +#endif + if( lsmash_setup_AudioSpecificConfig( summary ) ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + return summary; +} + +typedef struct +{ + mp4sys_importer_status status; + unsigned int raw_data_block_idx; + mp4sys_adts_fixed_header_t header; + mp4sys_adts_variable_header_t variable_header; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4sys_adts_info_t; + +static int mp4sys_adts_get_accessunit( mp4sys_importer_t* importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + mp4sys_adts_info_t* info = (mp4sys_adts_info_t*)importer->info; + mp4sys_importer_status current_status = info->status; + uint16_t raw_data_block_size = info->variable_header.raw_data_block_size[info->raw_data_block_idx]; + if( current_status == MP4SYS_IMPORTER_ERROR || buffered_sample->length < raw_data_block_size ) + return -1; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + if( current_status == MP4SYS_IMPORTER_CHANGE ) + { + lsmash_audio_summary_t* summary = mp4sys_adts_create_summary( &info->header ); + if( !summary ) + return -1; + lsmash_entry_t* entry = lsmash_get_entry( importer->summaries, track_number ); + if( !entry || !entry->data ) + return -1; + lsmash_cleanup_summary( entry->data ); + entry->data = summary; + info->samples_in_frame = summary->samples_in_frame; + } + + /* read a raw_data_block(), typically == payload of a ADTS frame */ + if( fread( buffered_sample->data, 1, raw_data_block_size, importer->stream ) != raw_data_block_size ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return -1; + } + buffered_sample->length = raw_data_block_size; + buffered_sample->dts = info->au_number++ * info->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + buffered_sample->prop.pre_roll.distance = 1; /* MDCT */ + + /* now we succeeded to read current frame, so "return" takes 0 always below. */ + + /* skip adts_raw_data_block_error_check() */ + if( info->header.protection_absent == 0 + && info->variable_header.number_of_raw_data_blocks_in_frame != 0 + && fread( buffered_sample->data, 1, 2, importer->stream ) != 2 ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + /* current adts_frame() has any more raw_data_block()? */ + if( info->raw_data_block_idx < info->variable_header.number_of_raw_data_blocks_in_frame ) + { + info->raw_data_block_idx++; + info->status = MP4SYS_IMPORTER_OK; + return 0; + } + info->raw_data_block_idx = 0; + + /* preparation for next frame */ + + uint8_t buf[MP4SYS_ADTS_MAX_FRAME_LENGTH]; + size_t ret = fread( buf, 1, MP4SYS_ADTS_BASIC_HEADER_LENGTH, importer->stream ); + if( ret == 0 ) + { + info->status = MP4SYS_IMPORTER_EOF; + return 0; + } + if( ret != MP4SYS_ADTS_BASIC_HEADER_LENGTH ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + /* + * NOTE: About the spec of ADTS headers. + * By the spec definition, ADTS's fixed header cannot change in the middle of stream. + * But spec of MP4 allows that a stream(track) changes its properties in the middle of it. + */ + /* + * NOTE: About detailed check for ADTS headers. + * We do not ommit detailed check for fixed header by simply testing bits' identification, + * because there're some flags which does not matter to audio_summary (so AudioSpecificConfig neither) + * so that we can take them as no change and never make new ObjectDescriptor. + * I know that can be done with/by bitmask also and that should be fast, but L-SMASH project prefers + * even foolishly straightforward way. + */ + /* + * NOTE: About our reading algorithm for ADTS. + * It's rather simple if we retrieve payload of ADTS (i.e. raw AAC frame) at the same time to + * retrieve headers. + * But then we have to cache and memcpy every frame so that it requires more clocks and memory. + * To avoid them, I adopted this separate retrieving method. + */ + mp4sys_adts_fixed_header_t header = {0}; + mp4sys_adts_variable_header_t variable_header = {0}; + if( mp4sys_adts_parse_headers( importer->stream, buf, &header, &variable_header ) ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + info->variable_header = variable_header; + + /* + * NOTE: About our support for change(s) of properties within an ADTS stream. + * We have to modify these conditions depending on the features we support. + * For example, if we support copyright_identification_* in any way within any feature + * defined by/in any specs, such as ISO/IEC 14496-1 (MPEG-4 Systems), like... + * "8.3 Intellectual Property Management and Protection (IPMP)", or something similar, + * we have to check copyright_identification_* and treat them in audio_summary. + * "Change(s)" may result in MP4SYS_IMPORTER_ERROR or MP4SYS_IMPORTER_CHANGE + * depending on the features we support, and what the spec allows. + * Sometimes the "change(s)" can be allowed, while sometimes they're forbidden. + */ + /* currently UNsupported "change(s)". */ + if( info->header.profile_ObjectType != header.profile_ObjectType /* currently unsupported. */ + || info->header.ID != header.ID /* In strict, this means change of object_type_indication. */ + || info->header.sampling_frequency_index != header.sampling_frequency_index ) /* This may change timebase. */ + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + /* currently supported "change(s)". */ + if( info->header.channel_configuration != header.channel_configuration ) + { + /* + * FIXME: About conditions of VALID "change(s)". + * we have to check whether any "change(s)" affect to audioProfileLevelIndication + * in InitialObjectDescriptor (MP4_IOD) or not. + * If another type or upper level is required by the change(s), that is forbidden. + * Because ObjectDescriptor does not have audioProfileLevelIndication, + * so that it seems impossible to change audioProfileLevelIndication in the middle of the stream. + * Note also any other properties, such as AudioObjectType, object_type_indication. + */ + /* + * NOTE: updating summary must be done on next call, + * because user may retrieve summary right after this function call of this time, + * and that should be of current, before change, one. + */ + info->header = header; + info->status = MP4SYS_IMPORTER_CHANGE; + return 0; + } + /* no change which matters to mp4 muxing was found */ + info->status = MP4SYS_IMPORTER_OK; + return 0; +} + +static void mp4sys_adts_cleanup( mp4sys_importer_t* importer ) +{ + debug_if( importer && importer->info ) + free( importer->info ); +} + +/* returns 0 if it seems adts. */ +static int mp4sys_adts_probe( mp4sys_importer_t* importer ) +{ + uint8_t buf[MP4SYS_ADTS_MAX_FRAME_LENGTH]; + if( fread( buf, 1, MP4SYS_ADTS_BASIC_HEADER_LENGTH, importer->stream ) != MP4SYS_ADTS_BASIC_HEADER_LENGTH ) + return -1; + + mp4sys_adts_fixed_header_t header = {0}; + mp4sys_adts_variable_header_t variable_header = {0}; + if( mp4sys_adts_parse_headers( importer->stream, buf, &header, &variable_header ) ) + return -1; + + /* now the stream seems valid ADTS */ + + lsmash_audio_summary_t* summary = mp4sys_adts_create_summary( &header ); + if( !summary ) + return -1; + + /* importer status */ + mp4sys_adts_info_t* info = lsmash_malloc_zero( sizeof(mp4sys_adts_info_t) ); + if( !info ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + info->status = MP4SYS_IMPORTER_OK; + info->raw_data_block_idx = 0; + info->header = header; + info->variable_header = variable_header; + info->samples_in_frame = summary->samples_in_frame; + + if( lsmash_add_entry( importer->summaries, summary ) ) + { + free( info ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + importer->info = info; + + return 0; +} + +static uint32_t mp4sys_adts_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_adts_info_t *info = (mp4sys_adts_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + return info->samples_in_frame; +} + +const static mp4sys_importer_functions mp4sys_adts_importer = +{ + "adts", + 1, + mp4sys_adts_probe, + mp4sys_adts_get_accessunit, + mp4sys_adts_get_last_delta, + mp4sys_adts_cleanup +}; + +/*************************************************************************** + mp3 (Legacy Interface) importer +***************************************************************************/ + +static void mp4sys_mp3_cleanup( mp4sys_importer_t* importer ) +{ + debug_if( importer && importer->info ) + free( importer->info ); +} + +typedef struct +{ + uint16_t syncword; /* <12> */ + uint8_t ID; /* <1> */ + uint8_t layer; /* <2> */ +// uint8_t protection_bit; /* <1> don't care. */ + uint8_t bitrate_index; /* <4> */ + uint8_t sampling_frequency; /* <2> */ + uint8_t padding_bit; /* <1> */ +// uint8_t private_bit; /* <1> don't care. */ + uint8_t mode; /* <2> */ +// uint8_t mode_extension; /* <2> don't care. */ +// uint8_t copyright; /* <1> don't care. */ +// uint8_t original_copy; /* <1> don't care. */ + uint8_t emphasis; /* <2> for error check only. */ + +} mp4sys_mp3_header_t; + +static int mp4sys_mp3_parse_header( uint8_t* buf, mp4sys_mp3_header_t* header ) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ + uint32_t data = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + header->syncword = (data >> 20) & 0xFFF; /* NOTE: don't consider what is called MPEG2.5, which last bit is 0. */ + header->ID = (data >> 19) & 0x1; + header->layer = (data >> 17) & 0x3; +// header->protection_bit = (data >> 16) & 0x1; /* don't care. */ + header->bitrate_index = (data >> 12) & 0xF; + header->sampling_frequency = (data >> 10) & 0x3; + header->padding_bit = (data >> 9) & 0x1; +// header->private_bit = (data >> 8) & 0x1; /* don't care. */ + header->mode = (data >> 6) & 0x3; +// header->mode_extension = (data >> 4) & 0x3; +// header->copyright = (data >> 3) & 0x1; /* don't care. */ +// header->original_copy = (data >> 2) & 0x1; /* don't care. */ + header->emphasis = data & 0x3; /* for error check only. */ + + if( header->syncword != 0xFFF ) return -1; + if( header->layer == 0x0 ) return -1; + if( header->bitrate_index == 0x0 || header->bitrate_index == 0xF ) return -1; /* FIXME: "free" bitrate is unsupported currently. */ + if( header->sampling_frequency == 0x3) return -1; + if( header->emphasis == 0x2) return -1; + return 0; +} + +#define MP4SYS_MP3_MAX_FRAME_LENGTH (1152*(16/8)*2) +#define MP4SYS_MP3_HEADER_LENGTH 4 +#define MP4SYS_MODE_IS_2CH( mode ) (!!~(mode)) +#define MP4SYS_LAYER_III 0x1 +#define MP4SYS_LAYER_I 0x3 + +static const uint32_t mp4sys_mp3_frequency_tbl[2][3] = { + { 22050, 24000, 16000 }, /* MPEG-2 BC audio */ + { 44100, 48000, 32000 } /* MPEG-1 audio */ +}; + +static lsmash_audio_summary_t *mp4sys_mp3_create_summary( mp4sys_mp3_header_t *header, int legacy_mode ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return NULL; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->object_type_indication = header->ID ? MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3 : MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3; + summary->max_au_length = MP4SYS_MP3_MAX_FRAME_LENGTH; + summary->frequency = mp4sys_mp3_frequency_tbl[header->ID][header->sampling_frequency]; + summary->channels = MP4SYS_MODE_IS_2CH( header->mode ) + 1; + summary->bit_depth = 16; + summary->samples_in_frame = header->layer == MP4SYS_LAYER_I ? 384 : 1152; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_Layer_1 + (MP4SYS_LAYER_I - header->layer); /* no effect with Legacy Interface. */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + summary->exdata = NULL; + summary->exdata_length = 0; +#if 0 /* FIXME: This is very unstable. Many players crash with this. */ + if( !legacy_mode ) + { + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + if( lsmash_setup_AudioSpecificConfig( summary ) ) + { + lsmash_cleanup_summary( summary ); + return NULL; + } + } +#endif + return summary; +} + +typedef struct +{ + mp4sys_importer_status status; + mp4sys_mp3_header_t header; + uint8_t raw_header[MP4SYS_MP3_HEADER_LENGTH]; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4sys_mp3_info_t; + +static int mp4sys_mp3_get_accessunit( mp4sys_importer_t* importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + mp4sys_mp3_info_t* info = (mp4sys_mp3_info_t*)importer->info; + mp4sys_mp3_header_t* header = (mp4sys_mp3_header_t*)&info->header; + mp4sys_importer_status current_status = info->status; + + const uint32_t bitrate_tbl[2][3][16] = { + { /* MPEG-2 BC audio */ + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, /* Layer III */ + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, /* Layer II */ + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } /* Layer I */ + }, + { /* MPEG-1 audio */ + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, /* Layer III */ + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, /* Layer II */ + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 } /* Layer I */ + } + }; + uint32_t bitrate = bitrate_tbl[header->ID][header->layer-1][header->bitrate_index]; + uint32_t frequency = mp4sys_mp3_frequency_tbl[header->ID][header->sampling_frequency]; + debug_if( bitrate == 0 || frequency == 0 ) + return -1; + uint32_t frame_size; + if( header->layer == MP4SYS_LAYER_I ) + { + /* mp1's 'slot' is 4 bytes unit. see 11172-3, Audio Sequence General. */ + frame_size = ( 12 * 1000 * bitrate / frequency + header->padding_bit ) * 4; + } + else + { + /* mp2/3's 'slot' is 1 bytes unit. */ + frame_size = 144 * 1000 * bitrate / frequency + header->padding_bit; + } + + if( current_status == MP4SYS_IMPORTER_ERROR || frame_size <= 4 || buffered_sample->length < frame_size ) + return -1; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + if( current_status == MP4SYS_IMPORTER_CHANGE ) + { + lsmash_audio_summary_t* summary = mp4sys_mp3_create_summary( header, 1 ); /* FIXME: use legacy mode. */ + if( !summary ) + return -1; + lsmash_entry_t* entry = lsmash_get_entry( importer->summaries, track_number ); + if( !entry || !entry->data ) + return -1; + lsmash_cleanup_summary( entry->data ); + entry->data = summary; + info->samples_in_frame = summary->samples_in_frame; + } + /* read a frame's data. */ + memcpy( buffered_sample->data, info->raw_header, MP4SYS_MP3_HEADER_LENGTH ); + frame_size -= MP4SYS_MP3_HEADER_LENGTH; + if( fread( ((uint8_t*)buffered_sample->data)+MP4SYS_MP3_HEADER_LENGTH, 1, frame_size, importer->stream ) != frame_size ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return -1; + } + buffered_sample->length = MP4SYS_MP3_HEADER_LENGTH + frame_size; + buffered_sample->dts = info->au_number++ * info->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + buffered_sample->prop.pre_roll.distance = header->layer == MP4SYS_LAYER_III ? 1 : 0; /* Layer III uses MDCT */ + + /* now we succeeded to read current frame, so "return" takes 0 always below. */ + /* preparation for next frame */ + + uint8_t buf[MP4SYS_MP3_HEADER_LENGTH]; + size_t ret = fread( buf, 1, MP4SYS_MP3_HEADER_LENGTH, importer->stream ); + if( ret == 0 ) + { + info->status = MP4SYS_IMPORTER_EOF; + return 0; + } + if( ret == 1 && *buf == 0x00 ) + { + /* NOTE: ugly hack for mp1 stream created with SCMPX. */ + info->status = MP4SYS_IMPORTER_EOF; + return 0; + } + if( ret != MP4SYS_MP3_HEADER_LENGTH ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + + mp4sys_mp3_header_t new_header = {0}; + if( mp4sys_mp3_parse_header( buf, &new_header ) ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + memcpy( info->raw_header, buf, MP4SYS_MP3_HEADER_LENGTH ); + + /* currently UNsupported "change(s)". */ + if( header->layer != new_header.layer /* This means change of object_type_indication with Legacy Interface. */ + || header->sampling_frequency != new_header.sampling_frequency ) /* This may change timescale. */ + { + info->status = MP4SYS_IMPORTER_ERROR; + return 0; + } + + /* currently supported "change(s)". */ + if( MP4SYS_MODE_IS_2CH( header->mode ) != MP4SYS_MODE_IS_2CH( new_header.mode ) ) + info->status = MP4SYS_IMPORTER_CHANGE; + else + info->status = MP4SYS_IMPORTER_OK; /* no change which matters to mp4 muxing was found */ + info->header = new_header; + return 0; +} + +static int mp4sys_mp3_probe( mp4sys_importer_t* importer ) +{ + uint8_t buf[MP4SYS_MP3_HEADER_LENGTH]; + if( fread( buf, 1, MP4SYS_MP3_HEADER_LENGTH, importer->stream ) != MP4SYS_MP3_HEADER_LENGTH ) + return -1; + + mp4sys_mp3_header_t header = {0}; + if( mp4sys_mp3_parse_header( buf, &header ) ) + return -1; + + /* now the stream seems valid mp3 */ + + lsmash_audio_summary_t* summary = mp4sys_mp3_create_summary( &header, 1 ); /* FIXME: use legacy mode. */ + if( !summary ) + return -1; + + /* importer status */ + mp4sys_mp3_info_t* info = lsmash_malloc_zero( sizeof(mp4sys_mp3_info_t) ); + if( !info ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + info->status = MP4SYS_IMPORTER_OK; + info->header = header; + info->samples_in_frame = summary->samples_in_frame; + memcpy( info->raw_header, buf, MP4SYS_MP3_HEADER_LENGTH ); + + if( lsmash_add_entry( importer->summaries, summary ) ) + { + free( info ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + importer->info = info; + + return 0; +} + +static uint32_t mp4sys_mp3_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_mp3_info_t *info = (mp4sys_mp3_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + return info->samples_in_frame; +} + +const static mp4sys_importer_functions mp4sys_mp3_importer = +{ + "MPEG-1/2BC_Audio_Legacy", + 1, + mp4sys_mp3_probe, + mp4sys_mp3_get_accessunit, + mp4sys_mp3_get_last_delta, + mp4sys_mp3_cleanup +}; + +/*************************************************************************** + AMR-NB/WB storage format importer + http://www.ietf.org/rfc/rfc3267.txt (Obsoleted) + http://www.ietf.org/rfc/rfc4867.txt +***************************************************************************/ +static void mp4sys_amr_cleanup( mp4sys_importer_t* importer ) +{ + debug_if( importer && importer->info ) + free( importer->info ); +} + +typedef struct +{ + uint8_t wb; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4sys_amr_info_t; + +static int mp4sys_amr_get_accessunit( mp4sys_importer_t* importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( track_number != 1 ) + return -1; + mp4sys_amr_info_t *info = (mp4sys_amr_info_t *)importer->info; + + uint8_t* buf = buffered_sample->data; + if( fread( buf, 1, 1, importer->stream ) == 0 ) + { + /* EOF */ + buffered_sample->length = 0; + return 0; + } + uint8_t FT = (*buf >> 3) & 0x0F; + + /* AMR-NB has varieties of frame-size table like this. so I'm not sure yet. */ + const int frame_size[2][16] = { + { 13, 14, 16, 18, 20, 21, 27, 32, 5, 5, 5, 5, 0, 0, 0, 1 }, + { 18, 24, 33, 37, 41, 47, 51, 59, 61, 6, 6, 0, 0, 0, 1, 1 } + }; + int read_size = frame_size[info->wb][FT]; + if( read_size == 0 || buffered_sample->length < read_size-- ) + return -1; + if( read_size == 0 ) + buffered_sample->length = 1; + else + { + if( fread( buf+1, 1, read_size, importer->stream ) != read_size ) + return -1; + buffered_sample->length = read_size + 1; + } + buffered_sample->dts = info->au_number++ * info->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + return 0; +} + +#define MP4SYS_DAMR_LENGTH 17 + +int mp4sys_amr_create_damr( lsmash_audio_summary_t *summary ) +{ + lsmash_bs_t* bs = lsmash_bs_create( NULL ); /* no file writing */ + if( !bs ) + return -1; + lsmash_bs_put_be32( bs, MP4SYS_DAMR_LENGTH ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_DAMR ); + /* NOTE: These are specific to each codec vendor, but we're surely not a vendor. + Using dummy data. */ + lsmash_bs_put_be32( bs, 0x20202020 ); /* vendor */ + lsmash_bs_put_byte( bs, 0 ); /* decoder_version */ + + /* NOTE: Using safe value for these settings, maybe sub-optimal. */ + lsmash_bs_put_be16( bs, 0x83FF ); /* mode_set, represents for possibly existing frame-type (0x83FF == all). */ + lsmash_bs_put_byte( bs, 1 ); /* mode_change_period */ + lsmash_bs_put_byte( bs, 1 ); /* frames_per_sample */ + + if( summary->exdata ) + free( summary->exdata ); + summary->exdata = lsmash_bs_export_data( bs, &summary->exdata_length ); + lsmash_bs_cleanup( bs ); + if( !summary->exdata ) + return -1; + summary->exdata_length = MP4SYS_DAMR_LENGTH; + return 0; +} + +#define MP4SYS_AMR_STORAGE_MAGIC_LENGTH 6 +#define MP4SYS_AMRWB_EX_MAGIC_LENGTH 3 + +static int mp4sys_amr_probe( mp4sys_importer_t* importer ) +{ + uint8_t buf[MP4SYS_AMR_STORAGE_MAGIC_LENGTH]; + uint8_t wb = 0; + if( fread( buf, 1, MP4SYS_AMR_STORAGE_MAGIC_LENGTH, importer->stream ) != MP4SYS_AMR_STORAGE_MAGIC_LENGTH ) + return -1; + if( memcmp( buf, "#!AMR", MP4SYS_AMR_STORAGE_MAGIC_LENGTH-1 ) ) + return -1; + if( buf[MP4SYS_AMR_STORAGE_MAGIC_LENGTH-1] != '\n' ) + { + if( buf[MP4SYS_AMR_STORAGE_MAGIC_LENGTH-1] != '-' ) + return -1; + if( fread( buf, 1, MP4SYS_AMRWB_EX_MAGIC_LENGTH, importer->stream ) != MP4SYS_AMRWB_EX_MAGIC_LENGTH ) + return -1; + if( memcmp( buf, "WB\n", MP4SYS_AMRWB_EX_MAGIC_LENGTH ) ) + return -1; + wb = 1; + } + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return -1; + summary->sample_type = wb ? ISOM_CODEC_TYPE_SAWB_AUDIO : ISOM_CODEC_TYPE_SAMR_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_NONE; /* AMR is not defined in ISO/IEC 14496-3 */ + summary->exdata = NULL; /* to be set in mp4sys_amrnb_create_damr() */ + summary->exdata_length = 0; /* to be set in mp4sys_amrnb_create_damr() */ + summary->max_au_length = wb ? 61 : 32; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->frequency = (8000 << wb); + summary->channels = 1; + summary->bit_depth = 16; + summary->samples_in_frame = (160 << wb); + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + mp4sys_amr_info_t *info = malloc( sizeof(mp4sys_amr_info_t) ); + if( !info ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + info->wb = wb; + info->samples_in_frame = summary->samples_in_frame; + info->au_number = 0; + importer->info = info; + if( mp4sys_amr_create_damr( summary ) || lsmash_add_entry( importer->summaries, summary ) ) + { + free( importer->info ); + importer->info = NULL; + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + return 0; +} + +static uint32_t mp4sys_amr_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_amr_info_t *info = (mp4sys_amr_info_t *)importer->info; + if( !info || track_number != 1 ) + return 0; + return info->samples_in_frame; +} + +const static mp4sys_importer_functions mp4sys_amr_importer = +{ + "amr", + 1, + mp4sys_amr_probe, + mp4sys_amr_get_accessunit, + mp4sys_amr_get_last_delta, + mp4sys_amr_cleanup +}; + +/*************************************************************************** + AC-3 importer +***************************************************************************/ +#define AC3_MAX_AU_LENGTH 3840 +#define AC3_MIN_AU_LENGTH 128 +#define AC3_SAMPLE_DURATION 1536 /* 256 (samples per audio block) * 6 (audio blocks) */ + +typedef struct +{ + uint8_t fscod; + uint8_t bsid; + uint8_t bsmod; + uint8_t acmod; + uint8_t lfeon; + uint8_t frmsizecod; +} ac3_dac3_element_t; + +typedef struct +{ + mp4sys_importer_status status; + ac3_dac3_element_t dac3_element; + lsmash_bits_t *bits; + uint8_t buffer[AC3_MAX_AU_LENGTH]; + uint8_t *next_dac3; + uint32_t au_number; +} mp4sys_ac3_info_t; + +static void mp4sys_remove_ac3_info( mp4sys_ac3_info_t *info ) +{ + if( !info ) + return; + lsmash_bits_adhoc_cleanup( info->bits ); + free( info ); +} + +static mp4sys_ac3_info_t *mp4sys_create_ac3_info( void ) +{ + mp4sys_ac3_info_t *info = (mp4sys_ac3_info_t *)lsmash_malloc_zero( sizeof(mp4sys_ac3_info_t) ); + if( !info ) + return NULL; + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + free( info ); + return NULL; + } + return info; +} + +static void mp4sys_ac3_cleanup( mp4sys_importer_t *importer ) +{ + debug_if( importer && importer->info ) + mp4sys_remove_ac3_info( importer->info ); +} + +static int ac3_check_syncframe_header( ac3_dac3_element_t *element ) +{ + if( element->fscod == 0x3 ) + return -1; /* unknown Sample Rate Code */ + if( element->frmsizecod > 0x25 ) + return -1; /* unknown Frame Size Code */ + if( element->bsid >= 10 ) + return -1; /* might be EAC-3 */ + return 0; +} + +static int ac3_parse_syncframe_header( mp4sys_ac3_info_t *info, uint8_t *data ) +{ + lsmash_bits_t *bits = info->bits; + if( lsmash_bits_import_data( bits, data, AC3_MIN_AU_LENGTH ) ) + return -1; + ac3_dac3_element_t *element = &info->dac3_element; + lsmash_bits_get( bits, 32 ); /* syncword + crc1 */ + element->fscod = lsmash_bits_get( bits, 2 ); + element->frmsizecod = lsmash_bits_get( bits, 6 ); + element->bsid = lsmash_bits_get( bits, 5 ); + element->bsmod = lsmash_bits_get( bits, 3 ); + element->acmod = lsmash_bits_get( bits, 3 ); + if( (element->acmod & 0x01) && (element->acmod != 0x01) ) + lsmash_bits_get( bits, 2 ); /* cmixlev */ + if( element->acmod & 0x04 ) + lsmash_bits_get( bits, 2 ); /* surmixlev */ + if( element->acmod == 0x02 ) + lsmash_bits_get( bits, 2 ); /* dsurmod */ + element->lfeon = lsmash_bits_get( bits, 1 ); + lsmash_bits_empty( bits ); + return ac3_check_syncframe_header( element ); +} + +#define AC3_DAC3_BOX_LENGTH 11 + +static uint8_t *ac3_create_dac3( mp4sys_ac3_info_t *info ) +{ + lsmash_bits_t *bits = info->bits; + ac3_dac3_element_t *element = &info->dac3_element; + lsmash_bits_put( bits, AC3_DAC3_BOX_LENGTH, 32 ); + lsmash_bits_put( bits, ISOM_BOX_TYPE_DAC3, 32 ); + lsmash_bits_put( bits, element->fscod, 2 ); + lsmash_bits_put( bits, element->bsid, 5 ); + lsmash_bits_put( bits, element->bsmod, 3 ); + lsmash_bits_put( bits, element->acmod, 3 ); + lsmash_bits_put( bits, element->lfeon, 1 ); + lsmash_bits_put( bits, element->frmsizecod >> 1, 5 ); + lsmash_bits_put( bits, 0, 5 ); + uint8_t *dac3 = lsmash_bits_export_data( bits, NULL ); + lsmash_bits_empty( bits ); + return dac3; +} + +#define IF_AC3_SYNCWORD( x ) if( (x)[0] != 0x0b || (x)[1] != 0x77 ) + +/* data_length must be size of data that is available. */ +int mp4sys_create_dac3_from_syncframe( lsmash_audio_summary_t *summary, uint8_t *data, uint32_t data_length ) +{ + if( data_length < AC3_MIN_AU_LENGTH ) + return -1; + IF_AC3_SYNCWORD( data ) + return -1; + mp4sys_ac3_info_t info; + info.bits = lsmash_bits_adhoc_create(); + if( !info.bits ) + return -1; + if( ac3_parse_syncframe_header( &info, data ) ) + { + lsmash_bits_adhoc_cleanup( info.bits ); + return -1; + } + uint8_t *dac3 = ac3_create_dac3( &info ); + lsmash_bits_adhoc_cleanup( info.bits ); + if( !dac3 ) + return -1; + if( summary->exdata ) + free( summary->exdata ); + summary->exdata = dac3; + summary->exdata_length = AC3_DAC3_BOX_LENGTH; + return 0; +} + +static const uint32_t ac3_sample_rate_table[4] = { 48000, 44100, 32000, 0 }; + +static const uint32_t ac3_frame_size_table[19][3] = +{ + /* 48, 44.1, 32 */ + { 128, 138, 192 }, + { 160, 174, 240 }, + { 192, 208, 288 }, + { 224, 242, 336 }, + { 256, 278, 384 }, + { 320, 348, 480 }, + { 384, 416, 576 }, + { 448, 486, 672 }, + { 512, 556, 768 }, + { 640, 696, 960 }, + { 768, 834, 1152 }, + { 896, 974, 1344 }, + { 1024, 1114, 1536 }, + { 1280, 1392, 1920 }, + { 1536, 1670, 2304 }, + { 1792, 1950, 2688 }, + { 2048, 2228, 3072 }, + { 2304, 2506, 3456 }, + { 2560, 2786, 3840 } +}; + +static const uint32_t ac3_channel_count_table[8] = { 2, 1, 2, 3, 3, 4, 4, 5 }; + +static const lsmash_channel_layout_tag ac3_channel_layout_table[8][2] = +{ + /* LFE: off LFE: on */ + { QT_CHANNEL_LAYOUT_UNKNOWN, QT_CHANNEL_LAYOUT_UNKNOWN }, /* FIXME: dual mono */ + { QT_CHANNEL_LAYOUT_MONO, QT_CHANNEL_LAYOUT_AC3_1_0_1 }, + { QT_CHANNEL_LAYOUT_STEREO, QT_CHANNEL_LAYOUT_DVD_4 }, + { QT_CHANNEL_LAYOUT_AC3_3_0, QT_CHANNEL_LAYOUT_AC3_3_0_1 }, + { QT_CHANNEL_LAYOUT_DVD_2, QT_CHANNEL_LAYOUT_AC3_2_1_1 }, + { QT_CHANNEL_LAYOUT_AC3_3_1, QT_CHANNEL_LAYOUT_AC3_3_1_1 }, + { QT_CHANNEL_LAYOUT_DVD_3, QT_CHANNEL_LAYOUT_DVD_18 }, + { QT_CHANNEL_LAYOUT_MPEG_5_0_C, QT_CHANNEL_LAYOUT_MPEG_5_1_C } +}; + +static lsmash_audio_summary_t *ac3_create_summary( mp4sys_ac3_info_t *info ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return NULL; + uint8_t *dac3 = ac3_create_dac3( info ); + if( !dac3 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + ac3_dac3_element_t *element = &info->dac3_element; + summary->exdata = dac3; + summary->exdata_length = AC3_DAC3_BOX_LENGTH; + summary->sample_type = ISOM_CODEC_TYPE_AC_3_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_AC_3_AUDIO; /* forbidden to use for ISO Base Media */ + summary->max_au_length = AC3_MAX_AU_LENGTH; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->frequency = ac3_sample_rate_table[ element->fscod ]; + summary->channels = ac3_channel_count_table[ element->acmod ] + element->lfeon; + summary->bit_depth = 16; /* no effect */ + summary->samples_in_frame = AC3_SAMPLE_DURATION; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + summary->layout_tag = ac3_channel_layout_table[ element->acmod ][ element->lfeon ]; + return summary; +} + +static int ac3_compare_dac3_elements( ac3_dac3_element_t *a, ac3_dac3_element_t *b ) +{ + return (a->fscod != b->fscod) + || (a->bsid != b->bsid) + || (a->bsmod != b->bsmod) + || (a->acmod != b->acmod) + || (a->lfeon != b->lfeon) + || ((a->frmsizecod >> 1) != (b->frmsizecod >> 1)); +} + +static int mp4sys_ac3_get_accessunit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return -1; + mp4sys_ac3_info_t *info = (mp4sys_ac3_info_t *)importer->info; + mp4sys_importer_status current_status = info->status; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + ac3_dac3_element_t *element = &info->dac3_element; + if( current_status == MP4SYS_IMPORTER_CHANGE ) + { + free( summary->exdata ); + summary->exdata = info->next_dac3; + summary->frequency = ac3_sample_rate_table[ element->fscod ]; + summary->channels = ac3_channel_count_table[ element->acmod ] + element->lfeon; + summary->layout_tag = ac3_channel_layout_table[ element->acmod ][ element->lfeon ]; + } + uint32_t frame_size = ac3_frame_size_table[ element->frmsizecod >> 1 ][ element->fscod ]; + if( element->fscod == 0x1 && element->frmsizecod & 0x1 ) + frame_size += 2; + if( frame_size > AC3_MIN_AU_LENGTH ) + { + uint32_t read_size = frame_size - AC3_MIN_AU_LENGTH; + if( fread( info->buffer + AC3_MIN_AU_LENGTH, 1, read_size, importer->stream ) != read_size ) + return -1; + } + memcpy( buffered_sample->data, info->buffer, frame_size ); + buffered_sample->length = frame_size; + buffered_sample->dts = info->au_number++ * summary->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + buffered_sample->prop.pre_roll.distance = 1; /* MDCT */ + if( fread( info->buffer, 1, AC3_MIN_AU_LENGTH, importer->stream ) != AC3_MIN_AU_LENGTH ) + info->status = MP4SYS_IMPORTER_EOF; + else + { + /* Parse the next syncframe header. */ + IF_AC3_SYNCWORD( info->buffer ) + return -1; + ac3_dac3_element_t current_element = info->dac3_element; + ac3_parse_syncframe_header( info, info->buffer ); + if( ac3_compare_dac3_elements( ¤t_element, &info->dac3_element ) ) + { + uint8_t *dac3 = ac3_create_dac3( info ); + if( !dac3 ) + return -1; + info->status = MP4SYS_IMPORTER_CHANGE; + info->next_dac3 = dac3; + } + else + info->status = MP4SYS_IMPORTER_OK; + } + return current_status; +} + +static int mp4sys_ac3_probe( mp4sys_importer_t* importer ) +{ + uint8_t buf[AC3_MIN_AU_LENGTH]; + if( fread( buf, 1, AC3_MIN_AU_LENGTH, importer->stream ) != AC3_MIN_AU_LENGTH ) + return -1; + IF_AC3_SYNCWORD( buf ) + return -1; + mp4sys_ac3_info_t *info = mp4sys_create_ac3_info(); + if( !info ) + return -1; + if( ac3_parse_syncframe_header( info, buf ) ) + { + mp4sys_remove_ac3_info( info ); + return -1; + } + lsmash_audio_summary_t *summary = ac3_create_summary( info ); + if( !summary ) + { + mp4sys_remove_ac3_info( info ); + return -1; + } + info->status = MP4SYS_IMPORTER_OK; + info->au_number = 0; + memcpy( info->buffer, buf, AC3_MIN_AU_LENGTH ); + importer->info = info; + if( lsmash_add_entry( importer->summaries, summary ) ) + { + mp4sys_remove_ac3_info( importer->info ); + importer->info = NULL; + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + return 0; +} + +static uint32_t mp4sys_ac3_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_ac3_info_t *info = (mp4sys_ac3_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + return AC3_SAMPLE_DURATION; +} + +const static mp4sys_importer_functions mp4sys_ac3_importer = +{ + "ac3", + 1, + mp4sys_ac3_probe, + mp4sys_ac3_get_accessunit, + mp4sys_ac3_get_last_delta, + mp4sys_ac3_cleanup +}; + +/*************************************************************************** + Enhanced AC-3 importer +***************************************************************************/ +#define EAC3_MAX_SYNCFRAME_LENGTH 4096 +#define EAC3_FIRST_FIVE_BYTES 5 +#define EAC3_MIN_SAMPLE_DURATION 256 + +typedef struct +{ + uint8_t fscod; + uint8_t fscod2; + uint8_t bsid; + uint8_t bsmod; + uint8_t acmod; + uint8_t lfeon; + uint8_t num_dep_sub; + uint16_t chan_loc; +} eac3_substream_info_t; + +typedef struct +{ + mp4sys_importer_status status; + uint8_t strmtyp; + uint8_t substreamid; + uint8_t current_independent_substream_id; + eac3_substream_info_t independent_info_0; /* mirror for creating summary */ + eac3_substream_info_t independent_info[8]; + eac3_substream_info_t dependent_info; + uint8_t numblkscod; + uint8_t number_of_audio_blocks; + uint8_t frmsizecod; + uint8_t number_of_independent_substreams; + lsmash_bits_t *bits; + uint8_t buffer[EAC3_MAX_SYNCFRAME_LENGTH]; + uint8_t *next_dec3; + uint32_t next_dec3_length; + uint32_t syncframe_count; + uint32_t syncframe_count_in_au; + uint32_t frame_size; + lsmash_multiple_buffers_t *au_buffers; + uint8_t *au; + uint32_t au_length; + uint8_t *incomplete_au; + uint32_t incomplete_au_length; + uint32_t au_number; +} mp4sys_eac3_info_t; + +static void mp4sys_remove_eac3_info( mp4sys_eac3_info_t *info ) +{ + if( !info ) + return; + lsmash_destroy_multiple_buffers( info->au_buffers ); + lsmash_bits_adhoc_cleanup( info->bits ); + free( info ); +} + +static mp4sys_eac3_info_t *mp4sys_create_eac3_info( void ) +{ + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)lsmash_malloc_zero( sizeof(mp4sys_eac3_info_t) ); + if( !info ) + return NULL; + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + free( info ); + return NULL; + } + info->au_buffers = lsmash_create_multiple_buffers( 2, EAC3_MAX_SYNCFRAME_LENGTH ); + if( !info->au_buffers ) + { + lsmash_bits_adhoc_cleanup( info->bits ); + free( info ); + return NULL; + } + info->au = lsmash_withdraw_buffer( info->au_buffers, 1 ); + info->incomplete_au = lsmash_withdraw_buffer( info->au_buffers, 2 ); + return info; +} + +static void mp4sys_eac3_cleanup( mp4sys_importer_t *importer ) +{ + debug_if( importer && importer->info ) + mp4sys_remove_eac3_info( importer->info ); +} + +static int eac3_check_syncframe_header( mp4sys_eac3_info_t *info ) +{ + if( info->strmtyp == 0x3 ) + return -1; /* unknown Stream type */ + eac3_substream_info_t *independent_info; + if( info->strmtyp != 0x1 ) + independent_info = &info->independent_info[ info->current_independent_substream_id ]; + else + independent_info = &info->dependent_info; + if( independent_info->fscod == 0x3 && independent_info->fscod2 == 0x3 ) + return -1; /* unknown Sample Rate Code */ + if( independent_info->bsid < 10 || independent_info->bsid > 16 ) + return -1; /* not EAC-3 */ + return 0; +} + +static int eac3_parse_syncframe_header( mp4sys_importer_t *importer ) +{ + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)importer->info; + lsmash_bits_t *bits = info->bits; + if( lsmash_bits_import_data( bits, info->buffer, EAC3_FIRST_FIVE_BYTES ) ) + return -1; + lsmash_bits_get( bits, 16 ); /* syncword */ + info->strmtyp = lsmash_bits_get( bits, 2 ); + info->substreamid = lsmash_bits_get( bits, 3 ); + eac3_substream_info_t *substream_info; + if( info->strmtyp != 0x1 ) + { + info->current_independent_substream_id = info->substreamid; + substream_info = &info->independent_info[ info->current_independent_substream_id ]; + if( info->substreamid == 0x0 ) + info->independent_info_0 = *substream_info; /* backup */ + substream_info->chan_loc = 0; + } + else + substream_info = &info->dependent_info; + uint16_t frmsiz = lsmash_bits_get( bits, 11 ); + substream_info->fscod = lsmash_bits_get( bits, 2 ); + if( substream_info->fscod == 0x3 ) + { + substream_info->fscod2 = lsmash_bits_get( bits, 2 ); + info->numblkscod = 0x3; + } + else + info->numblkscod = lsmash_bits_get( bits, 2 ); + substream_info->acmod = lsmash_bits_get( bits, 3 ); + substream_info->lfeon = lsmash_bits_get( bits, 1 ); + lsmash_bits_empty( bits ); + /* Read up to the end of the current syncframe. */ + info->frame_size = 2 * (frmsiz + 1); + uint32_t read_size = info->frame_size - EAC3_FIRST_FIVE_BYTES; + if( fread( info->buffer + EAC3_FIRST_FIVE_BYTES, 1, read_size, importer->stream ) != read_size ) + return -1; + if( lsmash_bits_import_data( bits, info->buffer + EAC3_FIRST_FIVE_BYTES, read_size ) ) + return -1; + /* Continue to parse header. */ + substream_info->bsid = lsmash_bits_get( bits, 5 ); + lsmash_bits_get( bits, 5 ); /* dialnorm */ + if( lsmash_bits_get( bits, 1 ) ) /* compre */ + lsmash_bits_get( bits, 8 ); /* compr */ + if( substream_info->acmod == 0x0 ) + { + lsmash_bits_get( bits, 5 ); /* dialnorm2 */ + if( lsmash_bits_get( bits, 1 ) ) /* compre2 */ + lsmash_bits_get( bits, 8 ); /* compr2 */ + } + if( info->strmtyp == 0x1 && lsmash_bits_get( bits, 1 ) ) /* chanmape */ + { + uint16_t chanmap = lsmash_bits_get( bits, 16 ); + uint16_t chan_loc = chanmap >> 5; + chan_loc = (chan_loc & 0xff ) | ((chan_loc & 0x200) >> 1); + info->independent_info[ info->current_independent_substream_id ].chan_loc |= chan_loc; + } + if( lsmash_bits_get( bits, 1 ) ) /* mixmdate */ + { + if( substream_info->acmod > 0x2 ) + lsmash_bits_get( bits, 2 ); /* dmixmod */ + if( ((substream_info->acmod & 0x1) && (substream_info->acmod > 0x2)) || (substream_info->acmod & 0x4) ) + lsmash_bits_get( bits, 6 ); /* ltrt[c/sur]mixlev + loro[c/sur]mixlev */ + if( substream_info->lfeon && lsmash_bits_get( bits, 1 ) ) /* lfemixlevcode */ + lsmash_bits_get( bits, 5 ); /* lfemixlevcod */ + if( info->strmtyp == 0x0 ) + { + if( lsmash_bits_get( bits, 1 ) ) /* pgmscle*/ + lsmash_bits_get( bits, 6 ); /* pgmscl */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* pgmscle2 */ + lsmash_bits_get( bits, 6 ); /* pgmscl2 */ + if( lsmash_bits_get( bits, 1 ) ) /* extpgmscle */ + lsmash_bits_get( bits, 6 ); /* extpgmscl */ + uint8_t mixdef = lsmash_bits_get( bits, 2 ); + if( mixdef == 0x1 ) + lsmash_bits_get( bits, 5 ); /* premixcmpsel + drcsrc + premixcmpscl */ + else if( mixdef == 0x2 ) + lsmash_bits_get( bits, 12 ); /* mixdata */ + else if( mixdef == 0x3 ) + { + uint8_t mixdeflen = lsmash_bits_get( bits, 5 ); + lsmash_bits_get( bits, 8 * (mixdeflen + 2) ); /* mixdata */ + } + if( substream_info->acmod < 0x2 ) + { + if( lsmash_bits_get( bits, 1 ) ) /* paninfoe */ + lsmash_bits_get( bits, 14 ); /* paninfo */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* paninfo2e */ + lsmash_bits_get( bits, 14 ); /* paninfo2 */ + } + if( lsmash_bits_get( bits, 1 ) ) /* frmmixcfginfoe */ + { + if( info->numblkscod == 0x0 ) + lsmash_bits_get( bits, 5 ); /* blkmixcfginfo[0] */ + else + { + int number_of_blocks_per_syncframe = ((int []){ 1, 2, 3, 6 })[ info->numblkscod ]; + for( int blk = 0; blk < number_of_blocks_per_syncframe; blk++ ) + if( lsmash_bits_get( bits, 1 ) ) /* blkmixcfginfoe */ + lsmash_bits_get( bits, 5 ); /* blkmixcfginfo[blk] */ + } + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* infomdate */ + { + substream_info->bsmod = lsmash_bits_get( bits, 3 ); + lsmash_bits_get( bits, 1 ); /* copyrightb */ + lsmash_bits_get( bits, 1 ); /* origbs */ + if( substream_info->acmod == 0x2 ) + lsmash_bits_get( bits, 4 ); /* dsurmod + dheadphonmod */ + else if( substream_info->acmod >= 0x6 ) + lsmash_bits_get( bits, 2 ); /* dsurexmod */ + if( lsmash_bits_get( bits, 1 ) ) /* audprodie */ + lsmash_bits_get( bits, 8 ); /* mixlevel + roomtyp + adconvtyp */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* audprodie2 */ + lsmash_bits_get( bits, 8 ); /* mixlevel2 + roomtyp2 + adconvtyp2 */ + if( substream_info->fscod < 0x3 ) + lsmash_bits_get( bits, 1 ); /* sourcefscod */ + } + else + substream_info->bsmod = 0; + if( info->strmtyp == 0x0 && info->numblkscod != 0x3 ) + lsmash_bits_get( bits, 1 ); /* convsync */ + if( info->strmtyp == 0x2 ) + { + int blkid; + if( info->numblkscod == 0x3 ) + blkid = 1; + else + blkid = lsmash_bits_get( bits, 1 ); + if( blkid ) + lsmash_bits_get( bits, 6 ); /* frmsizecod */ + } + if( lsmash_bits_get( bits, 1 ) ) /* addbsie */ + { + uint8_t addbsil = lsmash_bits_get( bits, 6 ); + lsmash_bits_get( bits, (addbsil + 1) * 8 ); /* addbsi */ + } + lsmash_bits_empty( bits ); + return eac3_check_syncframe_header( info ); +} + +static uint8_t *eac3_create_dec3( mp4sys_eac3_info_t *info, uint32_t *dec3_length ) +{ + if( info->number_of_independent_substreams > 8 ) + return NULL; + lsmash_bits_t *bits = info->bits; + lsmash_bits_put( bits, 0, 32 ); /* box size */ + lsmash_bits_put( bits, ISOM_BOX_TYPE_DEC3, 32 ); + lsmash_bits_put( bits, 0, 13 ); /* data_rate will be calculated by isom_update_bitrate_info */ + lsmash_bits_put( bits, info->number_of_independent_substreams - 1, 3 ); /* num_ind_sub */ + /* Apparently, the condition of this loop defined in ETSI TS 102 366 V1.2.1 (2008-08) is wrong. */ + for( int i = 0; i < info->number_of_independent_substreams; i++ ) + { + eac3_substream_info_t *independent_info = i ? &info->independent_info[i] : &info->independent_info_0; + lsmash_bits_put( bits, independent_info->fscod, 2 ); + lsmash_bits_put( bits, independent_info->bsid, 5 ); + lsmash_bits_put( bits, independent_info->bsmod, 5 ); + lsmash_bits_put( bits, independent_info->acmod, 3 ); + lsmash_bits_put( bits, independent_info->lfeon, 1 ); + lsmash_bits_put( bits, 0, 3 ); /* reserved */ + lsmash_bits_put( bits, independent_info->num_dep_sub, 4 ); + if( independent_info->num_dep_sub > 0 ) + lsmash_bits_put( bits, independent_info->chan_loc, 9 ); + else + lsmash_bits_put( bits, 0, 1 ); /* reserved */ + } + uint8_t *dec3 = lsmash_bits_export_data( bits, dec3_length ); + lsmash_bits_empty( bits ); + /* Update box size. */ + dec3[0] = ((*dec3_length) >> 24) & 0xff; + dec3[1] = ((*dec3_length) >> 16) & 0xff; + dec3[2] = ((*dec3_length) >> 8) & 0xff; + dec3[3] = (*dec3_length) & 0xff; + return dec3; +} + +#define IF_EAC3_SYNCWORD( x ) IF_AC3_SYNCWORD( x ) + +static void eac3_update_sample_rate( lsmash_audio_summary_t *summary, mp4sys_eac3_info_t *info ) +{ + /* Additional independent substreams 1 to 7 must be encoded at the same sample rate as independent substream 0. */ + summary->frequency = ac3_sample_rate_table[ info->independent_info_0.fscod ]; + if( summary->frequency == 0 ) + { + static const uint32_t eac3_reduced_sample_rate_table[4] = { 24000, 22050, 16000, 0 }; + summary->frequency = eac3_reduced_sample_rate_table[ info->independent_info_0.fscod2 ]; + } +} + +static void eac3_update_channel_layout( lsmash_audio_summary_t *summary, eac3_substream_info_t *independent_info ) +{ + if( independent_info->chan_loc == 0 ) + { + summary->layout_tag = ac3_channel_layout_table[ independent_info->acmod ][ independent_info->lfeon ]; + return; + } + else if( independent_info->acmod != 0x7 ) + { + summary->layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; + return; + } + /* OK. All L, C, R, Ls and Rs exsist. */ + if( !independent_info->lfeon ) + { + if( independent_info->chan_loc == 0x2 ) + summary->layout_tag = QT_CHANNEL_LAYOUT_EAC_7_0_A; + else if( independent_info->chan_loc == 0x4 ) + summary->layout_tag = QT_CHANNEL_LAYOUT_EAC_6_0_A; + else + summary->layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; + return; + } + /* Also LFE exsists. */ + static const struct + { + uint16_t chan_loc; + lsmash_channel_layout_tag tag; + } eac3_channel_layout_table[] + = { + { 0x1, QT_CHANNEL_LAYOUT_EAC3_7_1_B }, + { 0x2, QT_CHANNEL_LAYOUT_EAC3_7_1_A }, + { 0x4, QT_CHANNEL_LAYOUT_EAC3_6_1_A }, + { 0x8, QT_CHANNEL_LAYOUT_EAC3_6_1_B }, + { 0x10, QT_CHANNEL_LAYOUT_EAC3_7_1_C }, + { 0x10, QT_CHANNEL_LAYOUT_EAC3_7_1_D }, + { 0x40, QT_CHANNEL_LAYOUT_EAC3_7_1_E }, + { 0x80, QT_CHANNEL_LAYOUT_EAC3_6_1_C }, + { 0xc, QT_CHANNEL_LAYOUT_EAC3_7_1_F }, + { 0x84, QT_CHANNEL_LAYOUT_EAC3_7_1_G }, + { 0x88, QT_CHANNEL_LAYOUT_EAC3_7_1_H }, + { 0 } + }; + for( int i = 0; eac3_channel_layout_table[i].chan_loc; i++ ) + if( independent_info->chan_loc == eac3_channel_layout_table[i].chan_loc ) + { + summary->layout_tag = eac3_channel_layout_table[i].tag; + return; + } + summary->layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; +} + +static void eac3_update_channel_info( lsmash_audio_summary_t *summary, mp4sys_eac3_info_t *info ) +{ + summary->channels = 0; + for( int i = 0; i < info->number_of_independent_substreams; i++ ) + { + int channel_count = 0; + eac3_substream_info_t *independent_info = i ? &info->independent_info[i] : &info->independent_info_0; + channel_count = ac3_channel_count_table[ independent_info->acmod ] /* L/C/R/Ls/Rs combination */ + + 2 * !!(independent_info->chan_loc & 0x1) /* Lc/Rc pair */ + + 2 * !!(independent_info->chan_loc & 0x2) /* Lrs/Rrs pair */ + + !!(independent_info->chan_loc & 0x4) /* Cs */ + + !!(independent_info->chan_loc & 0x8) /* Ts */ + + 2 * !!(independent_info->chan_loc & 0x10) /* Lsd/Rsd pair */ + + 2 * !!(independent_info->chan_loc & 0x20) /* Lw/Rw pair */ + + 2 * !!(independent_info->chan_loc & 0x40) /* Lvh/Rvh pair */ + + !!(independent_info->chan_loc & 0x80) /* Cvh */ + + !!(independent_info->chan_loc & 0x100) /* LFE2 */ + + independent_info->lfeon; /* LFE */ + if( channel_count > summary->channels ) + { + /* Pick the maximum number of channels. */ + summary->channels = channel_count; + eac3_update_channel_layout( summary, independent_info ); + } + } +} + +static lsmash_audio_summary_t *eac3_create_summary( mp4sys_eac3_info_t *info ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return NULL; + summary->exdata = eac3_create_dec3( info, &summary->exdata_length ); + if( !summary->exdata ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_EC_3_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_EC_3_AUDIO; /* forbidden to use for ISO Base Media */ + summary->max_au_length = info->syncframe_count_in_au * EAC3_MAX_SYNCFRAME_LENGTH; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->bit_depth = 16; /* no effect */ + summary->samples_in_frame = EAC3_MIN_SAMPLE_DURATION * 6; /* 256 (samples per audio block) * 6 (audio blocks) */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + eac3_update_sample_rate( summary, info ); + eac3_update_channel_info( summary, info ); + return summary; +} + +static int eac3_read_syncframe( mp4sys_importer_t *importer ) +{ + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)importer->info; + uint32_t read_size = fread( info->buffer, 1, EAC3_FIRST_FIVE_BYTES, importer->stream ); + if( read_size == 0 ) + return 1; /* EOF */ + else if( read_size != EAC3_FIRST_FIVE_BYTES ) + return -1; + IF_EAC3_SYNCWORD( info->buffer ) + return -1; + if( eac3_parse_syncframe_header( importer ) ) + return -1; + return 0; +} + +static int eac3_get_next_accessunit_internal( mp4sys_importer_t *importer ) +{ + static const uint8_t audio_block_table[4] = { 1, 2, 3, 6 }; + int complete_au = 0; + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)importer->info; + while( 1 ) + { + int ret = eac3_read_syncframe( importer ); + if( ret == -1 ) + return -1; + else if( ret == 1 ) + { + /* According to ETSI TS 102 366 V1.2.1 (2008-08), + * one access unit consists of 6 audio blocks and begins with independent substream 0. + * The specification doesn't mention the case where a enhanced AC-3 stream ends at non-mod6 audio blocks. + * At the end of the stream, therefore, we might make an access unit which has less than 6 audio blocks anyway. */ + info->status = MP4SYS_IMPORTER_EOF; + complete_au = 1; + } + else + { + int independent = info->strmtyp != 0x1; + if( independent && info->substreamid == 0x0 ) + { + if( info->number_of_audio_blocks == 6 ) + { + /* Encountered the first syncframe of the next access unit. */ + info->number_of_audio_blocks = 0; + complete_au = 1; + } + else if( info->number_of_audio_blocks > 6 ) + return -1; + info->number_of_independent_substreams = 0; + info->number_of_audio_blocks += audio_block_table[ info->numblkscod ]; + } + else if( info->syncframe_count == 0 ) + /* The first syncframe in an AU must be independent and assigned substream ID 0. */ + return -1; + if( independent ) + info->independent_info[info->number_of_independent_substreams ++].num_dep_sub = 0; + else + ++ info->independent_info[info->number_of_independent_substreams - 1].num_dep_sub; + } + if( complete_au ) + { + memcpy( info->au, info->incomplete_au, info->incomplete_au_length ); + info->au_length = info->incomplete_au_length; + info->incomplete_au_length = 0; + info->syncframe_count_in_au = info->syncframe_count; + info->syncframe_count = 0; + if( info->status == MP4SYS_IMPORTER_EOF ) + break; + } + if( info->incomplete_au_length + info->frame_size > info->au_buffers->buffer_size ) + { + /* Increase buffer size to store AU. */ + lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( info->au_buffers, info->au_buffers->buffer_size + EAC3_MAX_SYNCFRAME_LENGTH ); + if( !temp ) + return -1; + info->au_buffers = temp; + info->au = lsmash_withdraw_buffer( info->au_buffers, 1 ); + info->incomplete_au = lsmash_withdraw_buffer( info->au_buffers, 2 ); + } + memcpy( info->incomplete_au + info->incomplete_au_length, info->buffer, info->frame_size ); + info->incomplete_au_length += info->frame_size; + ++info->syncframe_count; + if( complete_au ) + break; + } + return 0; +} + +static int mp4sys_eac3_get_accessunit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return -1; + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)importer->info; + mp4sys_importer_status current_status = info->status; + if( current_status == MP4SYS_IMPORTER_EOF && info->au_length == 0 ) + { + buffered_sample->length = 0; + return 0; + } + if( current_status == MP4SYS_IMPORTER_CHANGE ) + { + free( summary->exdata ); + summary->exdata = info->next_dec3; + summary->exdata_length = info->next_dec3_length; + summary->max_au_length = info->syncframe_count_in_au * EAC3_MAX_SYNCFRAME_LENGTH; + eac3_update_sample_rate( summary, info ); + eac3_update_channel_info( summary, info ); + } + memcpy( buffered_sample->data, info->au, info->au_length ); + buffered_sample->length = info->au_length; + buffered_sample->dts = info->au_number++ * summary->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + buffered_sample->prop.pre_roll.distance = 1; /* MDCT */ + if( info->status == MP4SYS_IMPORTER_EOF ) + { + info->au_length = 0; + return 0; + } + uint32_t old_syncframe_count_in_au = info->syncframe_count_in_au; + if( eac3_get_next_accessunit_internal( importer ) ) + return -1; + if( info->syncframe_count_in_au ) + { + uint32_t new_length; + uint8_t *dec3 = eac3_create_dec3( info, &new_length ); + if( !dec3 ) + return -1; + if( (info->syncframe_count_in_au > old_syncframe_count_in_au) + || (new_length != summary->exdata_length || memcmp( dec3, summary->exdata, summary->exdata_length )) ) + { + info->status = MP4SYS_IMPORTER_CHANGE; + info->next_dec3 = dec3; + info->next_dec3_length = new_length; + } + else + { + info->status = MP4SYS_IMPORTER_OK; + free( dec3 ); + } + } + return current_status; +} + +static int mp4sys_eac3_probe( mp4sys_importer_t* importer ) +{ + mp4sys_eac3_info_t *info = mp4sys_create_eac3_info(); + if( !info ) + return -1; + importer->info = info; + if( eac3_get_next_accessunit_internal( importer ) ) + { + mp4sys_remove_eac3_info( importer->info ); + importer->info = NULL; + return -1; + } + lsmash_audio_summary_t *summary = eac3_create_summary( info ); + if( !summary ) + { + mp4sys_remove_eac3_info( importer->info ); + importer->info = NULL; + return -1; + } + if( info->status != MP4SYS_IMPORTER_EOF ) + info->status = MP4SYS_IMPORTER_OK; + info->au_number = 0; + if( lsmash_add_entry( importer->summaries, summary ) ) + { + mp4sys_remove_eac3_info( importer->info ); + importer->info = NULL; + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + return 0; +} + +static uint32_t mp4sys_eac3_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_eac3_info_t *info = (mp4sys_eac3_info_t *)importer->info; + if( !info || track_number != 1 + || info->status != MP4SYS_IMPORTER_EOF || info->au_length != 0 ) + return 0; + return EAC3_MIN_SAMPLE_DURATION * info->number_of_audio_blocks; +} + +const static mp4sys_importer_functions mp4sys_eac3_importer = +{ + "eac3", + 1, + mp4sys_eac3_probe, + mp4sys_eac3_get_accessunit, + mp4sys_eac3_get_last_delta, + mp4sys_eac3_cleanup +}; + +/*************************************************************************** + MPEG-4 ALS importer +***************************************************************************/ +#define ALSSC_TWELVE_LENGTH 22 + +typedef struct +{ + uint32_t size; + uint32_t samp_freq; + uint32_t samples; + uint32_t channels; + uint16_t frame_length; + uint8_t resolution; + uint8_t random_access; + uint8_t ra_flag; + uint32_t access_unit_size; + uint32_t number_of_ra_units; + uint32_t *ra_unit_size; + uint8_t *sc_data; +} als_specific_config_t; + +typedef struct +{ + mp4sys_importer_status status; + als_specific_config_t alssc; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4sys_als_info_t; + +typedef struct +{ + FILE *stream; + uint32_t pos; + uint32_t buffer_size; + uint8_t *buffer; + uint8_t *end; +} als_stream_manager; + +static void mp4sys_remove_als_info( mp4sys_als_info_t *info ) +{ + if( info->alssc.ra_unit_size ) + free( info->alssc.ra_unit_size ); + if( info->alssc.sc_data ) + free( info->alssc.sc_data ); + free( info ); +} + +static void mp4sys_als_cleanup( mp4sys_importer_t *importer ) +{ + debug_if( importer && importer->info ) + mp4sys_remove_als_info( importer->info ); +} + +static int als_stream_read( als_stream_manager *manager, uint32_t read_size ) +{ + if( manager->buffer + manager->buffer_size >= manager->end ) + { + uint8_t *temp = realloc( manager->buffer, manager->buffer_size + read_size ); + if( !temp ) + return -1; + manager->buffer = temp; + manager->buffer_size += read_size; + } + uint32_t actual_read_size = fread( manager->buffer + manager->pos, 1, read_size, manager->stream ); + if( actual_read_size == 0 ) + return -1; + manager->end = manager->buffer + manager->pos + actual_read_size; + return 0; +} + +static int als_cleanup_stream_manager( als_stream_manager *manager ) +{ + free( manager->buffer ); + return -1; +} + +static uint32_t als_get_be32( als_stream_manager *manager ) +{ + uint32_t value = (manager->buffer[ manager->pos ] << 24) + | (manager->buffer[ manager->pos + 1 ] << 16) + | (manager->buffer[ manager->pos + 2 ] << 8) + | manager->buffer[ manager->pos + 3 ]; + manager->pos += 4; + return value; +} + +static int als_parse_specific_config( mp4sys_importer_t *importer, uint8_t *buf, als_specific_config_t *alssc ) +{ + alssc->samp_freq = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + alssc->samples = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11]; + if( alssc->samples == 0xffffffff ) + return -1; /* We don't support this case. */ + alssc->channels = (buf[12] << 8) | buf[13]; + alssc->resolution = (buf[14] & 0x1c) >> 2; + if( alssc->resolution > 3 ) + return -1; /* reserved */ + alssc->frame_length = (buf[15] << 8) | buf[16]; + alssc->random_access = buf[17]; + alssc->ra_flag = (buf[18] & 0xc0) >> 6; + if( alssc->ra_flag == 0 ) + return -1; /* We don't support this case. */ + buf[18] &= 0x3f; /* Set 0 to ra_flag. We will remove ra_unit_size in each access unit. */ +#if 0 + if( alssc->samples == 0xffffffff && alssc->ra_flag == 2 ) + return -1; +#endif + int chan_sort = !!(buf[20] & 0x1); + if( alssc->channels == 0 ) + { + if( buf[20] & 0x8 ) + return -1; /* If channels = 0 (mono), joint_stereo = 0. */ + else if( buf[20] & 0x4 ) + return -1; /* If channels = 0 (mono), mc_coding = 0. */ + else if( chan_sort ) + return -1; /* If channels = 0 (mono), chan_sort = 0. */ + } + int chan_config = !!(buf[20] & 0x2); + int crc_enabled = !!(buf[21] & 0x80); + int aux_data_enabled = !!(buf[21] & 0x1); + uint32_t read_size = 0; + if( chan_config ) + read_size += 2; /* chan_config_info */ + if( chan_sort ) + { + uint32_t ChBits; + for( ChBits = 1; alssc->channels >> ChBits; ChBits++ ); + uint32_t chan_pos_length = (alssc->channels + 1) * ChBits; + read_size += chan_pos_length / 8 + !!(chan_pos_length % 8); + } + /* Set up stream manager. */ + als_stream_manager manager; + manager.stream = importer->stream; + manager.buffer_size = ALSSC_TWELVE_LENGTH; + manager.buffer = malloc( manager.buffer_size ); + if( !manager.buffer ) + return -1; + manager.pos = ALSSC_TWELVE_LENGTH + read_size; + manager.end = manager.buffer + manager.buffer_size; + memcpy( manager.buffer, buf, ALSSC_TWELVE_LENGTH ); + /* Continue to read and parse. */ + read_size += 8; /* header_size and trailer_size */ + if( als_stream_read( &manager, read_size ) ) + return als_cleanup_stream_manager( &manager ); + uint32_t header_size = als_get_be32( &manager ); + uint32_t trailer_size = als_get_be32( &manager ); + read_size = header_size * (header_size != 0xffffffff) + trailer_size * (trailer_size != 0xffffffff) + 4 * crc_enabled; + if( als_stream_read( &manager, read_size ) ) + return -1; + manager.pos += read_size; /* Skip orig_header, orig_trailer and crc. */ + /* Random access unit */ + uint32_t number_of_frames = (alssc->samples / (alssc->frame_length + 1)) + !!(alssc->samples % (alssc->frame_length + 1)); + if( alssc->random_access != 0 ) + alssc->number_of_ra_units = number_of_frames / alssc->random_access + !!(number_of_frames % alssc->random_access); + else + alssc->number_of_ra_units = 0; + if( alssc->ra_flag == 2 && alssc->random_access != 0 ) + { + uint32_t pos = manager.pos; + read_size = alssc->number_of_ra_units * 4; + if( als_stream_read( &manager, read_size ) ) + return als_cleanup_stream_manager( &manager ); + alssc->ra_unit_size = malloc( alssc->number_of_ra_units * sizeof(uint32_t) ); + if( !alssc->ra_unit_size ) + return als_cleanup_stream_manager( &manager ); + for( uint32_t i = 0; i < alssc->number_of_ra_units; i++ ) + alssc->ra_unit_size[i] = als_get_be32( &manager ); + manager.pos = pos; /* Remove ra_unit_size. */ + } + else + alssc->ra_unit_size = NULL; + /* auxiliary data */ + if( aux_data_enabled ) + { + if( als_stream_read( &manager, 4 ) ) + return als_cleanup_stream_manager( &manager ); + uint32_t aux_size = als_get_be32( &manager ); + read_size = aux_size * (aux_size != 0xffffffff); + if( als_stream_read( &manager, read_size ) ) + return als_cleanup_stream_manager( &manager ); + manager.pos += read_size; + } + /* Copy ALSSpecificConfig. */ + alssc->size = manager.pos; + alssc->sc_data = malloc( alssc->size ); + if( !alssc->sc_data ) + return als_cleanup_stream_manager( &manager ); + memcpy( alssc->sc_data, manager.buffer, alssc->size ); + als_cleanup_stream_manager( &manager ); + return 0; +} + +static int mp4sys_als_get_accessunit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return -1; + mp4sys_als_info_t *info = (mp4sys_als_info_t *)importer->info; + mp4sys_importer_status current_status = info->status; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + als_specific_config_t *alssc = &info->alssc; + if( alssc->number_of_ra_units == 0 ) + { + if( fread( buffered_sample->data, 1, alssc->access_unit_size, importer->stream ) != alssc->access_unit_size ) + return -1; + buffered_sample->length = alssc->access_unit_size; + buffered_sample->cts = buffered_sample->dts = 0; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + info->status = MP4SYS_IMPORTER_EOF; + return 0; + } + uint32_t au_length; + if( alssc->ra_flag == 2 ) + { + au_length = alssc->ra_unit_size[info->au_number]; + if( fread( buffered_sample->data, 1, au_length, importer->stream ) != au_length ) + return -1; + } + else /* if( alssc->ra_flag == 1 ) */ + { + uint8_t temp[4]; + if( fread( temp, 1, 4, importer->stream ) != 4 ) + return -1; + au_length = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]; /* We remove ra_unit_size. */ + if( fread( buffered_sample->data, 1, au_length, importer->stream ) != au_length ) + return -1; + } + buffered_sample->length = au_length; + buffered_sample->dts = info->au_number++ * info->samples_in_frame; + buffered_sample->cts = buffered_sample->dts; + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + if( info->au_number == alssc->number_of_ra_units ) + info->status = MP4SYS_IMPORTER_EOF; + return 0; +} + +static lsmash_audio_summary_t *als_create_summary( mp4sys_importer_t *importer, als_specific_config_t *alssc ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_AudioStream ); + if( !summary ) + return NULL; + summary->exdata = lsmash_memdup( alssc->sc_data, alssc->size ); + if( !summary->exdata ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + summary->exdata_length = alssc->size; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_ALS; + summary->frequency = alssc->samp_freq; + summary->channels = alssc->channels + 1; + summary->bit_depth = (alssc->resolution + 1) * 8; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + if( alssc->random_access != 0 ) + { + summary->samples_in_frame = (alssc->frame_length + 1) * alssc->random_access; + summary->max_au_length = summary->channels * (summary->bit_depth / 8) * summary->samples_in_frame; + } + else + { + summary->samples_in_frame = 0; /* hack for mp4sys_als_get_last_delta */ + uint64_t pos = lsmash_ftell( importer->stream ); + lsmash_fseek( importer->stream, 0, SEEK_END ); + summary->max_au_length = alssc->access_unit_size = lsmash_ftell( importer->stream ) - pos; + lsmash_fseek( importer->stream, pos, SEEK_SET ); + } + if( lsmash_setup_AudioSpecificConfig( summary ) ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + return summary; +} + +static int mp4sys_als_probe( mp4sys_importer_t *importer ) +{ + uint8_t buf[ALSSC_TWELVE_LENGTH]; + if( fread( buf, 1, ALSSC_TWELVE_LENGTH, importer->stream ) != ALSSC_TWELVE_LENGTH ) + return -1; + /* Check ALS identifier( = 0x414C5300). */ + if( buf[0] != 0x41 || buf[1] != 0x4C || buf[2] != 0x53 || buf[3] != 0x00 ) + return -1; + als_specific_config_t alssc; + if( als_parse_specific_config( importer, buf, &alssc ) ) + return -1; + lsmash_audio_summary_t *summary = als_create_summary( importer, &alssc ); + if( !summary ) + return -1; + /* importer status */ + mp4sys_als_info_t *info = lsmash_malloc_zero( sizeof(mp4sys_als_info_t) ); + if( !info ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + info->status = MP4SYS_IMPORTER_OK; + info->alssc = alssc; + info->samples_in_frame = summary->samples_in_frame; + if( lsmash_add_entry( importer->summaries, summary ) ) + { + free( info ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return -1; + } + importer->info = info; + return 0; +} + +static uint32_t mp4sys_als_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_als_info_t *info = (mp4sys_als_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + als_specific_config_t *alssc = &info->alssc; + /* If alssc->number_of_ra_units == 0, then the last sample duration is just alssc->samples + * since als_create_summary sets 0 to summary->samples_in_frame i.e. info->samples_in_frame. */ + return alssc->samples - (alssc->number_of_ra_units - 1) * info->samples_in_frame; +} + +const static mp4sys_importer_functions mp4sys_als_importer = +{ + "als", + 1, + mp4sys_als_probe, + mp4sys_als_get_accessunit, + mp4sys_als_get_last_delta, + mp4sys_als_cleanup +}; + +/*************************************************************************** + H.264 importer +***************************************************************************/ +typedef struct +{ + uint8_t nal_ref_idc; + uint8_t nal_unit_type; + uint8_t length; +} h264_nalu_header_t; + +typedef struct +{ + uint16_t sar_width; + uint16_t sar_height; + uint8_t video_full_range_flag; + uint8_t colour_primaries; + uint8_t transfer_characteristics; + uint8_t matrix_coefficients; + uint32_t num_units_in_tick; + uint32_t time_scale; + uint8_t fixed_frame_rate_flag; +} h264_vui_t; + +typedef struct +{ + uint8_t present; + uint8_t profile_idc; + uint8_t constraint_set_flags; + uint8_t level_idc; + uint8_t seq_parameter_set_id; + uint8_t chroma_format_idc; + uint8_t separate_colour_plane_flag; + uint8_t ChromaArrayType; + uint8_t bit_depth_luma_minus8; + uint8_t bit_depth_chroma_minus8; + uint8_t pic_order_cnt_type; + uint8_t delta_pic_order_always_zero_flag; + uint8_t num_ref_frames_in_pic_order_cnt_cycle; + uint8_t frame_mbs_only_flag; + uint8_t hrd_present; + int32_t offset_for_non_ref_pic; + int32_t offset_for_top_to_bottom_field; + int32_t offset_for_ref_frame[255]; + int64_t ExpectedDeltaPerPicOrderCntCycle; + uint32_t max_num_ref_frames; + uint32_t log2_max_frame_num; + uint32_t MaxFrameNum; + uint32_t log2_max_pic_order_cnt_lsb; + uint32_t MaxPicOrderCntLsb; + uint32_t PicSizeInMapUnits; + uint32_t cropped_width; + uint32_t cropped_height; + h264_vui_t vui; +} h264_sps_t; + +typedef struct +{ + uint8_t present; + uint8_t pic_parameter_set_id; + uint8_t seq_parameter_set_id; + uint8_t entropy_coding_mode_flag; + uint8_t bottom_field_pic_order_in_frame_present_flag; + uint8_t weighted_pred_flag; + uint8_t weighted_bipred_idc; + uint8_t deblocking_filter_control_present_flag; + uint8_t redundant_pic_cnt_present_flag; + uint32_t SliceGroupChangeRate; +} h264_pps_t; + +typedef struct +{ + uint8_t present; + uint8_t random_accessible; + uint32_t recovery_frame_cnt; +} h264_sei_t; + +typedef struct +{ + uint8_t present; + uint8_t type; + uint8_t pic_order_cnt_type; + uint8_t nal_ref_idc; + uint8_t IdrPicFlag; + uint8_t pic_parameter_set_id; + uint8_t field_pic_flag; + uint8_t bottom_field_flag; + uint8_t has_mmco5; + uint8_t has_redundancy; + uint16_t idr_pic_id; + uint32_t frame_num; + int32_t pic_order_cnt_lsb; + int32_t delta_pic_order_cnt_bottom; + int32_t delta_pic_order_cnt[2]; +} h264_slice_info_t; + +typedef struct +{ + uint8_t type; + uint8_t idr; + uint8_t random_accessible; + uint8_t non_bipredictive; + uint8_t independent; + uint8_t disposable; /* 0: nal_ref_idc != 0, 1: otherwise */ + uint8_t has_redundancy; + uint8_t incomplete_au_has_primary; + uint8_t pic_parameter_set_id; + uint8_t field_pic_flag; + uint8_t bottom_field_flag; + /* POC */ + uint8_t has_mmco5; + uint8_t ref_pic_has_mmco5; + uint8_t ref_pic_bottom_field_flag; + int32_t ref_pic_TopFieldOrderCnt; + int32_t ref_pic_PicOrderCntMsb; + int32_t ref_pic_PicOrderCntLsb; + int32_t pic_order_cnt_lsb; + int32_t delta_pic_order_cnt_bottom; + int32_t delta_pic_order_cnt[2]; + int32_t PicOrderCnt; + /* */ + uint32_t recovery_frame_cnt; + uint32_t frame_num; + uint32_t FrameNumOffset; + uint8_t *au; + uint32_t au_length; + uint8_t *incomplete_au; + uint32_t incomplete_au_length; + uint32_t au_number; +} h264_picture_info_t; + +typedef struct +{ + mp4sys_importer_status status; + lsmash_video_summary_t *summary; + h264_nalu_header_t nalu_header; + uint8_t prev_nalu_type; + uint8_t composition_reordering_present; + uint8_t no_more_read; + uint8_t first_summary; + h264_sps_t sps; + h264_pps_t pps; + h264_sei_t sei; + isom_avcC_t avcC; + h264_slice_info_t slice; + h264_picture_info_t picture; + lsmash_bits_t *bits; + lsmash_multiple_buffers_t *buffers; + uint8_t *rbsp_buffer; + uint8_t *stream_buffer; + uint8_t *stream_buffer_pos; + uint8_t *stream_buffer_end; + uint64_t ebsp_head_pos; + uint32_t max_au_length; + uint32_t num_undecodable; + uint64_t last_intra_cts; + lsmash_media_ts_list_t ts_list; +} mp4sys_h264_info_t; + +enum +{ + H264_SLICE_TYPE_P = 0, + H264_SLICE_TYPE_B = 1, + H264_SLICE_TYPE_I = 2, + H264_SLICE_TYPE_SP = 3, + H264_SLICE_TYPE_SI = 4 +} h264_slice_type; + +enum +{ + H264_PICTURE_TYPE_I = 0, + H264_PICTURE_TYPE_I_P = 1, + H264_PICTURE_TYPE_I_P_B = 2, + H264_PICTURE_TYPE_SI = 3, + H264_PICTURE_TYPE_SI_SP = 4, + H264_PICTURE_TYPE_I_SI = 5, + H264_PICTURE_TYPE_I_SI_P_SP = 6, + H264_PICTURE_TYPE_I_SI_P_SP_B = 7, + H264_PICTURE_TYPE_NONE = 8, +} h264_picture_type; + +static void mp4sys_remove_h264_info( mp4sys_h264_info_t *info ) +{ + if( !info ) + return; + lsmash_remove_list( info->avcC.sequenceParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( info->avcC.pictureParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( info->avcC.sequenceParameterSetExt, isom_remove_avcC_ps ); + lsmash_bits_adhoc_cleanup( info->bits ); + lsmash_destroy_multiple_buffers( info->buffers ); + if( info->ts_list.timestamp ) + free( info->ts_list.timestamp ); + free( info ); +} + +static mp4sys_h264_info_t *mp4sys_create_h264_info( void ) +{ +#define H264_DEFAULT_BUFFER_SIZE (1<<16) + mp4sys_h264_info_t *info = lsmash_malloc_zero( sizeof(mp4sys_h264_info_t) ); + if( !info ) + return NULL; + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + mp4sys_remove_h264_info( info ); + return NULL; + } + isom_avcC_t *avcC = &info->avcC; + avcC->type = ISOM_BOX_TYPE_AVCC; + avcC->sequenceParameterSets = lsmash_create_entry_list(); + if( !avcC->sequenceParameterSets ) + { + mp4sys_remove_h264_info( info ); + return NULL; + } + avcC->pictureParameterSets = lsmash_create_entry_list(); + if( !avcC->pictureParameterSets ) + { + mp4sys_remove_h264_info( info ); + return NULL; + } + info->buffers = lsmash_create_multiple_buffers( 4, H264_DEFAULT_BUFFER_SIZE ); + if( !info->buffers ) + { + mp4sys_remove_h264_info( info ); + return NULL; + } + info->stream_buffer = lsmash_withdraw_buffer( info->buffers, 1 ); + info->rbsp_buffer = lsmash_withdraw_buffer( info->buffers, 2 ); + info->picture.au = lsmash_withdraw_buffer( info->buffers, 3 ); + info->picture.incomplete_au = lsmash_withdraw_buffer( info->buffers, 4 ); + return info; +#undef H264_DEFAULT_BUFFER_SIZE +} + +static void mp4sys_h264_cleanup( mp4sys_importer_t *importer ) +{ + debug_if( importer && importer->info ) + mp4sys_remove_h264_info( importer->info ); +} + +static inline uint64_t h264_get_codeNum( lsmash_bits_t *bits ) +{ + uint32_t leadingZeroBits = 0; + for( int b = 0; !b; leadingZeroBits++ ) + b = lsmash_bits_get( bits, 1 ); + --leadingZeroBits; + return ((uint64_t)1 << leadingZeroBits) - 1 + lsmash_bits_get( bits, leadingZeroBits ); +} + +static inline uint64_t h264_decode_exp_golomb_ue( uint64_t codeNum ) +{ + return codeNum; +} + +static inline int64_t h264_decode_exp_golomb_se( uint64_t codeNum ) +{ + if( codeNum & 1 ) + return (int64_t)((codeNum >> 1) + 1); + return -1 * (int64_t)(codeNum >> 1); +} + +static uint64_t h264_get_exp_golomb_ue( lsmash_bits_t *bits ) +{ + uint64_t codeNum = h264_get_codeNum( bits ); + return h264_decode_exp_golomb_ue( codeNum ); +} + +static uint64_t h264_get_exp_golomb_se( lsmash_bits_t *bits ) +{ + uint64_t codeNum = h264_get_codeNum( bits ); + return h264_decode_exp_golomb_se( codeNum ); +} + +/* Convert EBSP (Encapsulated Byte Sequence Packets) to RBSP (Raw Byte Sequence Packets). */ +static void h264_remove_emulation_prevention( uint8_t *src, uint64_t src_length, uint8_t **p_dst ) +{ + uint8_t *src_end = src + src_length; + uint8_t *dst = *p_dst; + while( src < src_end ) + if( ((src + 2) < src_end) && !src[0] && !src[1] && (src[2] == 0x03) ) + { + *dst++ = *src++; + *dst++ = *src++; + src++; /* Skip emulation_prevention_three_byte (0x03). */ + } + else + *dst++ = *src++; + *p_dst = dst; +} + + +#define IF_INVALID_VALUE( x ) if( x ) +#define IF_EXCEED_INT32( x ) if( (x) < INT32_MIN || (x) > INT32_MAX ) + +static int h264_check_more_rbsp_data( lsmash_bits_t *bits ) +{ + lsmash_bs_t *bs = bits->bs; + if( bs->pos < bs->store && !(bits->store == 0 && (bs->store == bs->pos + 1)) ) + return 1; /* rbsp_trailing_bits will be placed at the next or later byte. + * Note: bs->pos points at the next byte if bits->store isn't empty. */ + if( bits->store == 0 ) + { + if( bs->store == bs->pos + 1 ) + return bs->data[ bs->pos ] != 0x80; + /* No rbsp_trailing_bits is present in RBSP data. */ + bs->error = 1; + return 0; + } + /* Check whether remainder of bits is identical to rbsp_trailing_bits. */ + uint8_t remainder_bits = bits->cache & ~(~0U << bits->store); + uint8_t rbsp_trailing_bits = 1U << (bits->store - 1); + return remainder_bits != rbsp_trailing_bits; +} + +static int h264_check_nalu_header( h264_nalu_header_t *nalu_header, uint8_t **p_buf_pos, int use_long_start_code ) +{ + uint8_t *buf_pos = *p_buf_pos; + uint8_t forbidden_zero_bit = (*buf_pos >> 7) & 0x01; + uint8_t nal_ref_idc = nalu_header->nal_ref_idc = (*buf_pos >> 5) & 0x03; + uint8_t nal_unit_type = nalu_header->nal_unit_type = *buf_pos & 0x1f; + nalu_header->length = 1; + *p_buf_pos = buf_pos + nalu_header->length; + if( nal_unit_type == 14 || nal_unit_type == 20 ) + return -1; /* We don't support yet. */ + IF_INVALID_VALUE( forbidden_zero_bit ) + return -1; + /* SPS and PPS require long start code (0x00000001). + * Also AU delimiter requires it too because this type of NALU shall be the first NALU of any AU if present. */ + IF_INVALID_VALUE( !use_long_start_code && (nal_unit_type == 7 || nal_unit_type == 8 || nal_unit_type == 9) ) + return -1; + if( nal_ref_idc ) + { + /* nal_ref_idc shall be equal to 0 for all NALUs having nal_unit_type equal to 6, 9, 10, 11, or 12. */ + IF_INVALID_VALUE( nal_unit_type == 6 || nal_unit_type == 9 || nal_unit_type == 10 || nal_unit_type == 11 || nal_unit_type == 12 ) + return -1; + } + else + /* nal_ref_idc shall not be equal to 0 for NALUs with nal_unit_type equal to 5. */ + IF_INVALID_VALUE( nal_unit_type == 5 ) + return -1; + return 0; +} + +static int h264_parse_scaling_list( lsmash_bits_t *bits, int sizeOfScalingList ) +{ + /* scaling_list( scalingList, sizeOfScalingList, useDefaultScalingMatrixFlag ) */ + int nextScale = 8; + for( int i = 0; i < sizeOfScalingList; i++ ) + { + int64_t delta_scale = h264_get_exp_golomb_se( bits ); + IF_INVALID_VALUE( delta_scale < -128 || delta_scale > 127 ) + return -1; + nextScale = (nextScale + delta_scale + 256) % 256; + if( nextScale == 0 ) + break; + } + return 0; +} + +static int h264_parse_hrd_parameters( lsmash_bits_t *bits ) +{ + /* hrd_parameters() */ + uint64_t cpb_cnt_minus1 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( cpb_cnt_minus1 > 31 ) + return -1; + lsmash_bits_get( bits, 4 ); /* bit_rate_scale */ + lsmash_bits_get( bits, 4 ); /* cpb_size_scale */ + for( uint64_t SchedSelIdx = 0; SchedSelIdx <= cpb_cnt_minus1; SchedSelIdx++ ) + { + h264_get_exp_golomb_ue( bits ); /* bit_rate_value_minus1[ SchedSelIdx ] */ + h264_get_exp_golomb_ue( bits ); /* cpb_size_value_minus1[ SchedSelIdx ] */ + lsmash_bits_get( bits, 1 ); /* cbr_flag [ SchedSelIdx ] */ + } + lsmash_bits_get( bits, 5 ); /* initial_cpb_removal_delay_length_minus1 */ + lsmash_bits_get( bits, 5 ); /* cpb_removal_delay_length_minus1 */ + lsmash_bits_get( bits, 5 ); /* dpb_output_delay_length_minus1 */ + lsmash_bits_get( bits, 5 ); /* time_offset_length */ + return 0; +} + +static int h264_parse_sps_nalu( lsmash_bits_t *bits, h264_sps_t *sps, h264_nalu_header_t *nalu_header, + uint8_t *rbsp_buffer, uint8_t *ebsp, uint64_t ebsp_size ) +{ + uint8_t *rbsp_start = rbsp_buffer; + h264_remove_emulation_prevention( ebsp, ebsp_size, &rbsp_buffer ); + uint64_t rbsp_length = rbsp_buffer - rbsp_start; + if( lsmash_bits_import_data( bits, rbsp_start, rbsp_length ) ) + return -1; + memset( sps, 0, sizeof(h264_sps_t) ); + /* seq_parameter_set_data() */ + sps->profile_idc = lsmash_bits_get( bits, 8 ); + sps->constraint_set_flags = lsmash_bits_get( bits, 8 ); + sps->level_idc = lsmash_bits_get( bits, 8 ); + uint64_t seq_parameter_set_id = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( seq_parameter_set_id > 31 ) + return -1; + sps->seq_parameter_set_id = seq_parameter_set_id; + if( sps->profile_idc == 100 || sps->profile_idc == 110 || sps->profile_idc == 122 + || sps->profile_idc == 244 || sps->profile_idc == 44 || sps->profile_idc == 83 + || sps->profile_idc == 86 || sps->profile_idc == 118 || sps->profile_idc == 128 ) + { + sps->chroma_format_idc = h264_get_exp_golomb_ue( bits ); + if( sps->chroma_format_idc == 3 ) + sps->separate_colour_plane_flag = lsmash_bits_get( bits, 1 ); + uint64_t bit_depth_luma_minus8 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( bit_depth_luma_minus8 > 6 ) + return -1; + uint64_t bit_depth_chroma_minus8 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( bit_depth_chroma_minus8 > 6 ) + return -1; + sps->bit_depth_luma_minus8 = bit_depth_luma_minus8; + sps->bit_depth_chroma_minus8 = bit_depth_chroma_minus8; + lsmash_bits_get( bits, 1 ); /* qpprime_y_zero_transform_bypass_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* seq_scaling_matrix_present_flag */ + { + int num_loops = sps->chroma_format_idc != 3 ? 8 : 12; + for( int i = 0; i < num_loops; i++ ) + if( lsmash_bits_get( bits, 1 ) /* seq_scaling_list_present_flag[i] */ + && h264_parse_scaling_list( bits, i < 6 ? 16 : 64 ) ) + return -1; + } + } + else + { + sps->chroma_format_idc = 1; + sps->separate_colour_plane_flag = 0; + sps->bit_depth_luma_minus8 = 0; + sps->bit_depth_chroma_minus8 = 0; + } + sps->ChromaArrayType = sps->separate_colour_plane_flag ? 0 : sps->chroma_format_idc; + uint64_t log2_max_frame_num_minus4 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( log2_max_frame_num_minus4 > 12 ) + return -1; + sps->log2_max_frame_num = log2_max_frame_num_minus4 + 4; + sps->MaxFrameNum = 1 << sps->log2_max_frame_num; + uint64_t pic_order_cnt_type = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( pic_order_cnt_type > 2 ) + return -1; + sps->pic_order_cnt_type = pic_order_cnt_type; + if( sps->pic_order_cnt_type == 0 ) + { + uint64_t log2_max_pic_order_cnt_lsb_minus4 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( log2_max_pic_order_cnt_lsb_minus4 > 12 ) + return -1; + sps->log2_max_pic_order_cnt_lsb = log2_max_pic_order_cnt_lsb_minus4 + 4; + sps->MaxPicOrderCntLsb = 1 << sps->log2_max_pic_order_cnt_lsb; + } + else if( sps->pic_order_cnt_type == 1 ) + { + sps->delta_pic_order_always_zero_flag = lsmash_bits_get( bits, 1 ); + int64_t max_value = ((uint64_t)1 << 31) - 1; + int64_t min_value = -((uint64_t)1 << 31) + 1; + int64_t offset_for_non_ref_pic = h264_get_exp_golomb_se( bits ); + if( offset_for_non_ref_pic < min_value || offset_for_non_ref_pic > max_value ) + return -1; + sps->offset_for_non_ref_pic = offset_for_non_ref_pic; + int64_t offset_for_top_to_bottom_field = h264_get_exp_golomb_se( bits ); + if( offset_for_top_to_bottom_field < min_value || offset_for_top_to_bottom_field > max_value ) + return -1; + sps->offset_for_top_to_bottom_field = offset_for_top_to_bottom_field; + uint64_t num_ref_frames_in_pic_order_cnt_cycle = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( num_ref_frames_in_pic_order_cnt_cycle > 255 ) + return -1; + sps->num_ref_frames_in_pic_order_cnt_cycle = num_ref_frames_in_pic_order_cnt_cycle; + sps->ExpectedDeltaPerPicOrderCntCycle = 0; + for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) + { + int64_t offset_for_ref_frame = h264_get_exp_golomb_se( bits ); + if( offset_for_ref_frame < min_value || offset_for_ref_frame > max_value ) + return -1; + sps->offset_for_ref_frame[i] = offset_for_ref_frame; + sps->ExpectedDeltaPerPicOrderCntCycle += offset_for_ref_frame; + } + } + sps->max_num_ref_frames = h264_get_exp_golomb_ue( bits ); + lsmash_bits_get( bits, 1 ); /* gaps_in_frame_num_value_allowed_flag */ + uint64_t pic_width_in_mbs_minus1 = h264_get_exp_golomb_ue( bits ); + uint64_t pic_height_in_map_units_minus1 = h264_get_exp_golomb_ue( bits ); + sps->frame_mbs_only_flag = lsmash_bits_get( bits, 1 ); + if( !sps->frame_mbs_only_flag ) + lsmash_bits_get( bits, 1 ); /* mb_adaptive_frame_field_flag */ + lsmash_bits_get( bits, 1 ); /* direct_8x8_inference_flag */ + uint64_t PicWidthInMbs = pic_width_in_mbs_minus1 + 1; + uint64_t PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1; + sps->PicSizeInMapUnits = PicWidthInMbs * PicHeightInMapUnits; + sps->cropped_width = PicWidthInMbs * 16; + sps->cropped_height = (2 - sps->frame_mbs_only_flag) * PicHeightInMapUnits * 16; + if( lsmash_bits_get( bits, 1 ) ) /* frame_cropping_flag */ + { + uint8_t CropUnitX; + uint8_t CropUnitY; + if( sps->ChromaArrayType == 0 ) + { + CropUnitX = 1; + CropUnitY = 2 - sps->frame_mbs_only_flag; + } + else + { + static const int SubWidthC [] = { 0, 2, 2, 1 }; + static const int SubHeightC[] = { 0, 2, 1, 1 }; + CropUnitX = SubWidthC [ sps->chroma_format_idc ]; + CropUnitY = SubHeightC[ sps->chroma_format_idc ] * (2 - sps->frame_mbs_only_flag); + } + uint64_t frame_crop_left_offset = h264_get_exp_golomb_ue( bits ); + uint64_t frame_crop_right_offset = h264_get_exp_golomb_ue( bits ); + uint64_t frame_crop_top_offset = h264_get_exp_golomb_ue( bits ); + uint64_t frame_crop_bottom_offset = h264_get_exp_golomb_ue( bits ); + sps->cropped_width -= (frame_crop_left_offset + frame_crop_right_offset) * CropUnitX; + sps->cropped_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * CropUnitY; + } + if( lsmash_bits_get( bits, 1 ) ) /* vui_parameters_present_flag */ + { + /* vui_parameters() */ + if( lsmash_bits_get( bits, 1 ) ) /* aspect_ratio_info_present_flag */ + { + uint8_t aspect_ratio_idc = lsmash_bits_get( bits, 8 ); + if( aspect_ratio_idc == 255 ) + { + /* Extended_SAR */ + sps->vui.sar_width = lsmash_bits_get( bits, 16 ); + sps->vui.sar_height = lsmash_bits_get( bits, 16 ); + } + else + { + static const struct + { + uint16_t sar_width; + uint16_t sar_height; + } pre_defined_sar[] + = { + { 0, 0 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, { 16, 11 }, + { 40, 33 }, { 24, 11 }, { 20, 11 }, { 32, 11 }, { 80, 33 }, + { 18, 11 }, { 15, 11 }, { 64, 33 }, { 160, 99 }, { 4, 3 }, + { 3, 2 }, { 2, 1 } + }; + if( aspect_ratio_idc < (sizeof(pre_defined_sar) / sizeof(pre_defined_sar[0])) ) + { + sps->vui.sar_width = pre_defined_sar[ aspect_ratio_idc ].sar_width; + sps->vui.sar_height = pre_defined_sar[ aspect_ratio_idc ].sar_height; + } + else + { + /* Behavior when unknown aspect_ratio_idc is detected is not specified in the specification. */ + sps->vui.sar_width = 0; + sps->vui.sar_height = 0; + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* overscan_info_present_flag */ + lsmash_bits_get( bits, 1 ); /* overscan_appropriate_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* video_signal_type_present_flag */ + { + lsmash_bits_get( bits, 3 ); /* video_format */ + sps->vui.video_full_range_flag = lsmash_bits_get( bits, 1 ); + if( lsmash_bits_get( bits, 1 ) ) /* colour_description_present_flag */ + { + sps->vui.colour_primaries = lsmash_bits_get( bits, 8 ); + sps->vui.transfer_characteristics = lsmash_bits_get( bits, 8 ); + sps->vui.matrix_coefficients = lsmash_bits_get( bits, 8 ); + } + } + if( lsmash_bits_get( bits, 1 ) ) /* chroma_loc_info_present_flag */ + { + h264_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_top_field */ + h264_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_bottom_field */ + } + if( lsmash_bits_get( bits, 1 ) ) /* timing_info_present_flag */ + { + sps->vui.num_units_in_tick = lsmash_bits_get( bits, 32 ); + sps->vui.time_scale = lsmash_bits_get( bits, 32 ); + sps->vui.fixed_frame_rate_flag = lsmash_bits_get( bits, 1 ); + } + int nal_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + if( nal_hrd_parameters_present_flag + && h264_parse_hrd_parameters( bits ) ) + return -1; + int vcl_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + if( vcl_hrd_parameters_present_flag + && h264_parse_hrd_parameters( bits ) ) + return -1; + if( nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag ) + { + sps->hrd_present = 1; + lsmash_bits_get( bits, 1 ); /* low_delay_hrd_flag */ + } + lsmash_bits_get( bits, 1 ); /* pic_struct_present_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* bitstream_restriction_flag */ + { + lsmash_bits_get( bits, 1 ); /* motion_vectors_over_pic_boundaries_flag */ + h264_get_exp_golomb_ue( bits ); /* max_bytes_per_pic_denom */ + h264_get_exp_golomb_ue( bits ); /* max_bits_per_mb_denom */ + h264_get_exp_golomb_ue( bits ); /* log2_max_mv_length_horizontal */ + h264_get_exp_golomb_ue( bits ); /* log2_max_mv_length_vertical */ + h264_get_exp_golomb_ue( bits ); /* num_reorder_frames */ + h264_get_exp_golomb_ue( bits ); /* max_dec_frame_buffering */ + } + } + else + { + sps->vui.video_full_range_flag = 0; + sps->vui.num_units_in_tick = 1; + sps->vui.time_scale = 50; + sps->vui.fixed_frame_rate_flag = 0; + } + /* rbsp_trailing_bits() */ + IF_INVALID_VALUE( !lsmash_bits_get( bits, 1 ) ) /* rbsp_stop_one_bit */ + return -1; + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int h264_parse_pps_nalu( lsmash_bits_t *bits, h264_sps_t *sps, h264_pps_t *pps, h264_nalu_header_t *nalu_header, + uint8_t *rbsp_buffer, uint8_t *ebsp, uint64_t ebsp_size ) +{ + if( !sps ) + return -1; + uint8_t *rbsp_start = rbsp_buffer; + h264_remove_emulation_prevention( ebsp, ebsp_size, &rbsp_buffer ); + uint64_t rbsp_length = rbsp_buffer - rbsp_start; + if( lsmash_bits_import_data( bits, rbsp_start, rbsp_length ) ) + return -1; + memset( pps, 0, sizeof(h264_pps_t) ); + /* pic_parameter_set_rbsp */ + uint64_t pic_parameter_set_id = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( pic_parameter_set_id > 255 ) + return -1; + pps->pic_parameter_set_id = pic_parameter_set_id; + uint64_t seq_parameter_set_id = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( seq_parameter_set_id > 31 ) + return -1; + pps->seq_parameter_set_id = seq_parameter_set_id; + pps->entropy_coding_mode_flag = lsmash_bits_get( bits, 1 ); + pps->bottom_field_pic_order_in_frame_present_flag = lsmash_bits_get( bits, 1 ); + uint64_t num_slice_groups_minus1 = h264_get_exp_golomb_ue( bits ); + if( num_slice_groups_minus1 ) /* num_slice_groups_minus1 */ + { + uint64_t slice_group_map_type = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( slice_group_map_type > 6 ) + return -1; + if( slice_group_map_type == 0 ) + for( uint64_t iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ ) + h264_get_exp_golomb_ue( bits ); /* run_length_minus1[ iGroup ] */ + else if( slice_group_map_type == 2 ) + for( uint64_t iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) + { + h264_get_exp_golomb_ue( bits ); /* top_left [ iGroup ] */ + h264_get_exp_golomb_ue( bits ); /* bottom_right[ iGroup ] */ + } + else if( slice_group_map_type == 3 + || slice_group_map_type == 4 + || slice_group_map_type == 5 ) + { + lsmash_bits_get( bits, 1 ); /* slice_group_change_direction_flag */ + uint64_t slice_group_change_rate_minus1 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( slice_group_change_rate_minus1 > (sps->PicSizeInMapUnits - 1) ) + return -1; + pps->SliceGroupChangeRate = slice_group_change_rate_minus1 + 1; + } + else if( slice_group_map_type == 6 ) + { + uint64_t pic_size_in_map_units_minus1 = h264_get_exp_golomb_ue( bits ); + /* slice_group_id_length = ceil( log2( num_slice_groups_minus1 + 1 ) ); */ + uint64_t slice_group_id_length; + for( slice_group_id_length = 1; num_slice_groups_minus1 >> slice_group_id_length; slice_group_id_length++ ); + for( uint64_t i = 0; i <= pic_size_in_map_units_minus1; i++ ) + /* slice_group_id */ + IF_INVALID_VALUE( lsmash_bits_get( bits, slice_group_id_length ) > num_slice_groups_minus1 ) + return -1; + } + } + h264_get_exp_golomb_ue( bits ); /* num_ref_idx_l0_default_active_minus1 */ + h264_get_exp_golomb_ue( bits ); /* num_ref_idx_l1_default_active_minus1 */ + pps->weighted_pred_flag = lsmash_bits_get( bits, 1 ); + pps->weighted_bipred_idc = lsmash_bits_get( bits, 2 ); + h264_get_exp_golomb_se( bits ); /* pic_init_qp_minus26 */ + h264_get_exp_golomb_se( bits ); /* pic_init_qs_minus26 */ + h264_get_exp_golomb_se( bits ); /* chroma_qp_index_offset */ + pps->deblocking_filter_control_present_flag = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 1 ); /* constrained_intra_pred_flag */ + pps->redundant_pic_cnt_present_flag = lsmash_bits_get( bits, 1 ); + if( h264_check_more_rbsp_data( bits ) ) + { + int transform_8x8_mode_flag = lsmash_bits_get( bits, 1 ); + if( lsmash_bits_get( bits, 1 ) ) /* pic_scaling_matrix_present_flag */ + { + int num_loops = 6 + (sps->chroma_format_idc != 3 ? 2 : 6) * transform_8x8_mode_flag; + for( int i = 0; i < num_loops; i++ ) + if( lsmash_bits_get( bits, 1 ) /* pic_scaling_list_present_flag[i] */ + && h264_parse_scaling_list( bits, i < 6 ? 16 : 64 ) ) + return -1; + } + h264_get_exp_golomb_se( bits ); /* second_chroma_qp_index_offset */ + } + /* rbsp_trailing_bits() */ + IF_INVALID_VALUE( !lsmash_bits_get( bits, 1 ) ) /* rbsp_stop_one_bit */ + return -1; + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int h264_parse_sei_nalu( lsmash_bits_t *bits, h264_sei_t *sei, h264_nalu_header_t *nalu_header, + uint8_t *rbsp_buffer, uint8_t *ebsp, uint64_t ebsp_size ) +{ + uint8_t *rbsp_start = rbsp_buffer; + h264_remove_emulation_prevention( ebsp, ebsp_size, &rbsp_buffer ); + uint64_t rbsp_length = rbsp_buffer - rbsp_start; + if( lsmash_bits_import_data( bits, rbsp_start, rbsp_length ) ) + return -1; + uint64_t rbsp_pos = 0; + do + { + /* sei_message() */ + uint32_t payloadType = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_type_byte */ + payloadType += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + uint32_t payloadSize = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_size_byte */ + payloadSize += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + if( payloadType == 3 ) + { + /* filler_payload + * AVC file format is forbidden to contain this. */ + return -1; + } + else if( payloadType == 6 ) + { + /* recovery_point */ + sei->present = 1; + sei->random_accessible = 1; + sei->recovery_frame_cnt = h264_get_exp_golomb_ue( bits ); + lsmash_bits_get( bits, 1 ); /* exact_match_flag */ + lsmash_bits_get( bits, 1 ); /* broken_link_flag */ + lsmash_bits_get( bits, 2 ); /* changing_slice_group_idc */ + } + else + lsmash_bits_get( bits, payloadSize * 8 ); + lsmash_bits_get_align( bits ); + rbsp_pos += payloadSize; + } while( *(rbsp_start + rbsp_pos) != 0x80 ); /* All SEI messages are byte aligned at their end. + * Therefore, 0x80 shall be rbsp_trailing_bits(). */ + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int h264_parse_slice_header( lsmash_bits_t *bits, h264_sps_t *sps, h264_pps_t *pps, + h264_slice_info_t *slice, h264_nalu_header_t *nalu_header ) +{ + memset( slice, 0, sizeof(h264_slice_info_t) ); + slice->pic_order_cnt_type = sps->pic_order_cnt_type; + slice->nal_ref_idc = nalu_header->nal_ref_idc; + slice->IdrPicFlag = (nalu_header->nal_unit_type == 5); + /* slice_header() */ + h264_get_exp_golomb_ue( bits ); /* first_mb_in_slice */ + uint8_t slice_type = slice->type = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( (uint64_t)slice->type > 9 ) + return -1; + if( slice_type > 4 ) + slice_type = slice->type -= 5; + IF_INVALID_VALUE( (slice->IdrPicFlag || sps->max_num_ref_frames == 0) && slice_type != 2 && slice_type != 4 ) + return -1; + slice->pic_parameter_set_id = h264_get_exp_golomb_ue( bits ); + if( sps->separate_colour_plane_flag ) + lsmash_bits_get( bits, 2 ); /* colour_plane_id */ + uint64_t frame_num = lsmash_bits_get( bits, sps->log2_max_frame_num ); + IF_INVALID_VALUE( frame_num >= (1 << sps->log2_max_frame_num) || (slice->IdrPicFlag && frame_num) ) + return -1; + slice->frame_num = frame_num; + if( !sps->frame_mbs_only_flag ) + { + slice->field_pic_flag = lsmash_bits_get( bits, 1 ); + if( slice->field_pic_flag ) + slice->bottom_field_flag = lsmash_bits_get( bits, 1 ); + } + if( slice->IdrPicFlag ) + { + uint64_t idr_pic_id = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( idr_pic_id > 65535 ) + return -1; + slice->idr_pic_id = idr_pic_id; + } + if( sps->pic_order_cnt_type == 0 ) + { + uint64_t pic_order_cnt_lsb = lsmash_bits_get( bits, sps->log2_max_pic_order_cnt_lsb ); + IF_INVALID_VALUE( pic_order_cnt_lsb >= sps->MaxPicOrderCntLsb ) + return -1; + slice->pic_order_cnt_lsb = pic_order_cnt_lsb; + if( pps->bottom_field_pic_order_in_frame_present_flag && !slice->field_pic_flag ) + slice->delta_pic_order_cnt_bottom = h264_get_exp_golomb_se( bits ); + } + else if( sps->pic_order_cnt_type == 1 && !sps->delta_pic_order_always_zero_flag ) + { + slice->delta_pic_order_cnt[0] = h264_get_exp_golomb_se( bits ); + if( pps->bottom_field_pic_order_in_frame_present_flag && !slice->field_pic_flag ) + slice->delta_pic_order_cnt[1] = h264_get_exp_golomb_se( bits ); + } + if( pps->redundant_pic_cnt_present_flag ) + { + uint64_t redundant_pic_cnt = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( redundant_pic_cnt > 127 ) + return -1; + slice->has_redundancy = !!redundant_pic_cnt; + } + if( slice_type == H264_SLICE_TYPE_B ) + lsmash_bits_get( bits, 1 ); + uint64_t num_ref_idx_l0_active_minus1 = 0; + uint64_t num_ref_idx_l1_active_minus1 = 0; + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_SP || slice_type == H264_SLICE_TYPE_B ) + { + if( lsmash_bits_get( bits, 1 ) ) /* num_ref_idx_active_override_flag */ + { + num_ref_idx_l0_active_minus1 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( num_ref_idx_l0_active_minus1 > 31 ) + return -1; + if( slice_type == H264_SLICE_TYPE_B ) + { + num_ref_idx_l1_active_minus1 = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( num_ref_idx_l1_active_minus1 > 31 ) + return -1; + } + } + } + if( nalu_header->nal_unit_type == 20 ) + { + return -1; /* No support of MVC yet */ +#if 0 + /* ref_pic_list_mvc_modification() */ + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_B || slice_type == H264_SLICE_TYPE_SP ) + { + if( lsmash_bits_get( bits, 1 ) ) /* (S)P: ref_pic_list_modification_flag_l0 + * B: ref_pic_list_modification_flag_l1 */ + { + uint64_t modification_of_pic_nums_idc; + do + { + modification_of_pic_nums_idc = h264_get_exp_golomb_ue( bits ); +#if 0 + if( modification_of_pic_nums_idc == 0 || modification_of_pic_nums_idc == 1 ) + h264_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 */ + else if( modification_of_pic_nums_idc == 2 ) + h264_get_exp_golomb_ue( bits ); /* long_term_pic_num */ + else if( modification_of_pic_nums_idc == 4 || modification_of_pic_nums_idc == 5 ) + h264_get_exp_golomb_ue( bits ); /* abs_diff_view_idx_minus1 */ +#else + if( modification_of_pic_nums_idc != 3 ) + h264_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1, long_term_pic_num or abs_diff_view_idx_minus1 */ +#endif + } while( modification_of_pic_nums_idc != 3 ); + } +#endif + } + else + { + /* ref_pic_list_modification() */ + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_B || slice_type == H264_SLICE_TYPE_SP ) + { + if( lsmash_bits_get( bits, 1 ) ) /* (S)P: ref_pic_list_modification_flag_l0 + * B: ref_pic_list_modification_flag_l1 */ + { + uint64_t modification_of_pic_nums_idc; + do + { + modification_of_pic_nums_idc = h264_get_exp_golomb_ue( bits ); +#if 0 + if( modification_of_pic_nums_idc == 0 || modification_of_pic_nums_idc == 1 ) + h264_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 */ + else if( modification_of_pic_nums_idc == 2 ) + h264_get_exp_golomb_ue( bits ); /* long_term_pic_num */ +#else + if( modification_of_pic_nums_idc != 3 ) + h264_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 or long_term_pic_num */ +#endif + } while( modification_of_pic_nums_idc != 3 ); + } + } + } + if( (pps->weighted_pred_flag && (slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_SP)) + || (pps->weighted_bipred_idc == 1 && slice_type == H264_SLICE_TYPE_B) ) + { + /* pred_weight_table() */ + h264_get_exp_golomb_ue( bits ); /* luma_log2_weight_denom */ + if( sps->ChromaArrayType ) + h264_get_exp_golomb_ue( bits ); /* chroma_log2_weight_denom */ + for( uint8_t i = 0; i <= num_ref_idx_l0_active_minus1; i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* luma_weight_l0_flag */ + { + h264_get_exp_golomb_se( bits ); /* luma_weight_l0[i] */ + h264_get_exp_golomb_se( bits ); /* luma_offset_l0[i] */ + } + if( sps->ChromaArrayType + && lsmash_bits_get( bits, 1 ) /* chroma_weight_l0_flag */ ) + for( int j = 0; j < 2; j++ ) + { + h264_get_exp_golomb_se( bits ); /* chroma_weight_l0[i][j]*/ + h264_get_exp_golomb_se( bits ); /* chroma_offset_l0[i][j] */ + } + } + if( slice_type == H264_SLICE_TYPE_B ) + for( uint8_t i = 0; i <= num_ref_idx_l1_active_minus1; i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* luma_weight_l1_flag */ + { + h264_get_exp_golomb_se( bits ); /* luma_weight_l1[i] */ + h264_get_exp_golomb_se( bits ); /* luma_offset_l1[i] */ + } + if( sps->ChromaArrayType + && lsmash_bits_get( bits, 1 ) /* chroma_weight_l1_flag */ ) + for( int j = 0; j < 2; j++ ) + { + h264_get_exp_golomb_se( bits ); /* chroma_weight_l1[i][j]*/ + h264_get_exp_golomb_se( bits ); /* chroma_offset_l1[i][j] */ + } + } + } + if( !nalu_header->nal_ref_idc ) + { + /* dec_ref_pic_marking() */ + if( slice->IdrPicFlag ) + { + lsmash_bits_get( bits, 1 ); /* no_output_of_prior_pics_flag */ + lsmash_bits_get( bits, 1 ); /* long_term_reference_flag */ + } + else if( lsmash_bits_get( bits, 1 ) ) /* adaptive_ref_pic_marking_mode_flag */ + { + uint64_t memory_management_control_operation; + do + { + memory_management_control_operation = h264_get_exp_golomb_ue( bits ); + if( memory_management_control_operation ) + { + if( memory_management_control_operation == 5 ) + slice->has_mmco5 = 1; + h264_get_exp_golomb_ue( bits ); + } + } while( memory_management_control_operation ); + } + } +#if 0 /* We needn't read more. + * Skip slice_id (only in slice_data_partition_a_layer_rbsp( )), slice_data() and rbsp_slice_trailing_bits(). */ + if( pps->entropy_coding_mode_flag && slice_type != H264_SLICE_TYPE_I && slice_type != H264_SLICE_TYPE_SI ) + h264_get_exp_golomb_ue( bits ); /* cabac_init_idc */ + h264_get_exp_golomb_se( bits ); /* slice_qp_delta */ + if( slice_type == H264_SLICE_TYPE_SP || slice_type == H264_SLICE_TYPE_SI ) + { + if( slice_type == H264_SLICE_TYPE_SP ) + lsmash_bits_get( bits, 1 ); /* sp_for_switch_flag */ + h264_get_exp_golomb_se( bits ); /* slice_qs_delta */ + } + if( pps->deblocking_filter_control_present_flag + && h264_get_exp_golomb_ue( bits ) != 1 /* disable_deblocking_filter_idc */ ) + { + int64_t slice_alpha_c0_offset_div2 = h264_get_exp_golomb_se( bits ); + IF_INVALID_VALUE( slice_alpha_c0_offset_div2 < -6 || slice_alpha_c0_offset_div2 > 6 ) + return -1; + int64_t slice_beta_offset_div2 = h264_get_exp_golomb_se( bits ); + IF_INVALID_VALUE( slice_beta_offset_div2 < -6 || slice_beta_offset_div2 > 6 ) + return -1; + } + if( pps->num_slice_groups_minus1 + && (slice_group_map_type == 3 || slice_group_map_type == 4 || slice_group_map_type == 5) ) + { + uint64_t slice_group_change_cycle_length = ceil( log( sps->PicSizeInMapUnits / pps->SliceGroupChangeRate + 1 ) / 0.693147180559945 ); + uint64_t slice_group_change_cycle = lsmash_bits_get( bits, slice_group_change_cycle_length ); + IF_INVALID_VALUE( slice_group_change_cycle > (uint64_t)ceil( sps->PicSizeInMapUnits / pps->SliceGroupChangeRate ) ) + return -1; + } +#endif + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int h264_parse_slice( lsmash_bits_t *bits, h264_sps_t *sps, h264_pps_t *pps, + h264_slice_info_t *slice, h264_nalu_header_t *nalu_header, + uint8_t *rbsp_buffer, uint8_t *ebsp, uint64_t ebsp_size ) +{ + if( !sps || !pps ) + return -1; /* This would occur when the stream starts from non-IDR picture. */ + uint8_t *rbsp_start = rbsp_buffer; + h264_remove_emulation_prevention( ebsp, ebsp_size, &rbsp_buffer ); + uint64_t rbsp_length = rbsp_buffer - rbsp_start; + if( lsmash_bits_import_data( bits, rbsp_start, rbsp_length ) ) + return -1; + if( nalu_header->nal_unit_type != 3 && nalu_header->nal_unit_type != 4 ) + return h264_parse_slice_header( bits, sps, pps, slice, nalu_header ); + /* slice_data_partition_b_layer_rbsp() or slice_data_partition_c_layer_rbsp() */ + h264_get_exp_golomb_ue( bits ); /* slice_id */ + if( sps->separate_colour_plane_flag ) + lsmash_bits_get( bits, 2 ); /* colour_plane_id */ + if( pps->redundant_pic_cnt_present_flag ) + { + uint64_t redundant_pic_cnt = h264_get_exp_golomb_ue( bits ); + IF_INVALID_VALUE( redundant_pic_cnt > 127 ) + return -1; + slice->has_redundancy = !!redundant_pic_cnt; + } + /* Skip slice_data() and rbsp_slice_trailing_bits(). */ + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int h264_calculate_poc( h264_sps_t *sps, h264_picture_info_t *picture, h264_picture_info_t *prev_picture ) +{ + int64_t TopFieldOrderCnt = 0; + int64_t BottomFieldOrderCnt = 0; + if( sps->pic_order_cnt_type == 0 ) + { + int32_t prevPicOrderCntMsb; + int32_t prevPicOrderCntLsb; + if( picture->idr ) + { + prevPicOrderCntMsb = 0; + prevPicOrderCntLsb = 0; + } + else if( prev_picture->ref_pic_has_mmco5 ) + { + prevPicOrderCntMsb = 0; + prevPicOrderCntLsb = prev_picture->ref_pic_bottom_field_flag ? 0 : prev_picture->ref_pic_TopFieldOrderCnt; + } + else + { + prevPicOrderCntMsb = prev_picture->ref_pic_PicOrderCntMsb; + prevPicOrderCntLsb = prev_picture->ref_pic_PicOrderCntLsb; + } + int64_t PicOrderCntMsb; + int32_t pic_order_cnt_lsb = picture->pic_order_cnt_lsb; + uint64_t MaxPicOrderCntLsb = sps->MaxPicOrderCntLsb; + if( (pic_order_cnt_lsb < prevPicOrderCntLsb) + && ((prevPicOrderCntLsb - pic_order_cnt_lsb) >= (MaxPicOrderCntLsb / 2)) ) + PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb; + else if( (pic_order_cnt_lsb > prevPicOrderCntLsb) + && ((pic_order_cnt_lsb - prevPicOrderCntLsb) > (MaxPicOrderCntLsb / 2)) ) + PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb; + else + PicOrderCntMsb = prevPicOrderCntMsb; + IF_EXCEED_INT32( PicOrderCntMsb ) + return -1; + if( !picture->field_pic_flag ) + { + TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb; + BottomFieldOrderCnt = TopFieldOrderCnt + picture->delta_pic_order_cnt_bottom; + } + else if( picture->bottom_field_flag ) + BottomFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb; + else + TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb; + IF_EXCEED_INT32( TopFieldOrderCnt ) + return -1; + IF_EXCEED_INT32( BottomFieldOrderCnt ) + return -1; +#if 0 + fprintf( stderr, "PictureOrderCount\n" ); + fprintf( stderr, " prevPicOrderCntMsb: %"PRId32"\n", prevPicOrderCntMsb ); + fprintf( stderr, " prevPicOrderCntLsb: %"PRId32"\n", prevPicOrderCntLsb ); + fprintf( stderr, " PicOrderCntMsb: %"PRId64"\n", PicOrderCntMsb ); + fprintf( stderr, " pic_order_cnt_lsb: %"PRId32"\n", pic_order_cnt_lsb ); + fprintf( stderr, " MaxPicOrderCntLsb: %"PRIu64"\n", MaxPicOrderCntLsb ); + fprintf( stderr, " TopFieldOrderCnt: %"PRId64"\n", TopFieldOrderCnt ); + fprintf( stderr, " BottomFieldOrderCnt: %"PRId64"\n", BottomFieldOrderCnt ); +#endif + if( !picture->disposable ) + { + picture->ref_pic_has_mmco5 = picture->has_mmco5; + picture->ref_pic_bottom_field_flag = picture->bottom_field_flag; + picture->ref_pic_TopFieldOrderCnt = TopFieldOrderCnt; + picture->ref_pic_PicOrderCntMsb = PicOrderCntMsb; + picture->ref_pic_PicOrderCntLsb = pic_order_cnt_lsb; + } + } + else if( sps->pic_order_cnt_type == 1 ) + { + uint32_t frame_num = picture->frame_num; + uint32_t prevFrameNum = prev_picture->frame_num; + uint32_t prevFrameNumOffset = prev_picture->has_mmco5 ? 0 : prev_picture->FrameNumOffset; + uint64_t FrameNumOffset = picture->idr ? 0 : prevFrameNumOffset + (prevFrameNum > frame_num ? sps->MaxFrameNum : 0); + IF_INVALID_VALUE( FrameNumOffset > INT32_MAX ) + return -1; + uint64_t absFrameNum; + int64_t expectedPicOrderCnt; + if( sps->num_ref_frames_in_pic_order_cnt_cycle ) + { + absFrameNum = FrameNumOffset + frame_num; + absFrameNum -= picture->disposable && absFrameNum > 0; + if( absFrameNum ) + { + uint64_t picOrderCntCycleCnt = (absFrameNum - 1) / sps->num_ref_frames_in_pic_order_cnt_cycle; + uint8_t frameNumInPicOrderCntCycle = (absFrameNum - 1) % sps->num_ref_frames_in_pic_order_cnt_cycle; + expectedPicOrderCnt = picOrderCntCycleCnt * sps->ExpectedDeltaPerPicOrderCntCycle; + for( uint8_t i = 0; i <= frameNumInPicOrderCntCycle; i++ ) + expectedPicOrderCnt += sps->offset_for_ref_frame[i]; + } + else + expectedPicOrderCnt = 0; + } + else + { + absFrameNum = 0; + expectedPicOrderCnt = 0; + } + if( picture->disposable ) + expectedPicOrderCnt += sps->offset_for_non_ref_pic; + if( !picture->field_pic_flag ) + { + TopFieldOrderCnt = expectedPicOrderCnt + picture->delta_pic_order_cnt[0]; + BottomFieldOrderCnt = TopFieldOrderCnt + sps->offset_for_top_to_bottom_field + picture->delta_pic_order_cnt[1]; + } + else if( picture->bottom_field_flag ) + BottomFieldOrderCnt = expectedPicOrderCnt + sps->offset_for_top_to_bottom_field + picture->delta_pic_order_cnt[0]; + else + TopFieldOrderCnt = expectedPicOrderCnt + picture->delta_pic_order_cnt[0]; + IF_EXCEED_INT32( TopFieldOrderCnt ) + return -1; + IF_EXCEED_INT32( BottomFieldOrderCnt ) + return -1; + } + else if( sps->pic_order_cnt_type == 2 ) + { + uint32_t frame_num = picture->frame_num; + uint32_t prevFrameNum = prev_picture->frame_num; + int32_t prevFrameNumOffset = prev_picture->has_mmco5 ? 0 : prev_picture->FrameNumOffset; + int64_t FrameNumOffset; + int64_t tempPicOrderCnt; + if( picture->idr ) + { + FrameNumOffset = 0; + tempPicOrderCnt = 0; + } + else + { + FrameNumOffset = prevFrameNumOffset + (prevFrameNum > frame_num ? sps->MaxFrameNum : 0); + tempPicOrderCnt = 2 * (FrameNumOffset + frame_num) - picture->disposable; + } + IF_EXCEED_INT32( FrameNumOffset ) + return -1; + if( !picture->field_pic_flag ) + { + TopFieldOrderCnt = tempPicOrderCnt; + BottomFieldOrderCnt = tempPicOrderCnt; + } + else if( picture->bottom_field_flag ) + BottomFieldOrderCnt = tempPicOrderCnt; + else + TopFieldOrderCnt = tempPicOrderCnt; + IF_EXCEED_INT32( TopFieldOrderCnt ) + return -1; + IF_EXCEED_INT32( BottomFieldOrderCnt ) + return -1; + picture->FrameNumOffset = FrameNumOffset; + } + if( !picture->field_pic_flag ) + picture->PicOrderCnt = LSMASH_MIN( TopFieldOrderCnt, BottomFieldOrderCnt ); + else + picture->PicOrderCnt = picture->bottom_field_flag ? BottomFieldOrderCnt : TopFieldOrderCnt; +#if 0 + fprintf( stderr, " POC: %"PRId32"\n", picture->PicOrderCnt ); +#endif + return 0; +} + +static inline void h264_compare_parameter_set( lsmash_entry_list_t *parameter_sets, uint8_t *ps_nalu, uint16_t ps_nalu_length, int *same_ps ) +{ + if( !parameter_sets->head ) + return; + isom_avcC_ps_entry_t *ps = (isom_avcC_ps_entry_t *)parameter_sets->head->data; + if( ps && (ps->parameterSetLength == ps_nalu_length) ) + *same_ps = !memcmp( ps->parameterSetNALUnit, ps_nalu, ps_nalu_length ); +} + +#define H264_NALU_LENGTH_SIZE 4 /* We always use 4 bytes length. */ + +static lsmash_video_summary_t *h264_create_summary( mp4sys_h264_info_t *info, uint8_t *rbsp_buffer, int probe, + h264_nalu_header_t *sps_nalu_header, + uint8_t *sps_nalu, uint16_t sps_nalu_length, + h264_nalu_header_t *pps_nalu_header, + uint8_t *pps_nalu, uint16_t pps_nalu_length ) +{ + assert( info ); + isom_avcC_t *avcC = &info->avcC; + int same_sps = 0; + int same_pps = 0; + if( sps_nalu ) + { + h264_compare_parameter_set( avcC->sequenceParameterSets, sps_nalu, sps_nalu_length, &same_sps ); + if( !same_sps ) + { + if( h264_parse_sps_nalu( info->bits, &info->sps, sps_nalu_header, rbsp_buffer, + sps_nalu + sps_nalu_header->length, sps_nalu_length - sps_nalu_header->length ) ) + return NULL; + if( !probe || !info->sps.present ) + { + h264_sps_t *sps = &info->sps; + avcC->configurationVersion = 1; + avcC->AVCProfileIndication = sps->profile_idc; + avcC->profile_compatibility = sps->constraint_set_flags; + avcC->AVCLevelIndication = sps->level_idc; + avcC->lengthSizeMinusOne = H264_NALU_LENGTH_SIZE - 1; + avcC->numOfSequenceParameterSets = 1; + avcC->chroma_format = sps->chroma_format_idc; + avcC->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8; + avcC->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8; + lsmash_remove_entries( avcC->sequenceParameterSets, isom_remove_avcC_ps ); + isom_avcC_ps_entry_t *ps = malloc( sizeof(isom_avcC_ps_entry_t) ); + if( !ps ) + return NULL; + ps->parameterSetNALUnit = lsmash_memdup( sps_nalu, sps_nalu_length ); + if( !ps->parameterSetNALUnit ) + { + free( ps ); + return NULL; + } + ps->parameterSetLength = sps_nalu_length; + if( lsmash_add_entry( avcC->sequenceParameterSets, ps ) ) + { + free( ps->parameterSetNALUnit ); + free( ps ); + return NULL; + } + info->sps.present = 1; + } + } + } + if( pps_nalu ) + { + h264_compare_parameter_set( avcC->pictureParameterSets, pps_nalu, pps_nalu_length, &same_pps ); + if( !same_pps ) + { + if( h264_parse_pps_nalu( info->bits, &info->sps, &info->pps, pps_nalu_header, rbsp_buffer, + pps_nalu + pps_nalu_header->length, pps_nalu_length - pps_nalu_header->length ) ) + return NULL; + if( !probe || !info->pps.present ) + { + avcC->numOfPictureParameterSets = 1; + lsmash_remove_entries( avcC->pictureParameterSets, isom_remove_avcC_ps ); + isom_avcC_ps_entry_t *ps = malloc( sizeof(isom_avcC_ps_entry_t) ); + if( !ps ) + return NULL; + ps->parameterSetNALUnit = lsmash_memdup( pps_nalu, pps_nalu_length ); + if( !ps->parameterSetNALUnit ) + { + free( ps ); + return NULL; + } + ps->parameterSetLength = pps_nalu_length; + if( lsmash_add_entry( avcC->pictureParameterSets, ps ) ) + { + free( ps->parameterSetNALUnit ); + free( ps ); + return NULL; + } + info->pps.present = 1; + } + } + } + /* Create summary when SPS, PPS and no summary are present even if probe is true. + * Skip to create a new summary when detecting the first summary if probe is true, i.e. we hold the first summary. */ + if( !info->sps.present || !info->pps.present || (probe && info->summary) ) + return info->summary; + if( !probe && ((!sps_nalu && pps_nalu && same_pps) || (!pps_nalu && sps_nalu && same_sps)) ) + return info->summary; + h264_sps_t *sps = &info->sps; + h264_pps_t *pps = &info->pps; + if( sps->seq_parameter_set_id != pps->seq_parameter_set_id ) + return info->summary; /* No support yet */ + lsmash_video_summary_t *summary; + if( info->summary ) + { + summary = info->summary; + if( summary->exdata ) + free( summary->exdata ); + summary->exdata = NULL; + info->first_summary = 0; + } + else + { + summary = (lsmash_video_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_VisualStream ); + if( !summary ) + return NULL; + info->first_summary = 1; + } + /* Update summary here. + * max_au_length is set at the last of mp4sys_h264_probe function. */ + summary->sample_type = ISOM_CODEC_TYPE_AVC1_VIDEO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Visual_H264_ISO_14496_10; + summary->timescale = sps->vui.time_scale; + summary->timebase = sps->vui.num_units_in_tick; + summary->full_range = sps->vui.video_full_range_flag; + summary->vfr = !sps->vui.fixed_frame_rate_flag; + summary->width = sps->cropped_width; + summary->height = sps->cropped_height; + summary->par_h = sps->vui.sar_width; + summary->par_v = sps->vui.sar_height; + summary->primaries = sps->vui.colour_primaries; + summary->transfer = sps->vui.transfer_characteristics; + summary->matrix = sps->vui.matrix_coefficients; + /* Export 'avcC' box into exdata. */ + lsmash_bs_t *bs = lsmash_bs_create( NULL ); + if( !bs ) + goto fail; + lsmash_bs_put_be32( bs, avcC->size ); + lsmash_bs_put_be32( bs, avcC->type ); + lsmash_bs_put_byte( bs, avcC->configurationVersion ); + lsmash_bs_put_byte( bs, avcC->AVCProfileIndication ); + lsmash_bs_put_byte( bs, avcC->profile_compatibility ); + lsmash_bs_put_byte( bs, avcC->AVCLevelIndication ); + lsmash_bs_put_byte( bs, avcC->lengthSizeMinusOne | 0xfc ); + lsmash_bs_put_byte( bs, avcC->numOfSequenceParameterSets | 0xe0 ); + isom_avcC_ps_entry_t *ps = (isom_avcC_ps_entry_t *)avcC->sequenceParameterSets->head->data; + if( !ps ) + goto fail; + lsmash_bs_put_be16( bs, ps->parameterSetLength ); + lsmash_bs_put_bytes( bs, ps->parameterSetNALUnit, ps->parameterSetLength ); + lsmash_bs_put_byte( bs, avcC->numOfPictureParameterSets ); + ps = (isom_avcC_ps_entry_t *)avcC->pictureParameterSets->head->data; + if( !ps ) + goto fail; + lsmash_bs_put_be16( bs, ps->parameterSetLength ); + lsmash_bs_put_bytes( bs, ps->parameterSetNALUnit, ps->parameterSetLength ); + if( ISOM_REQUIRES_AVCC_EXTENSION( avcC->AVCProfileIndication ) ) + { + lsmash_bs_put_byte( bs, avcC->chroma_format | 0xfc ); + lsmash_bs_put_byte( bs, avcC->bit_depth_luma_minus8 | 0xf8 ); + lsmash_bs_put_byte( bs, avcC->bit_depth_chroma_minus8 | 0xf8 ); + lsmash_bs_put_byte( bs, avcC->numOfSequenceParameterSetExt ); + /* No SequenceParameterSetExt */ + } + summary->exdata = lsmash_bs_export_data( bs, &summary->exdata_length ); + lsmash_bs_cleanup( bs ); + /* Update box size. */ + uint8_t *exdata = (uint8_t *)summary->exdata; + exdata[0] = (summary->exdata_length >> 24) & 0xff; + exdata[1] = (summary->exdata_length >> 16) & 0xff; + exdata[2] = (summary->exdata_length >> 8) & 0xff; + exdata[3] = summary->exdata_length & 0xff; + return summary; +fail: + lsmash_remove_list( avcC->sequenceParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( avcC->pictureParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( avcC->sequenceParameterSetExt, isom_remove_avcC_ps ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_bs_cleanup( bs ); + return NULL; +} + +static inline void h264_update_picture_type( h264_picture_info_t *picture, h264_slice_info_t *slice ) +{ + if( picture->type == H264_PICTURE_TYPE_I_P ) + { + if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_SI || slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + } + else if( picture->type == H264_PICTURE_TYPE_I_P_B ) + { + if( slice->type != H264_SLICE_TYPE_P && slice->type != H264_SLICE_TYPE_B && slice->type != H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_I ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_P; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_SI ) + picture->type = H264_PICTURE_TYPE_I_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + } + else if( picture->type == H264_PICTURE_TYPE_SI_SP ) + { + if( slice->type == H264_SLICE_TYPE_P || slice->type == H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_SI ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + else if( slice->type != H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_SI_SP; + } + else if( picture->type == H264_PICTURE_TYPE_I_SI ) + { + if( slice->type == H264_SLICE_TYPE_P || slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_I_SI_P_SP ) + { + if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_NONE ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_P; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I; + else if( slice->type == H264_SLICE_TYPE_SI ) + picture->type = H264_PICTURE_TYPE_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_SI_SP; + } +#if 0 + fprintf( stderr, "Picture type = %s\n", picture->type == H264_PICTURE_TYPE_I_P ? "P" + : picture->type == H264_PICTURE_TYPE_I_P_B ? "B" + : picture->type == H264_PICTURE_TYPE_I ? "I" + : picture->type == H264_PICTURE_TYPE_SI ? "SI" + : picture->type == H264_PICTURE_TYPE_I_SI ? "SI" + : "SP" ); +#endif +} + +/* Shall be called at least once per picture. */ +static void h264_update_picture_info_for_slice( h264_picture_info_t *picture, h264_slice_info_t *slice ) +{ + picture->has_mmco5 |= slice->has_mmco5; + picture->has_redundancy |= slice->has_redundancy; + picture->incomplete_au_has_primary |= !slice->has_redundancy; + h264_update_picture_type( picture, slice ); + slice->present = 0; /* Discard this slice info. */ +} + +/* Shall be called exactly once per picture. */ +static void h264_update_picture_info( h264_picture_info_t *picture, h264_slice_info_t *slice, h264_sei_t *sei ) +{ + picture->frame_num = slice->frame_num; + picture->pic_order_cnt_lsb = slice->pic_order_cnt_lsb; + picture->delta_pic_order_cnt_bottom = slice->delta_pic_order_cnt_bottom; + picture->delta_pic_order_cnt[0] = slice->delta_pic_order_cnt[0]; + picture->delta_pic_order_cnt[1] = slice->delta_pic_order_cnt[1]; + picture->field_pic_flag = slice->field_pic_flag; + picture->bottom_field_flag = slice->bottom_field_flag; + picture->idr = slice->IdrPicFlag; + picture->pic_parameter_set_id = slice->pic_parameter_set_id; + picture->disposable = (slice->nal_ref_idc == 0); + picture->random_accessible = slice->IdrPicFlag; + h264_update_picture_info_for_slice( picture, slice ); + picture->independent = picture->type == H264_PICTURE_TYPE_I || picture->type == H264_PICTURE_TYPE_I_SI; + picture->non_bipredictive = picture->type != H264_PICTURE_TYPE_I_P_B && picture->type != H264_PICTURE_TYPE_I_SI_P_SP_B; + if( sei->present ) + { + picture->random_accessible |= sei->random_accessible; + picture->recovery_frame_cnt = sei->recovery_frame_cnt; + sei->present = 0; + } +} + +static inline int h264_find_au_delimit_by_slice_info( h264_slice_info_t *slice, h264_slice_info_t *prev_slice ) +{ + if( slice->frame_num != prev_slice->frame_num + || ((slice->pic_order_cnt_type == 0 && prev_slice->pic_order_cnt_type == 0) + && (slice->pic_order_cnt_lsb != prev_slice->pic_order_cnt_lsb + || slice->delta_pic_order_cnt_bottom != prev_slice->delta_pic_order_cnt_bottom)) + || ((slice->pic_order_cnt_type == 1 && prev_slice->pic_order_cnt_type == 1) + && (slice->delta_pic_order_cnt[0] != prev_slice->delta_pic_order_cnt[0] + || slice->delta_pic_order_cnt[1] != prev_slice->delta_pic_order_cnt[1])) + || slice->field_pic_flag != prev_slice->field_pic_flag + || slice->bottom_field_flag != prev_slice->bottom_field_flag + || slice->IdrPicFlag != prev_slice->IdrPicFlag + || slice->pic_parameter_set_id != prev_slice->pic_parameter_set_id + || ((slice->nal_ref_idc == 0 || prev_slice->nal_ref_idc == 0) + && (slice->nal_ref_idc != prev_slice->nal_ref_idc)) + || (slice->IdrPicFlag == 1 && prev_slice->IdrPicFlag == 1 + && slice->idr_pic_id != prev_slice->idr_pic_id) ) + return 1; + return 0; +} + +static inline int h264_find_au_delimit_by_nalu_type( uint8_t nalu_type, uint8_t prev_nalu_type ) +{ + return ((nalu_type >= 6 && nalu_type <= 9) || (nalu_type >= 14 && nalu_type <= 18)) + && ((prev_nalu_type >= 1 && prev_nalu_type <= 5) || prev_nalu_type == 12 || prev_nalu_type == 19); +} + +static int h264_supplement_buffer( mp4sys_h264_info_t *info, uint32_t size ) +{ + uint32_t buffer_pos_offset = info->stream_buffer_pos - info->stream_buffer; + uint32_t buffer_valid_length = info->stream_buffer_end - info->stream_buffer; + lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( info->buffers, size ); + if( !temp ) + return -1; + info->buffers = temp; + info->stream_buffer = lsmash_withdraw_buffer( info->buffers, 1 ); + info->rbsp_buffer = lsmash_withdraw_buffer( info->buffers, 2 ); + info->picture.au = lsmash_withdraw_buffer( info->buffers, 3 ); + info->picture.incomplete_au = lsmash_withdraw_buffer( info->buffers, 4 ); + info->stream_buffer_pos = info->stream_buffer + buffer_pos_offset; + info->stream_buffer_end = info->stream_buffer + buffer_valid_length; + return 0; +} + +static inline int h264_check_next_short_start_code( uint8_t *buf_pos, uint8_t *buf_end ) +{ + return ((buf_pos + 2) < buf_end) && !buf_pos[0] && !buf_pos[1] && (buf_pos[2] == 0x01); +} + +static void h264_check_buffer_shortage( mp4sys_importer_t *importer, uint32_t anticipation_bytes ) +{ + mp4sys_h264_info_t *info = (mp4sys_h264_info_t *)importer->info; + assert( anticipation_bytes < info->buffers->buffer_size ); + if( info->no_more_read ) + return; + uint32_t remainder_bytes = info->stream_buffer_end - info->stream_buffer_pos; + if( remainder_bytes <= anticipation_bytes ) + { + /* Move unused data to the head of buffer. */ + for( uint32_t i = 0; i < remainder_bytes; i++ ) + *(info->stream_buffer + i) = *(info->stream_buffer_pos + i); + /* Read and store the next data into the buffer. + * Move the position of buffer on the head. */ + uint32_t read_size = fread( info->stream_buffer + remainder_bytes, 1, info->buffers->buffer_size - remainder_bytes, importer->stream ); + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + remainder_bytes + read_size; + info->no_more_read = read_size == 0 ? feof( importer->stream ) : 0; + } +} + +static inline int h264_complete_au( h264_picture_info_t *picture, int probe ) +{ + if( !picture->incomplete_au_has_primary || picture->incomplete_au_length == 0 ) + return 0; + if( !probe ) + memcpy( picture->au, picture->incomplete_au, picture->incomplete_au_length ); + picture->au_length = picture->incomplete_au_length; + picture->incomplete_au_length = 0; + picture->incomplete_au_has_primary = 0; + return 1; +} + +static void h264_append_nalu_to_au( h264_picture_info_t *picture, uint8_t *src_nalu, uint32_t nalu_length, int probe ) +{ + if( !probe ) + { + uint8_t *dst_nalu = picture->incomplete_au + picture->incomplete_au_length + H264_NALU_LENGTH_SIZE; + for( int i = H264_NALU_LENGTH_SIZE; i; i-- ) + *(dst_nalu - i) = (nalu_length >> ((i - 1) * 8)) & 0xff; + memcpy( dst_nalu, src_nalu, nalu_length ); + } + /* Note: picture->incomplete_au_length shall be 0 immediately after AU has completed. + * Therefore, possible_au_length in h264_get_access_unit_internal() can't be used here + * to avoid increasing AU length monotonously through the entire stream. */ + picture->incomplete_au_length += H264_NALU_LENGTH_SIZE + nalu_length; +} + +static inline void h264_get_au_internal_end( mp4sys_h264_info_t *info, h264_picture_info_t *picture, h264_nalu_header_t *nalu_header, int no_more_buf ) +{ + info->status = info->no_more_read && no_more_buf && (picture->incomplete_au_length == 0) + ? MP4SYS_IMPORTER_EOF + : MP4SYS_IMPORTER_OK; + info->nalu_header = *nalu_header; +} + +static int h264_get_au_internal_succeeded( mp4sys_h264_info_t *info, h264_picture_info_t *picture, h264_nalu_header_t *nalu_header, int no_more_buf ) +{ + h264_get_au_internal_end( info, picture, nalu_header, no_more_buf ); + picture->au_number += 1; + return 0; +} + +static int h264_get_au_internal_failed( mp4sys_h264_info_t *info, h264_picture_info_t *picture, h264_nalu_header_t *nalu_header, int no_more_buf, int complete_au ) +{ + h264_get_au_internal_end( info, picture, nalu_header, no_more_buf ); + if( complete_au ) + picture->au_number += 1; + return -1; +} + + +/* If probe equals 0, don't get the actual data (EBPS) of an access unit and only parse NALU. + * Currently, you can get AU of AVC video elemental stream only, not AVC parameter set elemental stream defined in 14496-15. */ +static int h264_get_access_unit_internal( mp4sys_importer_t *importer, mp4sys_h264_info_t *info, uint32_t track_number, int probe ) +{ +#define H264_SHORT_START_CODE_LENGTH 3 + h264_slice_info_t *slice = &info->slice; + h264_picture_info_t *picture = &info->picture; + h264_nalu_header_t nalu_header = info->nalu_header; + uint64_t consecutive_zero_byte_count = 0; + uint64_t ebsp_length = 0; + int no_more_buf = 0; + int complete_au = 0; + picture->au_length = 0; + picture->type = H264_PICTURE_TYPE_NONE; + picture->random_accessible = 0; + picture->recovery_frame_cnt = 0; + picture->has_mmco5 = 0; + picture->has_redundancy = 0; + while( 1 ) + { + h264_check_buffer_shortage( importer, 2 ); + no_more_buf = info->stream_buffer_pos >= info->stream_buffer_end; + int no_more = info->no_more_read && no_more_buf; + if( h264_check_next_short_start_code( info->stream_buffer_pos, info->stream_buffer_end ) || no_more ) + { + if( no_more && ebsp_length == 0 ) + { + /* For the last NALU. + * This NALU already has been appended into the latest access unit and parsed. */ + ebsp_length = picture->incomplete_au_length - (H264_NALU_LENGTH_SIZE + nalu_header.length); + consecutive_zero_byte_count = 0; + h264_update_picture_info( picture, slice, &info->sei ); + h264_complete_au( picture, probe ); + return h264_get_au_internal_succeeded( info, picture, &nalu_header, no_more_buf ); + } + uint64_t next_nalu_head_pos = info->ebsp_head_pos + ebsp_length + !no_more * H264_SHORT_START_CODE_LENGTH; + uint8_t *next_short_start_code_pos = info->stream_buffer_pos; /* Memorize position of short start code of the next NALU in buffer. + * This is used when backward reading of stream doesn't occur. */ + uint8_t nalu_type = nalu_header.nal_unit_type; + int read_back = 0; +#if 0 + if( probe ) + { + fprintf( stderr, "NALU type: %"PRIu8"\n", nalu_type ); + fprintf( stderr, " NALU header position: %"PRIx64"\n", info->ebsp_head_pos - nalu_header.length ); + fprintf( stderr, " EBSP position: %"PRIx64"\n", info->ebsp_head_pos ); + fprintf( stderr, " EBSP length: %"PRIx64" (%"PRIu64")\n", ebsp_length - consecutive_zero_byte_count, + ebsp_length - consecutive_zero_byte_count ); + fprintf( stderr, " consecutive_zero_byte_count: %"PRIx64"\n", consecutive_zero_byte_count ); + fprintf( stderr, " Next NALU header position: %"PRIx64"\n", next_nalu_head_pos ); + } +#endif + if( nalu_type == 12 ) + { + /* We don't support streams with both filler and HRD yet. + * Otherwise, just skip filler because elemental streams defined in 14496-15 are forbidden to use filler. */ + if( info->sps.hrd_present ) + return h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + } + else if( (nalu_type >= 1 && nalu_type <= 13) || nalu_type == 19 ) + { + /* Get the EBSP of the current NALU here. + * AVC elemental stream defined in 14496-15 can recognizes from 0 to 13, and 19 of nal_unit_type. + * We don't support SVC and MVC elemental stream defined in 14496-15 yet. */ + ebsp_length -= consecutive_zero_byte_count; /* Any EBSP doesn't have zero bytes at the end. */ + uint64_t nalu_length = nalu_header.length + ebsp_length; + uint64_t possible_au_length = picture->incomplete_au_length + H264_NALU_LENGTH_SIZE + nalu_length; + if( info->buffers->buffer_size < possible_au_length ) + { + if( h264_supplement_buffer( info, 2 * possible_au_length ) ) + h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + next_short_start_code_pos = info->stream_buffer_pos; + } + /* Move to the first byte of the current NALU. */ + read_back = (info->stream_buffer_pos - info->stream_buffer) < (nalu_length + consecutive_zero_byte_count); + if( read_back ) + { + lsmash_fseek( importer->stream, info->ebsp_head_pos - nalu_header.length, SEEK_SET ); + int read_fail = fread( info->stream_buffer, 1, nalu_length, importer->stream ) != nalu_length; + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + nalu_length; + if( read_fail ) + h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); +#if 0 + if( probe ) + fprintf( stderr, " ----Read Back\n" ); +#endif + } + else + info->stream_buffer_pos -= nalu_length + consecutive_zero_byte_count; + if( nalu_type >= 1 && nalu_type <= 5 ) + { + /* VCL NALU (slice) */ + h264_slice_info_t prev_slice = *slice; + if( h264_parse_slice( info->bits, &info->sps, &info->pps, + slice, &nalu_header, info->rbsp_buffer, + info->stream_buffer_pos + nalu_header.length, ebsp_length ) ) + return h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + if( prev_slice.present ) + { + /* Check whether the AU that contains the previous VCL NALU completed or not. */ + if( h264_find_au_delimit_by_slice_info( slice, &prev_slice ) ) + { + /* The current NALU is the first VCL NALU of the primary coded picture of an new AU. + * Therefore, the previous slice belongs to the AU you want at this time. */ + h264_update_picture_info( picture, &prev_slice, &info->sei ); + complete_au = h264_complete_au( picture, probe ); + } + else + h264_update_picture_info_for_slice( picture, &prev_slice ); + } + h264_append_nalu_to_au( picture, info->stream_buffer_pos, nalu_length, probe ); + slice->present = 1; + } + else + { + if( h264_find_au_delimit_by_nalu_type( nalu_type, info->prev_nalu_type ) ) + { + /* The last slice belongs to the AU you want at this time. */ + h264_update_picture_info( picture, slice, &info->sei ); + complete_au = h264_complete_au( picture, probe ); + } + else if( no_more ) + complete_au = h264_complete_au( picture, probe ); + switch( nalu_type ) + { + case 7 : /* SPS */ + info->summary = h264_create_summary( info, info->rbsp_buffer, probe, + &nalu_header, info->stream_buffer_pos, nalu_length, + NULL, NULL, 0 ); + break; + case 8 : /* PPS */ + info->summary = h264_create_summary( info, info->rbsp_buffer, probe, + NULL, NULL, 0, + &nalu_header, info->stream_buffer_pos, nalu_length ); + break; + case 9 : /* We drop access unit delimiters. */ + break; + case 13 : /* We don't support sequence parameter set extension yet. */ + return h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + case 6 : /* SEI */ + if( h264_parse_sei_nalu( info->bits, &info->sei, &nalu_header, + info->rbsp_buffer, info->stream_buffer_pos + nalu_header.length, ebsp_length ) ) + return h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + /* Don't break here. + * Append this SEI NALU to access unit. */ + default : + h264_append_nalu_to_au( picture, info->stream_buffer_pos, nalu_length, probe ); + break; + } + } + } + /* Move to the first byte of the next NALU. */ + if( read_back ) + { + lsmash_fseek( importer->stream, next_nalu_head_pos, SEEK_SET ); + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + } + else + info->stream_buffer_pos = next_short_start_code_pos + H264_SHORT_START_CODE_LENGTH; + info->prev_nalu_type = nalu_type; + h264_check_buffer_shortage( importer, 0 ); + no_more_buf = info->stream_buffer_pos >= info->stream_buffer_end; + ebsp_length = 0; + no_more = info->no_more_read && no_more_buf; + if( !no_more ) + { + /* Check the next NALU header. */ + if( h264_check_nalu_header( &nalu_header, &info->stream_buffer_pos, !!consecutive_zero_byte_count ) ) + return h264_get_au_internal_failed( info, picture, &nalu_header, no_more_buf, complete_au ); + info->ebsp_head_pos = next_nalu_head_pos + nalu_header.length; + } + /* If there is no more data in the stream, and flushed chunk of NALUs, flush it as complete AU here. */ + else if( picture->incomplete_au_length && picture->au_length == 0 ) + { + h264_update_picture_info( picture, slice, &info->sei ); + h264_complete_au( picture, probe ); + return h264_get_au_internal_succeeded( info, picture, &nalu_header, no_more_buf ); + } + if( complete_au ) + return h264_get_au_internal_succeeded( info, picture, &nalu_header, no_more_buf ); + consecutive_zero_byte_count = 0; + continue; /* Avoid increment of ebsp_length. */ + } + else if( !no_more ) + { + if( *info->stream_buffer_pos ++ ) + consecutive_zero_byte_count = 0; + else + ++consecutive_zero_byte_count; + } + ++ebsp_length; + } +#undef H264_SHORT_START_CODE_LENGTH +} + +#undef H264_NALU_LENGTH_SIZE + +static int mp4sys_h264_get_accessunit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + mp4sys_h264_info_t *info = (mp4sys_h264_info_t *)importer->info; + mp4sys_importer_status current_status = info->status; + if( current_status == MP4SYS_IMPORTER_ERROR || buffered_sample->length < info->max_au_length ) + return -1; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + if( info->summary && !info->first_summary ) + { + current_status = MP4SYS_IMPORTER_CHANGE; + /* Summaries may be not active immediately because we can't get any corresponding AU at once. + * The first summary will be an only exception of this. */ + lsmash_entry_t* entry = lsmash_get_entry( importer->summaries, track_number ); + if( !entry || !entry->data ) + return -1; + lsmash_cleanup_summary( entry->data ); + entry->data = info->summary; + info->summary = NULL; + } + if( h264_get_access_unit_internal( importer, info, track_number, 0 ) ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return -1; + } + h264_sps_t *sps = &info->sps; + h264_picture_info_t *picture = &info->picture; + buffered_sample->dts = info->ts_list.timestamp[picture->au_number - 1].dts; + buffered_sample->cts = info->ts_list.timestamp[picture->au_number - 1].cts; + if( picture->au_number < info->num_undecodable ) + buffered_sample->prop.leading = ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + else + buffered_sample->prop.leading = picture->non_bipredictive || buffered_sample->cts >= info->last_intra_cts + ? ISOM_SAMPLE_IS_NOT_LEADING : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + if( picture->independent ) + info->last_intra_cts = buffered_sample->cts; + if( info->composition_reordering_present && !picture->disposable && !picture->idr ) + buffered_sample->prop.allow_earlier = QT_SAMPLE_EARLIER_PTS_ALLOWED; + buffered_sample->prop.independent = picture->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; + buffered_sample->prop.disposable = picture->disposable ? ISOM_SAMPLE_IS_DISPOSABLE : ISOM_SAMPLE_IS_NOT_DISPOSABLE; + buffered_sample->prop.redundant = picture->has_redundancy ? ISOM_SAMPLE_HAS_REDUNDANCY : ISOM_SAMPLE_HAS_NO_REDUNDANCY; + buffered_sample->prop.post_roll.identifier = picture->frame_num; + if( picture->random_accessible ) + { + if( picture->idr ) + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + else if( picture->recovery_frame_cnt ) + { + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_RECOVERY; + buffered_sample->prop.post_roll.complete = (picture->frame_num + picture->recovery_frame_cnt) % sps->MaxFrameNum; + } + else + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_OPEN_RAP; + } + buffered_sample->length = picture->au_length; + memcpy( buffered_sample->data, picture->au, picture->au_length ); + /* Return 1 if a new summary is detected. */ + return current_status == MP4SYS_IMPORTER_CHANGE; +} + +static int mp4sys_h264_probe( mp4sys_importer_t *importer ) +{ +#define H264_LONG_START_CODE_LENGTH 4 +#define H264_CHECK_NEXT_LONG_START_CODE( x ) (!(x)[0] && !(x)[1] && !(x)[2] && ((x)[3] == 0x01)) + /* Find the first start code. */ + mp4sys_h264_info_t *info = mp4sys_create_h264_info(); + if( !info ) + return -1; + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + info->no_more_read = info->stream_buffer >= info->stream_buffer_end ? feof( importer->stream ) : 0; + while( 1 ) + { + /* Invalid if encountered any value of non-zero before the first start code. */ + IF_INVALID_VALUE( *info->stream_buffer_pos ) + goto fail; + /* The first NALU of an AU in decoding order shall have long start code (0x00000001). */ + if( H264_CHECK_NEXT_LONG_START_CODE( info->stream_buffer_pos ) ) + break; + /* If the first trial of finding long start code failed, we assume this stream is not byte stream format of H.264. */ + if( (info->stream_buffer_pos + H264_LONG_START_CODE_LENGTH) == info->stream_buffer_end ) + goto fail; + ++info->stream_buffer_pos; + } + /* OK. It seems the stream has a long start code of H.264. */ + importer->info = info; + info->stream_buffer_pos += H264_LONG_START_CODE_LENGTH; + h264_check_buffer_shortage( importer, 0 ); + h264_nalu_header_t first_nalu_header; + if( h264_check_nalu_header( &first_nalu_header, &info->stream_buffer_pos, 1 ) ) + goto fail; + uint64_t first_ebsp_head_pos = info->stream_buffer_pos - info->stream_buffer; /* EBSP doesn't include NALU header. */ + info->status = info->no_more_read ? MP4SYS_IMPORTER_EOF : MP4SYS_IMPORTER_OK; + info->nalu_header = first_nalu_header; + info->ebsp_head_pos = first_ebsp_head_pos; + /* Parse all NALU in the stream for preparation of calculating timestamps. */ + uint32_t poc_alloc = (1 << 12) * sizeof(uint64_t); + uint64_t *poc = malloc( poc_alloc ); + if( !poc ) + goto fail; + uint32_t num_access_units = 0; + fprintf( stderr, "Analyzing stream as H.264\r" ); + while( info->status != MP4SYS_IMPORTER_EOF ) + { +#if 0 + fprintf( stderr, "Analyzing stream as H.264: %"PRIu32"\n", num_access_units + 1 ); +#endif + h264_picture_info_t prev_picture = info->picture; + if( h264_get_access_unit_internal( importer, info, 0, 1 ) ) + goto fail; + if( h264_calculate_poc( &info->sps, &info->picture, &prev_picture ) ) + goto fail; + if( poc_alloc <= num_access_units * sizeof(uint64_t) ) + { + uint32_t alloc = 2 * num_access_units * sizeof(uint64_t); + uint64_t *temp = realloc( poc, alloc ); + if( !temp ) + { + free( poc ); + goto fail; + } + poc = temp; + poc_alloc = alloc; + } + poc[num_access_units++] = info->picture.PicOrderCnt; + info->max_au_length = LSMASH_MAX( info->picture.au_length, info->max_au_length ); + } + fprintf( stderr, " \r" ); + if( !info->summary || lsmash_add_entry( importer->summaries, info->summary ) ) + goto fail; + lsmash_media_ts_t *timestamp = malloc( num_access_units * sizeof(lsmash_media_ts_t) ); + if( !timestamp ) + { + free( poc ); + goto fail; + } + /* Make zero-origin. */ + int32_t min_poc = poc[0]; + for( uint32_t i = 1; i < num_access_units; i++ ) + min_poc = LSMASH_MIN( (int32_t)poc[i], min_poc ); + if( min_poc ) + for( uint32_t i = 0; i < num_access_units; i++ ) + poc[i] -= min_poc; + /* Deduplicate POCs. */ + uint64_t poc_offset = 0; + uint64_t poc_max = 0; + for( uint32_t i = 1; i < num_access_units; i++ ) + { + if( poc[i] == 0 ) + { + poc_offset += poc_max + 1; + poc_max = 0; + } + else + poc_max = LSMASH_MAX( poc[i], poc_max ); + poc[i] += poc_offset; + } + /* Count leading samples that are undecodable. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + { + if( poc[i] == 0 ) + break; + ++info->num_undecodable; + } + /* Get max composition delay. */ + uint32_t composition_delay = 0; + uint32_t max_composition_delay = 0; + for( uint32_t i = 1; i < num_access_units; i++ ) + if( poc[i] < poc[i - 1] ) + { + ++composition_delay; + max_composition_delay = LSMASH_MAX( max_composition_delay, composition_delay ); + } + else + composition_delay = 0; + /* Generate timestamps. */ + if( max_composition_delay ) + { + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].cts = poc[i]; + timestamp[i].dts = i; + } + qsort( timestamp, num_access_units, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))lsmash_compare_cts ); + for( uint32_t i = 0; i < num_access_units; i++ ) + timestamp[i].cts = i + max_composition_delay; + qsort( timestamp, num_access_units, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))lsmash_compare_dts ); + } + else + for( uint32_t i = 0; i < num_access_units; i++ ) + timestamp[i].cts = timestamp[i].dts = i; + free( poc ); +#if 0 + for( uint32_t i = 0; i < num_access_units; i++ ) + fprintf( stderr, "Timestamp[%"PRIu32"]: DTS=%"PRIu64", CTS=%"PRIu64"\n", i, timestamp[i].dts, timestamp[i].cts ); +#endif + info->ts_list.sample_count = num_access_units; + info->ts_list.timestamp = timestamp; + info->composition_reordering_present = !!max_composition_delay; + /* Go back to EBSP of the first NALU. */ + lsmash_fseek( importer->stream, first_ebsp_head_pos, SEEK_SET ); + info->status = MP4SYS_IMPORTER_OK; + info->nalu_header = first_nalu_header; + info->prev_nalu_type = 0; + info->no_more_read = 0; + info->first_summary = 0; + info->summary->max_au_length = info->max_au_length; + info->summary = NULL; + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + info->ebsp_head_pos = first_ebsp_head_pos; + uint8_t *temp_au = info->picture.au; + uint8_t *temp_incomplete_au = info->picture.incomplete_au; + memset( &info->picture, 0, sizeof(h264_picture_info_t) ); + info->picture.au = temp_au; + info->picture.incomplete_au = temp_incomplete_au; + memset( &info->slice, 0, sizeof(h264_slice_info_t) ); + memset( &info->sps, 0, sizeof(h264_sps_t) ); + memset( &info->pps, 0, sizeof(h264_pps_t) ); + lsmash_remove_entries( info->avcC.sequenceParameterSets, isom_remove_avcC_ps ); + lsmash_remove_entries( info->avcC.pictureParameterSets, isom_remove_avcC_ps ); + lsmash_remove_entries( info->avcC.sequenceParameterSetExt, isom_remove_avcC_ps ); + importer->info = info; + return 0; +fail: + mp4sys_remove_h264_info( info ); + lsmash_remove_entries( importer->summaries, lsmash_cleanup_summary ); + return -1; +#undef H264_LONG_START_CODE_LENGTH +#undef H264_CHECK_NEXT_LONG_START_CODE +} + +static uint32_t mp4sys_h264_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_h264_info_t *info = (mp4sys_h264_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + return info->ts_list.sample_count > 1 + ? 1 + : UINT32_MAX; /* arbitrary */ +} + +const static mp4sys_importer_functions mp4sys_h264_importer = +{ + "H.264", + 1, + mp4sys_h264_probe, + mp4sys_h264_get_accessunit, + mp4sys_h264_get_last_delta, + mp4sys_h264_cleanup +}; + +/*************************************************************************** + SMPTE VC-1 importer (only for Advanced Profile) +***************************************************************************/ + +typedef struct +{ + uint8_t hrd_num_leaky_buckets; +} vc1_hrd_param_t; + +typedef struct +{ + uint8_t present; + uint8_t profile; + uint8_t level; + uint8_t colordiff_format; /* currently 4:2:0 only */ + uint8_t interlace; + uint8_t color_prim; + uint8_t transfer_char; + uint8_t matrix_coef; + uint8_t hrd_param_flag; + uint8_t aspect_width; + uint8_t aspect_height; + uint8_t framerate_flag; + uint32_t framerate_numerator; + uint32_t framerate_denominator; + uint16_t max_coded_width; + uint16_t max_coded_height; + uint16_t disp_horiz_size; + uint16_t disp_vert_size; + vc1_hrd_param_t hrd_param; + uint8_t *ebdu; + uint32_t length; +} vc1_sequence_header_t; + +typedef struct +{ + uint8_t present; + uint8_t closed_entry_point; + uint8_t *ebdu; + uint32_t length; +} vc1_entry_point_t; + +typedef struct +{ + uint8_t present; + uint8_t frame_coding_mode; + uint8_t type; + uint8_t closed_gop; + uint8_t start_of_sequence; + uint8_t random_accessible; +} vc1_picture_info_t; + +typedef struct +{ + uint8_t random_accessible; + uint8_t closed_gop; + uint8_t independent; + uint8_t non_bipredictive; + uint8_t disposable; + uint8_t *data; + uint32_t data_length; + uint8_t *incomplete_data; + uint32_t incomplete_data_length; + uint32_t number; +} vc1_access_unit_t; + +typedef struct +{ + mp4sys_importer_status status; + lsmash_video_summary_t *summary; + uint8_t bdu_type; + uint8_t prev_bdu_type; + uint8_t no_more_read; + uint8_t composition_reordering_present; + uint8_t slice_present; + uint8_t multiple_sequence; + uint8_t multiple_entry_point; + vc1_sequence_header_t first_sequence; + vc1_sequence_header_t sequence; + vc1_entry_point_t first_entry_point; + vc1_entry_point_t entry_point; + vc1_picture_info_t next_picture; + vc1_access_unit_t access_unit; + lsmash_bits_t *bits; + lsmash_multiple_buffers_t *buffers; + uint8_t *rbdu_buffer; + uint8_t *stream_buffer; + uint8_t *stream_buffer_pos; + uint8_t *stream_buffer_end; + uint64_t ebdu_head_pos; + uint32_t max_au_length; + uint32_t num_undecodable; + uint64_t last_ref_intra_cts; + lsmash_media_ts_list_t ts_list; +} mp4sys_vc1_info_t; + +#define VC1_START_CODE_PREFIX_LENGTH 3 /* 0x000001 */ +#define VC1_START_CODE_SUFFIX_LENGTH 1 /* BDU type */ +#define VC1_START_CODE_LENGTH (VC1_START_CODE_PREFIX_LENGTH + VC1_START_CODE_SUFFIX_LENGTH) /* = 4 */ + +enum +{ + VC1_ADVANCED_PICTURE_TYPE_I = 0x6, /* 0b110 */ + VC1_ADVANCED_PICTURE_TYPE_P = 0x0, /* 0b0 */ + VC1_ADVANCED_PICTURE_TYPE_B = 0x2, /* 0b10 */ + VC1_ADVANCED_PICTURE_TYPE_BI = 0xE, /* 0b1110 */ + VC1_ADVANCED_PICTURE_TYPE_SKIPPED = 0xF, /* 0b1111 */ +} vc1_picture_type; + +enum +{ + VC1_ADVANCED_FIELD_PICTURE_TYPE_II = 0x0, /* 0b000 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_IP = 0x1, /* 0b001 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_PI = 0x2, /* 0b010 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_PP = 0x3, /* 0b011 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BB = 0x4, /* 0b100 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BBI = 0x5, /* 0b101 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BIB = 0x6, /* 0b110 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI = 0x7, /* 0b111 */ +} vc1_field_picture_type; + +enum +{ + VC1_FRAME_CODING_MODE_PROGRESSIVE = 0x0, /* 0b0 */ + VC1_FRAME_CODING_MODE_FRAME_INTERLACE = 0x2, /* 0b10 */ + VC1_FRAME_CODING_MODE_FIELD_INTERLACE = 0x3, /* 0b11 */ +} vc1_frame_coding_mode; + +#define MP4SYS_VC1_DEFAULT_BUFFER_SIZE (1<<16) + +static void mp4sys_remove_vc1_info( mp4sys_vc1_info_t *info ) +{ + if( !info ) + return; + lsmash_bits_adhoc_cleanup( info->bits ); + lsmash_destroy_multiple_buffers( info->buffers ); + if( info->ts_list.timestamp ) + free( info->ts_list.timestamp ); + if( info->sequence.ebdu ) + free( info->sequence.ebdu ); + if( info->entry_point.ebdu ) + free( info->entry_point.ebdu ); + free( info ); +} + +static mp4sys_vc1_info_t *mp4sys_create_vc1_info( void ) +{ + mp4sys_vc1_info_t *info = lsmash_malloc_zero( sizeof(mp4sys_vc1_info_t) ); + if( !info ) + return NULL; + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + mp4sys_remove_vc1_info( info ); + return NULL; + } + info->buffers = lsmash_create_multiple_buffers( 4, MP4SYS_VC1_DEFAULT_BUFFER_SIZE ); + if( !info->buffers ) + { + mp4sys_remove_vc1_info( info ); + return NULL; + } + info->stream_buffer = lsmash_withdraw_buffer( info->buffers, 1 ); + info->rbdu_buffer = lsmash_withdraw_buffer( info->buffers, 2 ); + info->access_unit.data = lsmash_withdraw_buffer( info->buffers, 3 ); + info->access_unit.incomplete_data = lsmash_withdraw_buffer( info->buffers, 4 ); + return info; +} + +static void mp4sys_vc1_cleanup( mp4sys_importer_t *importer ) +{ + debug_if( importer && importer->info ) + mp4sys_remove_vc1_info( importer->info ); +} + +/* Convert EBDU (Encapsulated Byte Data Unit) to RBDU (Raw Byte Data Unit). */ +static void vc1_remove_emulation_prevention( uint8_t *src, uint64_t src_length, uint8_t **p_dst ) +{ + uint8_t *src_end = src + src_length; + uint8_t *dst = *p_dst; + while( src < src_end ) + if( ((src + 2) < src_end) && !src[0] && !src[1] && (src[2] == 0x03) ) + { + *dst++ = *src++; + *dst++ = *src++; + src++; /* Skip emulation_prevention_three_byte (0x03). */ + } + else + *dst++ = *src++; + *p_dst = dst; +} + +static int vc1_bits_import_rbdu_from_ebdu( lsmash_bits_t *bits, uint8_t *rbdu_buffer, uint8_t *ebdu, uint64_t ebdu_length ) +{ + uint8_t *rbdu_start = rbdu_buffer; + vc1_remove_emulation_prevention( ebdu + VC1_START_CODE_LENGTH, ebdu_length - VC1_START_CODE_LENGTH, &rbdu_buffer ); + uint64_t rbdu_length = rbdu_buffer - rbdu_start; + return lsmash_bits_import_data( bits, rbdu_start, rbdu_length ); +} + +static void vc1_parse_hrd_param( lsmash_bits_t *bits, vc1_hrd_param_t *hrd_param ) +{ + hrd_param->hrd_num_leaky_buckets = lsmash_bits_get( bits, 5 ); + lsmash_bits_get( bits, 4 ); /* bitrate_exponent */ + lsmash_bits_get( bits, 4 ); /* buffer_size_exponent */ + for( uint8_t i = 0; i < hrd_param->hrd_num_leaky_buckets; i++ ) + { + lsmash_bits_get( bits, 16 ); /* hrd_rate */ + lsmash_bits_get( bits, 16 ); /* hrd_buffer */ + } +} + +static int vc1_parse_sequence_header( mp4sys_vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_length, int probe ) +{ + lsmash_bits_t *bits = info->bits; + vc1_sequence_header_t *sequence = &info->sequence; + if( vc1_bits_import_rbdu_from_ebdu( bits, info->rbdu_buffer, ebdu, ebdu_length ) ) + return -1; + memset( sequence, 0, sizeof(vc1_sequence_header_t) ); + sequence->profile = lsmash_bits_get( bits, 2 ); + if( sequence->profile != 3 ) + return -1; /* SMPTE Reserved */ + sequence->level = lsmash_bits_get( bits, 3 ); + if( sequence->level > 4 ) + return -1; /* SMPTE Reserved */ + sequence->colordiff_format = lsmash_bits_get( bits, 2 ); + if( sequence->colordiff_format != 1 ) + return -1; /* SMPTE Reserved */ + lsmash_bits_get( bits, 9 ); /* frmrtq_postproc (3) + * bitrtq_postproc (5) + * postproc_flag (1) */ + sequence->max_coded_width = lsmash_bits_get( bits, 12 ); + sequence->max_coded_height = lsmash_bits_get( bits, 12 ); + lsmash_bits_get( bits, 1 ); /* pulldown */ + sequence->interlace = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 4 ); /* tfcntrflag (1) + * finterpflag (1) + * reserved (1) + * psf (1) */ + if( lsmash_bits_get( bits, 1 ) ) /* display_ext */ + { + sequence->disp_horiz_size = lsmash_bits_get( bits, 14 ) + 1; + sequence->disp_vert_size = lsmash_bits_get( bits, 14 ) + 1; + if( lsmash_bits_get( bits, 1 ) ) /* aspect_ratio_flag */ + { + uint8_t aspect_ratio = lsmash_bits_get( bits, 4 ); + if( aspect_ratio == 15 ) + { + sequence->aspect_width = lsmash_bits_get( bits, 8 ) + 1; /* aspect_horiz_size */ + sequence->aspect_height = lsmash_bits_get( bits, 8 ) + 1; /* aspect_vert_size */ + } + else + { + static const struct + { + uint32_t aspect_width; + uint32_t aspect_height; + } vc1_aspect_ratio[15] = + { + { 0, 0 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, { 16, 11 }, { 40, 33 }, { 24, 11 }, + { 20, 11 }, { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 }, { 64, 33 }, { 160, 99 }, + { 0, 0 } /* SMPTE Reserved */ + }; + sequence->aspect_width = vc1_aspect_ratio[ aspect_ratio ].aspect_width; + sequence->aspect_height = vc1_aspect_ratio[ aspect_ratio ].aspect_height; + } + } + sequence->framerate_flag = lsmash_bits_get( bits, 1 ); + if( sequence->framerate_flag ) + { + if( lsmash_bits_get( bits, 1 ) ) /* framerateind */ + { + sequence->framerate_numerator = lsmash_bits_get( bits, 16 ) + 1; + sequence->framerate_denominator = 32; + } + else + { + static const uint32_t vc1_frameratenr_table[8] = { 0, 24, 25, 30, 50, 60, 48, 72 }; + uint8_t frameratenr = lsmash_bits_get( bits, 8 ); + if( frameratenr == 0 || frameratenr > 7 ) + return -1; + uint8_t frameratedr = lsmash_bits_get( bits, 4 ); + if( frameratedr != 1 && frameratedr != 2 ) + return -1; + if( frameratedr == 1 ) + { + sequence->framerate_numerator = vc1_frameratenr_table[ frameratenr ]; + sequence->framerate_denominator = 1; + } + else + { + sequence->framerate_numerator = vc1_frameratenr_table[ frameratenr ] * 1000; + sequence->framerate_denominator = 1001; + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* color_format_flag */ + { + sequence->color_prim = lsmash_bits_get( bits, 8 ); + sequence->transfer_char = lsmash_bits_get( bits, 8 ); + sequence->matrix_coef = lsmash_bits_get( bits, 8 ); + } + sequence->hrd_param_flag = lsmash_bits_get( bits, 1 ); + if( sequence->hrd_param_flag ) + vc1_parse_hrd_param( bits, &sequence->hrd_param ); + } + /* '1' and stuffing bits ('0's) */ + IF_INVALID_VALUE( !lsmash_bits_get( bits, 1 ) ) + return -1; + lsmash_bits_empty( bits ); + /* Preparation for creating VC1SpecificBox */ + if( probe ) + { + vc1_sequence_header_t *first_sequence = &info->first_sequence; + if( !first_sequence->present ) + { + sequence->ebdu = malloc( ebdu_length ); + if( !sequence->ebdu ) + return -1; + memcpy( sequence->ebdu, ebdu, ebdu_length ); + sequence->length = ebdu_length; + sequence->present = 1; + *first_sequence = *sequence; + } + else if( first_sequence->ebdu && (first_sequence->length == ebdu_length) ) + info->multiple_sequence |= !!memcmp( ebdu, first_sequence->ebdu, ebdu_length ); + } + return bits->bs->error ? -1 : 0; +} + +static int vc1_parse_entry_point_header( mp4sys_vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_length, int probe ) +{ + lsmash_bits_t *bits = info->bits; + vc1_sequence_header_t *sequence = &info->sequence; + vc1_entry_point_t *entry_point = &info->entry_point; + if( vc1_bits_import_rbdu_from_ebdu( bits, info->rbdu_buffer, ebdu, ebdu_length ) ) + return -1; + memset( entry_point, 0, sizeof(vc1_entry_point_t) ); + uint8_t broken_link_flag = lsmash_bits_get( bits, 1 ); /* 0: no concatenation between the current and the previous entry points + * 1: concatenated and needed to discard B-pictures */ + entry_point->closed_entry_point = lsmash_bits_get( bits, 1 ); /* 0: Open RAP, 1: Closed RAP */ + if( !broken_link_flag && entry_point->closed_entry_point ) + return -1; /* invalid combination */ + lsmash_bits_get( bits, 4 ); /* panscan_flag (1) + * refdist_flag (1) + * loopfilter (1) + * fastuvmc (1) */ + uint8_t extended_mv = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 6 ); /* dquant (2) + * vstransform (1) + * overlap (1) + * quantizer (2) */ + if( sequence->hrd_param_flag ) + for( uint8_t i = 0; i < sequence->hrd_param.hrd_num_leaky_buckets; i++ ) + lsmash_bits_get( bits, 8 ); /* hrd_full */ + /* Decide coded size here. + * The correct formula is defined in Amendment 2:2011 to SMPTE ST 421M:2006. + * Don't use the formula specified in SMPTE 421M-2006. */ + uint16_t coded_width; + uint16_t coded_height; + if( lsmash_bits_get( bits, 1 ) ) /* coded_size_flag */ + { + coded_width = lsmash_bits_get( bits, 12 ); + coded_height = lsmash_bits_get( bits, 12 ); + } + else + { + coded_width = sequence->max_coded_width; + coded_height = sequence->max_coded_height; + } + coded_width = 2 * (coded_width + 1); /* corrected */ + coded_height = 2 * (coded_height + 1); /* corrected */ + if( sequence->disp_horiz_size == 0 || sequence->disp_vert_size == 0 ) + { + sequence->disp_horiz_size = coded_width; + sequence->disp_vert_size = coded_height; + } + /* */ + if( extended_mv ) + lsmash_bits_get( bits, 1 ); /* extended_dmv */ + if( lsmash_bits_get( bits, 1 ) ) /* range_mapy_flag */ + lsmash_bits_get( bits, 3 ); /* range_mapy */ + if( lsmash_bits_get( bits, 1 ) ) /* range_mapuv_flag */ + lsmash_bits_get( bits, 3 ); /* range_mapuv */ + /* '1' and stuffing bits ('0's) */ + IF_INVALID_VALUE( !lsmash_bits_get( bits, 1 ) ) + return -1; + lsmash_bits_empty( bits ); + /* Preparation for creating VC1SpecificBox */ + if( probe ) + { + vc1_entry_point_t *first_entry_point = &info->first_entry_point; + if( !first_entry_point->present ) + { + entry_point->ebdu = malloc( ebdu_length ); + if( !entry_point->ebdu ) + return -1; + memcpy( entry_point->ebdu, ebdu, ebdu_length ); + entry_point->length = ebdu_length; + entry_point->present = 1; + *first_entry_point = *entry_point; + } + else if( first_entry_point->ebdu && (first_entry_point->length == ebdu_length) ) + info->multiple_entry_point |= !!memcmp( ebdu, first_entry_point->ebdu, ebdu_length ); + } + return bits->bs->error ? -1 : 0; +} + +static inline uint8_t vc1_get_vlc( lsmash_bits_t *bits, int length ) +{ + uint8_t value = 0; + for( int i = 0; i < length; i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) + value = (value << 1) | 1; + else + { + value = value << 1; + break; + } + } + return value; +} + +static int vc1_parse_advanced_picture( lsmash_bits_t *bits, + vc1_sequence_header_t *sequence, vc1_picture_info_t *picture, + uint8_t *rbdu_buffer, uint8_t *ebdu, uint64_t ebdu_length ) +{ + if( vc1_bits_import_rbdu_from_ebdu( bits, rbdu_buffer, ebdu, ebdu_length ) ) + return -1; + if( sequence->interlace ) + picture->frame_coding_mode = vc1_get_vlc( bits, 2 ); + else + picture->frame_coding_mode = 0; + if( picture->frame_coding_mode != 0x3 ) + picture->type = vc1_get_vlc( bits, 4 ); /* ptype (variable length) */ + else + picture->type = lsmash_bits_get( bits, 3 ); /* fptype (3) */ + picture->present = 1; + lsmash_bits_empty( bits ); + return bits->bs->error ? -1 : 0; +} + +static int vc1_supplement_buffer( mp4sys_vc1_info_t *info, uint32_t size ) +{ + uint32_t buffer_pos_offset = info->stream_buffer_pos - info->stream_buffer; + uint32_t buffer_valid_length = info->stream_buffer_end - info->stream_buffer; + lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( info->buffers, size ); + if( !temp ) + return -1; + info->buffers = temp; + info->stream_buffer = lsmash_withdraw_buffer( info->buffers, 1 ); + info->rbdu_buffer = lsmash_withdraw_buffer( info->buffers, 2 ); + info->access_unit.data = lsmash_withdraw_buffer( info->buffers, 3 ); + info->access_unit.incomplete_data = lsmash_withdraw_buffer( info->buffers, 4 ); + info->stream_buffer_pos = info->stream_buffer + buffer_pos_offset; + info->stream_buffer_end = info->stream_buffer + buffer_valid_length; + return 0; +} + +static inline int vc1_check_next_start_code_prefix( uint8_t *buf_pos, uint8_t *buf_end ) +{ + return ((buf_pos + 2) < buf_end) && !buf_pos[0] && !buf_pos[1] && (buf_pos[2] == 0x01); +} + +static inline int vc1_check_next_start_code_suffix( uint8_t *p_bdu_type, uint8_t **p_start_code_suffix ) +{ + uint8_t bdu_type = **p_start_code_suffix; + if( (bdu_type >= 0x00 && bdu_type <= 0x09) || (bdu_type >= 0x20 && bdu_type <= 0xFF) ) + return -1; /* SMPTE reserved or forbidden value */ + *p_bdu_type = bdu_type; + ++ *p_start_code_suffix; + return 0; +} + +static void vc1_check_buffer_shortage( mp4sys_importer_t *importer, uint32_t anticipation_bytes ) +{ + mp4sys_vc1_info_t *info = (mp4sys_vc1_info_t *)importer->info; + assert( anticipation_bytes < info->buffers->buffer_size ); + if( info->no_more_read ) + return; + uint32_t remainder_bytes = info->stream_buffer_end - info->stream_buffer_pos; + if( remainder_bytes <= anticipation_bytes ) + { + /* Move unused data to the head of buffer. */ + for( uint32_t i = 0; i < remainder_bytes; i++ ) + *(info->stream_buffer + i) = *(info->stream_buffer_pos + i); + /* Read and store the next data into the buffer. + * Move the position of buffer on the head. */ + uint32_t read_size = fread( info->stream_buffer + remainder_bytes, 1, info->buffers->buffer_size - remainder_bytes, importer->stream ); + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + remainder_bytes + read_size; + info->no_more_read = read_size == 0 ? feof( importer->stream ) : 0; + } +} + +static inline int vc1_find_au_delimit_by_bdu_type( uint8_t bdu_type, uint8_t prev_bdu_type ) +{ + /* In any access unit, EBDU with smaller least significant 8-bits of BDU type doesn't precede EBDU with larger one. + * Therefore, the condition: (bdu_type 0xF) > (prev_bdu_type & 0xF) is more precisely. + * No two or more frame start codes shall be in the same access unit. */ + return bdu_type > prev_bdu_type || (bdu_type == 0x0D && prev_bdu_type == 0x0D); +} + +static inline void vc1_update_au_property( vc1_access_unit_t *access_unit, vc1_picture_info_t *picture ) +{ + access_unit->random_accessible = picture->random_accessible; + access_unit->closed_gop = picture->closed_gop; + /* I-picture + * Be coded using information only from itself. (independent) + * All the macroblocks in an I-picture are intra-coded. + * P-picture + * Be coded using motion compensated prediction from past reference fields or frame. + * Can contain macroblocks that are inter-coded (i.e. coded using prediction) and macroblocks that are intra-coded. + * B-picture + * Be coded using motion compensated prediction from past and/or future reference fields or frames. (bi-predictive) + * Cannot be used for predicting any other picture. (disposable) + * BI-picture + * All the macroblocks in BI-picture are intra-coded. (independent) + * Cannot be used for predicting any other picture. (disposable) */ + if( picture->frame_coding_mode == 0x3 ) + { + /* field interlace */ + access_unit->independent = picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_II || picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI; + access_unit->non_bipredictive = picture->type < VC1_ADVANCED_FIELD_PICTURE_TYPE_BB || picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI; + access_unit->disposable = picture->type >= VC1_ADVANCED_FIELD_PICTURE_TYPE_BB; + } + else + { + /* frame progressive/interlace */ + access_unit->independent = picture->type == VC1_ADVANCED_PICTURE_TYPE_I || picture->type == VC1_ADVANCED_PICTURE_TYPE_BI; + access_unit->non_bipredictive = picture->type != VC1_ADVANCED_PICTURE_TYPE_B; + access_unit->disposable = picture->type == VC1_ADVANCED_PICTURE_TYPE_B || picture->type == VC1_ADVANCED_PICTURE_TYPE_BI; + } + picture->present = 0; + picture->type = 0; + picture->closed_gop = 0; + picture->start_of_sequence = 0; + picture->random_accessible = 0; +} + +static inline int vc1_complete_au( vc1_access_unit_t *access_unit, vc1_picture_info_t *next_picture, int probe ) +{ + if( !next_picture->present ) + return 0; + if( !probe ) + memcpy( access_unit->data, access_unit->incomplete_data, access_unit->incomplete_data_length ); + access_unit->data_length = access_unit->incomplete_data_length; + access_unit->incomplete_data_length = 0; + vc1_update_au_property( access_unit, next_picture ); + return 1; +} + +static inline void vc1_append_ebdu_to_au( vc1_access_unit_t *access_unit, uint8_t *ebdu, uint32_t ebdu_length, int probe ) +{ + if( !probe ) + memcpy( access_unit->incomplete_data + access_unit->incomplete_data_length, ebdu, ebdu_length ); + /* Note: access_unit->incomplete_data_length shall be 0 immediately after AU has completed. + * Therefore, possible_au_length in vc1_get_access_unit_internal() can't be used here + * to avoid increasing AU length monotonously through the entire stream. */ + access_unit->incomplete_data_length += ebdu_length; +} + +static inline void vc1_get_au_internal_end( mp4sys_vc1_info_t *info, vc1_access_unit_t *access_unit, uint8_t bdu_type, int no_more_buf ) +{ + info->status = info->no_more_read && no_more_buf && (access_unit->incomplete_data_length == 0) + ? MP4SYS_IMPORTER_EOF + : MP4SYS_IMPORTER_OK; + info->bdu_type = bdu_type; +} + +static int vc1_get_au_internal_succeeded( mp4sys_vc1_info_t *info, vc1_access_unit_t *access_unit, uint8_t bdu_type, int no_more_buf ) +{ + vc1_get_au_internal_end( info, access_unit, bdu_type, no_more_buf ); + access_unit->number += 1; + return 0; +} + +static int vc1_get_au_internal_failed( mp4sys_vc1_info_t *info, vc1_access_unit_t *access_unit, uint8_t bdu_type, int no_more_buf, int complete_au ) +{ + vc1_get_au_internal_end( info, access_unit, bdu_type, no_more_buf ); + if( complete_au ) + access_unit->number += 1; + return -1; +} + +static int vc1_get_access_unit_internal( mp4sys_importer_t *importer, mp4sys_vc1_info_t *info, uint32_t track_number, int probe ) +{ + vc1_access_unit_t *access_unit = &info->access_unit; + uint8_t bdu_type = info->bdu_type; + uint64_t consecutive_zero_byte_count = 0; + uint64_t ebdu_length = 0; + int no_more_buf = 0; + int complete_au = 0; + access_unit->data_length = 0; + while( 1 ) + { + vc1_check_buffer_shortage( importer, 2 ); + no_more_buf = info->stream_buffer_pos >= info->stream_buffer_end; + int no_more = info->no_more_read && no_more_buf; + if( vc1_check_next_start_code_prefix( info->stream_buffer_pos, info->stream_buffer_end ) || no_more ) + { + if( no_more && ebdu_length == 0 ) + { + /* For the last EBDU. + * This EBDU already has been appended into the latest access unit and parsed. */ + ebdu_length = access_unit->incomplete_data_length; + vc1_complete_au( access_unit, &info->next_picture, probe ); + return vc1_get_au_internal_succeeded( info, access_unit, bdu_type, no_more_buf ); + } + ebdu_length += VC1_START_CODE_LENGTH; + uint64_t next_scs_file_offset = info->ebdu_head_pos + ebdu_length + !no_more * VC1_START_CODE_PREFIX_LENGTH; + uint8_t *next_ebdu_pos = info->stream_buffer_pos; /* Memorize position of beginning of the next EBDU in buffer. + * This is used when backward reading of stream doesn't occur. */ + int read_back = 0; +#if 0 + if( probe ) + { + fprintf( stderr, "BDU type: %"PRIu8" \n", bdu_type ); + fprintf( stderr, " EBDU position: %"PRIx64" \n", info->ebdu_head_pos ); + fprintf( stderr, " EBDU length: %"PRIx64" (%"PRIu64") \n", ebdu_length - consecutive_zero_byte_count, + ebdu_length - consecutive_zero_byte_count ); + fprintf( stderr, " consecutive_zero_byte_count: %"PRIx64" \n", consecutive_zero_byte_count ); + fprintf( stderr, " Next start code suffix position: %"PRIx64"\n", next_scs_file_offset ); + } +#endif + if( bdu_type >= 0x0A && bdu_type <= 0x0F ) + { + /* Get the current EBDU here. */ + ebdu_length -= consecutive_zero_byte_count; /* Any EBDU doesn't have zero bytes at the end. */ + uint64_t possible_au_length = access_unit->incomplete_data_length + ebdu_length; + if( info->buffers->buffer_size < possible_au_length ) + { + if( vc1_supplement_buffer( info, 2 * possible_au_length ) ) + vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + next_ebdu_pos = info->stream_buffer_pos; + } + /* Move to the first byte of the current EBDU. */ + read_back = (info->stream_buffer_pos - info->stream_buffer) < (ebdu_length + consecutive_zero_byte_count); + if( read_back ) + { + lsmash_fseek( importer->stream, info->ebdu_head_pos, SEEK_SET ); + int read_fail = fread( info->stream_buffer, 1, ebdu_length, importer->stream ) != ebdu_length; + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + ebdu_length; + if( read_fail ) + vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); +#if 0 + if( probe ) + fprintf( stderr, " ----Read Back\n" ); +#endif + } + else + info->stream_buffer_pos -= ebdu_length + consecutive_zero_byte_count; + /* Complete the current access unit if encountered delimiter of current access unit. */ + if( vc1_find_au_delimit_by_bdu_type( bdu_type, info->prev_bdu_type ) ) + /* The last video coded EBDU belongs to the access unit you want at this time. */ + complete_au = vc1_complete_au( access_unit, &info->next_picture, probe ); + /* Process EBDU by its BDU type and append it to access unit. */ + switch( bdu_type ) + { + /* FRM_SC: Frame start code + * FLD_SC: Field start code + * SLC_SC: Slice start code + * SEQ_SC: Sequence header start code + * EP_SC: Entry-point start code + * PIC_L: Picture layer + * SLC_L: Slice layer + * SEQ_L: Sequence layer + * EP_L: Entry-point layer */ + case 0x0D : /* Frame + * For the Progressive or Frame Interlace mode, shall signal the beginning of a new video frame. + * For the Field Interlace mode, shall signal the beginning of a sequence of two independently coded video fields. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][[SLC_SC][SLC_L] (optional)] ... */ + if( vc1_parse_advanced_picture( info->bits, &info->sequence, &info->next_picture, info->rbdu_buffer, + info->stream_buffer_pos, ebdu_length ) ) + return vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + case 0x0C : /* Field + * Shall only be used for Field Interlaced frames + * and shall only be used to signal the beginning of the second field of the frame. + * [FRM_SC][PIC_L][FLD_SC][PIC_L][[SLC_SC][SLC_L] (optional)] ... + * Field start code is followed by INTERLACE_FIELD_PICTURE_FIELD2() which doesn't have info of its field picture type.*/ + break; + case 0x0B : /* Slice + * Shall not be used for start code of the first slice of a frame. + * Shall not be used for start code of the first slice of an interlace field coded picture. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][SLC_SC][SLC_L][[SLC_SC][SLC_L] (optional)] ... + * Slice layer may repeat frame header. We just ignore it. */ + info->slice_present = 1; + break; + case 0x0E : /* Entry-point header + * Entry-point indicates the direct followed frame is a start of group of frames. + * Entry-point doesn't indicates the frame is a random access point when multiple sequence headers are present, + * since it is necessary to decode sequence header which subsequent frames belong to for decoding them. + * Entry point shall be followed by + * 1. I-picture - progressive or frame interlace + * 2. I/I-picture, I/P-picture, or P/I-picture - field interlace + * [[SEQ_SC][SEQ_L] (optional)][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( vc1_parse_entry_point_header( info, info->stream_buffer_pos, ebdu_length, probe ) ) + return vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + info->next_picture.closed_gop = info->entry_point.closed_entry_point; + info->next_picture.random_accessible = info->multiple_sequence ? info->next_picture.start_of_sequence : 1; + break; + case 0x0F : /* Sequence header + * [SEQ_SC][SEQ_L][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( vc1_parse_sequence_header( info, info->stream_buffer_pos, ebdu_length, probe ) ) + return vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + info->next_picture.start_of_sequence = 1; + break; + default : /* End-of-sequence (0x0A) */ + break; + } + vc1_append_ebdu_to_au( access_unit, info->stream_buffer_pos, ebdu_length, probe ); + } + else /* We don't support other BDU types such as user data yet. */ + return vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + /* Move to the first byte of the next start code suffix. */ + if( read_back ) + { + lsmash_fseek( importer->stream, next_scs_file_offset, SEEK_SET ); + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + } + else + info->stream_buffer_pos = next_ebdu_pos + VC1_START_CODE_PREFIX_LENGTH; + info->prev_bdu_type = bdu_type; + vc1_check_buffer_shortage( importer, 0 ); + no_more_buf = info->stream_buffer_pos >= info->stream_buffer_end; + ebdu_length = 0; + no_more = info->no_more_read && no_more_buf; + if( !no_more ) + { + /* Check the next BDU type. */ + if( vc1_check_next_start_code_suffix( &bdu_type, &info->stream_buffer_pos ) ) + return vc1_get_au_internal_failed( info, access_unit, bdu_type, no_more_buf, complete_au ); + info->ebdu_head_pos = next_scs_file_offset - VC1_START_CODE_PREFIX_LENGTH; + } + /* If there is no more data in the stream, and flushed chunk of EBDUs, flush it as complete AU here. */ + else if( access_unit->incomplete_data_length && access_unit->data_length == 0 ) + { + vc1_complete_au( access_unit, &info->next_picture, probe ); + return vc1_get_au_internal_succeeded( info, access_unit, bdu_type, no_more_buf ); + } + if( complete_au ) + return vc1_get_au_internal_succeeded( info, access_unit, bdu_type, no_more_buf ); + consecutive_zero_byte_count = 0; + continue; /* Avoid increment of ebdu_length. */ + } + else if( !no_more ) + { + if( *info->stream_buffer_pos ++ ) + consecutive_zero_byte_count = 0; + else + ++consecutive_zero_byte_count; + } + ++ebdu_length; + } +} + +static int mp4sys_vc1_get_accessunit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + debug_if( !importer || !importer->info || !buffered_sample->data || !buffered_sample->length ) + return -1; + if( !importer->info || track_number != 1 ) + return -1; + mp4sys_vc1_info_t *info = (mp4sys_vc1_info_t *)importer->info; + mp4sys_importer_status current_status = info->status; + if( current_status == MP4SYS_IMPORTER_ERROR || buffered_sample->length < info->max_au_length ) + return -1; + if( current_status == MP4SYS_IMPORTER_EOF ) + { + buffered_sample->length = 0; + return 0; + } + if( vc1_get_access_unit_internal( importer, info, track_number, 0 ) ) + { + info->status = MP4SYS_IMPORTER_ERROR; + return -1; + } + vc1_access_unit_t *access_unit = &info->access_unit; + buffered_sample->dts = info->ts_list.timestamp[access_unit->number - 1].dts; + buffered_sample->cts = info->ts_list.timestamp[access_unit->number - 1].cts; + buffered_sample->prop.leading = access_unit->non_bipredictive || buffered_sample->cts >= info->last_ref_intra_cts + ? ISOM_SAMPLE_IS_NOT_LEADING : access_unit->independent + ? ISOM_SAMPLE_IS_DECODABLE_LEADING + : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + if( access_unit->independent && !access_unit->disposable ) + info->last_ref_intra_cts = buffered_sample->cts; + if( info->composition_reordering_present && !access_unit->disposable && !access_unit->closed_gop ) + buffered_sample->prop.allow_earlier = QT_SAMPLE_EARLIER_PTS_ALLOWED; + buffered_sample->prop.independent = access_unit->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; + buffered_sample->prop.disposable = access_unit->disposable ? ISOM_SAMPLE_IS_DISPOSABLE : ISOM_SAMPLE_IS_NOT_DISPOSABLE; + buffered_sample->prop.redundant = ISOM_SAMPLE_HAS_NO_REDUNDANCY; + if( access_unit->random_accessible ) + buffered_sample->prop.random_access_type = ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + buffered_sample->length = access_unit->data_length; + memcpy( buffered_sample->data, access_unit->data, access_unit->data_length ); + return current_status; +} + +static uint8_t *vc1_create_dvc1( mp4sys_vc1_info_t *info, uint32_t *dvc1_length ) +{ + lsmash_bits_t *bits = info->bits; + vc1_sequence_header_t *sequence = &info->first_sequence; + lsmash_bits_put( bits, 0, 32 ); /* box size */ + lsmash_bits_put( bits, ISOM_BOX_TYPE_DVC1, 32 ); /* box type = 'dvc1' */ + lsmash_bits_put( bits, sequence->profile << 2, 4 ); /* profile */ + lsmash_bits_put( bits, sequence->level, 3 ); /* level */ + lsmash_bits_put( bits, 0, 1 ); /* reserved */ + /* VC1AdvDecSpecStruc (for Advanced Profile) */ + lsmash_bits_put( bits, sequence->level, 3 ); /* level (identical to the previous level field) */ + lsmash_bits_put( bits, 0, 1 ); /* cbr */ + lsmash_bits_put( bits, 0, 6 ); /* reserved */ + lsmash_bits_put( bits, !sequence->interlace, 1 ); /* no_interlace */ + lsmash_bits_put( bits, !info->multiple_sequence, 1 ); /* no_multiple_seq */ + lsmash_bits_put( bits, !info->multiple_entry_point, 1 ); /* no_multiple_entry */ + lsmash_bits_put( bits, !info->slice_present, 1 ); /* no_slice_code */ + lsmash_bits_put( bits, !info->composition_reordering_present, 1 ); /* no_bframe */ + lsmash_bits_put( bits, 0, 1 ); /* reserved */ + uint32_t framerate = sequence->framerate_flag + ? ((double)sequence->framerate_numerator / sequence->framerate_denominator) + 0.5 + : 0xffffffff; /* 0xffffffff means framerate is unknown or unspecified. */ + lsmash_bits_put( bits, framerate, 32 ); /* framerate */ + /* seqhdr_ephdr[] */ + uint8_t *ebdu = sequence->ebdu; + for( uint32_t i = 0; i < sequence->length; i++ ) + lsmash_bits_put( bits, *ebdu++, 8 ); + ebdu = info->first_entry_point.ebdu; + for( uint32_t i = 0; i < info->first_entry_point.length; i++ ) + lsmash_bits_put( bits, *ebdu++, 8 ); + /* */ + uint8_t *dvc1 = lsmash_bits_export_data( bits, dvc1_length ); + lsmash_bits_empty( bits ); + /* Update box size. */ + dvc1[0] = ((*dvc1_length) >> 24) & 0xff; + dvc1[1] = ((*dvc1_length) >> 16) & 0xff; + dvc1[2] = ((*dvc1_length) >> 8) & 0xff; + dvc1[3] = (*dvc1_length) & 0xff; + return dvc1; +} + +static lsmash_video_summary_t *vc1_create_summary( mp4sys_vc1_info_t *info ) +{ + if( !info->first_sequence.present || !info->first_entry_point.present ) + return NULL; + lsmash_video_summary_t *summary = (lsmash_video_summary_t *)lsmash_create_summary( MP4SYS_STREAM_TYPE_VisualStream ); + if( !summary ) + return NULL; + summary->exdata = vc1_create_dvc1( info, &summary->exdata_length ); + if( !summary->exdata ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary); + return NULL; + } + vc1_sequence_header_t *sequence = &info->first_sequence; + summary->sample_type = ISOM_CODEC_TYPE_VC_1_VIDEO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_VC_1_VIDEO; + summary->timescale = sequence->framerate_numerator; + summary->timebase = sequence->framerate_denominator; + summary->vfr = !sequence->framerate_flag; + summary->width = sequence->disp_horiz_size; + summary->height = sequence->disp_vert_size; + summary->par_h = sequence->aspect_width; + summary->par_v = sequence->aspect_height; + summary->primaries = sequence->color_prim; + summary->transfer = sequence->transfer_char; + summary->matrix = sequence->matrix_coef; + return summary; +} + +static int mp4sys_vc1_probe( mp4sys_importer_t *importer ) +{ +#define VC1_CHECK_FIRST_START_CODE( x ) (!(x)[0] && !(x)[1] && ((x)[2] == 0x01)) + /* Find the first start code. */ + mp4sys_vc1_info_t *info = mp4sys_create_vc1_info(); + if( !info ) + return -1; + info->stream_buffer_pos = info->stream_buffer; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + info->no_more_read = info->stream_buffer >= info->stream_buffer_end ? feof( importer->stream ) : 0; + while( 1 ) + { + /* Invalid if encountered any value of non-zero before the first start code. */ + IF_INVALID_VALUE( *info->stream_buffer_pos ) + goto fail; + /* The first EBDU in decoding order of the stream shall have start code (0x000001). */ + if( VC1_CHECK_FIRST_START_CODE( info->stream_buffer_pos ) ) + break; + /* If the first trial of finding start code of sequence header failed, we assume this stream is not byte stream format of VC-1. */ + if( (info->stream_buffer_pos + VC1_START_CODE_LENGTH) == info->stream_buffer_end ) + goto fail; + ++info->stream_buffer_pos; + } + /* OK. It seems the stream has a sequence header of VC-1. */ + importer->info = info; + uint64_t first_ebdu_head_pos = info->stream_buffer_pos - info->stream_buffer; + info->stream_buffer_pos += VC1_START_CODE_PREFIX_LENGTH; + vc1_check_buffer_shortage( importer, 0 ); + uint8_t first_bdu_type = *info->stream_buffer_pos ++; + info->status = info->no_more_read ? MP4SYS_IMPORTER_EOF : MP4SYS_IMPORTER_OK; + info->bdu_type = first_bdu_type; + info->ebdu_head_pos = first_ebdu_head_pos; + /* Parse all EBDU in the stream for preparation of calculating timestamps. */ + uint32_t cts_alloc = (1 << 12) * sizeof(uint64_t); + uint64_t *cts = malloc( cts_alloc ); + if( !cts ) + goto fail; + uint32_t num_access_units = 0; + uint32_t num_consecutive_b = 0; + fprintf( stderr, "Analyzing stream as VC-1\r" ); + while( info->status != MP4SYS_IMPORTER_EOF ) + { +#if 0 + fprintf( stderr, "Analyzing stream as VC-1: %"PRIu32"\n", num_access_units + 1 ); +#endif + if( vc1_get_access_unit_internal( importer, info, 0, 1 ) ) + goto fail; + /* In the case where B-pictures exist + * Decode order + * I[0]P[1]P[2]B[3]B[4]P[5]... + * DTS + * 0 1 2 3 4 5 ... + * Composition order + * I[0]P[1]B[3]B[4]P[2]P[5]... + * CTS + * 1 2 3 4 5 6 ... + * We assumes B or BI-pictures always be present in the stream here. */ + if( !info->access_unit.disposable ) + { + /* Apply CTS of the last B-picture plus 1 to the last non-B-picture. */ + if( num_access_units > num_consecutive_b ) + cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; + num_consecutive_b = 0; + } + else /* B or BI-picture */ + { + /* B and BI-pictures shall be output or displayed in the same order as they are encoded. */ + cts[ num_access_units ] = num_access_units; + ++num_consecutive_b; + } + if( cts_alloc <= num_access_units * sizeof(uint64_t) ) + { + uint32_t alloc = 2 * num_access_units * sizeof(uint64_t); + uint64_t *temp = realloc( cts, alloc ); + if( !temp ) + { + free( cts ); + goto fail; + } + cts = temp; + cts_alloc = alloc; + } + info->max_au_length = LSMASH_MAX( info->access_unit.data_length, info->max_au_length ); + ++num_access_units; + } + if( num_access_units > num_consecutive_b ) + cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; + else + { + free( cts ); + goto fail; + } + fprintf( stderr, " \r" ); + /* Construct timestamps. */ + lsmash_media_ts_t *timestamp = malloc( num_access_units * sizeof(lsmash_media_ts_t) ); + if( !timestamp ) + { + free( cts ); + goto fail; + } + for( uint32_t i = 1; i < num_access_units; i++ ) + if( cts[i] < cts[i - 1] ) + { + info->composition_reordering_present = 1; + break; + } + if( info->composition_reordering_present ) + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].cts = cts[i]; + timestamp[i].dts = i; + } + else + for( uint32_t i = 0; i < num_access_units; i++ ) + timestamp[i].cts = timestamp[i].dts = i; + free( cts ); +#if 0 + for( uint32_t i = 0; i < num_access_units; i++ ) + fprintf( stderr, "Timestamp[%"PRIu32"]: DTS=%"PRIu64", CTS=%"PRIu64"\n", i, timestamp[i].dts, timestamp[i].cts ); +#endif + info->summary = vc1_create_summary( info ); + if( !info->summary || lsmash_add_entry( importer->summaries, info->summary ) ) + goto fail; + info->ts_list.sample_count = num_access_units; + info->ts_list.timestamp = timestamp; + /* Go back to layer of the first EBDU. */ + lsmash_fseek( importer->stream, first_ebdu_head_pos, SEEK_SET ); + info->status = MP4SYS_IMPORTER_OK; + info->bdu_type = first_bdu_type; + info->prev_bdu_type = 0; + info->no_more_read = 0; + info->summary->max_au_length = info->max_au_length; + info->summary = NULL; + info->stream_buffer_pos = info->stream_buffer + VC1_START_CODE_LENGTH; + info->stream_buffer_end = info->stream_buffer + fread( info->stream_buffer, 1, info->buffers->buffer_size, importer->stream ); + info->ebdu_head_pos = first_ebdu_head_pos; + uint8_t *temp_access_unit = info->access_unit.data; + uint8_t *temp_incomplete_access_unit = info->access_unit.incomplete_data; + memset( &info->access_unit, 0, sizeof(vc1_access_unit_t) ); + info->access_unit.data = temp_access_unit; + info->access_unit.incomplete_data = temp_incomplete_access_unit; + memset( &info->next_picture, 0, sizeof(vc1_picture_info_t) ); + importer->info = info; + return 0; +fail: + mp4sys_remove_vc1_info( info ); + lsmash_remove_entries( importer->summaries, lsmash_cleanup_summary ); + return -1; +#undef VC1_CHECK_FIRST_START_CODE +} + +static uint32_t mp4sys_vc1_get_last_delta( mp4sys_importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_vc1_info_t *info = (mp4sys_vc1_info_t *)importer->info; + if( !info || track_number != 1 || info->status != MP4SYS_IMPORTER_EOF ) + return 0; + return info->ts_list.sample_count > 1 + ? 1 + : UINT32_MAX; /* arbitrary */ +} + +#undef VC1_START_CODE_PREFIX_LENGTH +#undef VC1_START_CODE_SUFFIX_LENGTH +#undef VC1_START_CODE_LENGTH + +const static mp4sys_importer_functions mp4sys_vc1_importer = +{ + "VC-1", + 1, + mp4sys_vc1_probe, + mp4sys_vc1_get_accessunit, + mp4sys_vc1_get_last_delta, + mp4sys_vc1_cleanup +}; + +/*************************************************************************** + importer public interfaces +***************************************************************************/ + + +/******** importer listing table ********/ +const static mp4sys_importer_functions* mp4sys_importer_tbl[] = { + &mp4sys_adts_importer, + &mp4sys_mp3_importer, + &mp4sys_amr_importer, + &mp4sys_ac3_importer, + &mp4sys_eac3_importer, + &mp4sys_als_importer, + &mp4sys_h264_importer, + &mp4sys_vc1_importer, + NULL, +}; + +/******** importer public functions ********/ + +void mp4sys_importer_close( mp4sys_importer_t* importer ) +{ + if( !importer ) + return; + if( !importer->is_stdin && importer->stream ) + fclose( importer->stream ); + if( importer->funcs.cleanup ) + importer->funcs.cleanup( importer ); + lsmash_remove_list( importer->summaries, lsmash_cleanup_summary ); + free( importer ); +} + +mp4sys_importer_t *mp4sys_importer_open( const char *identifier, const char *format ) +{ + if( identifier == NULL ) + return NULL; + + int auto_detect = ( format == NULL || !strcmp( format, "auto" ) ); + mp4sys_importer_t *importer = (mp4sys_importer_t *)lsmash_malloc_zero( sizeof(mp4sys_importer_t) ); + if( !importer ) + return NULL; + + if( !strcmp( identifier, "-" ) ) + { + /* special treatment for stdin */ + if( auto_detect ) + { + free( importer ); + return NULL; + } + importer->stream = stdin; + importer->is_stdin = 1; + } + else if( (importer->stream = fopen( identifier, "rb" )) == NULL ) + { + mp4sys_importer_close( importer ); + return NULL; + } + importer->summaries = lsmash_create_entry_list(); + if( !importer->summaries ) + { + mp4sys_importer_close( importer ); + return NULL; + } + /* find importer */ + const mp4sys_importer_functions *funcs; + if( auto_detect ) + { + /* just rely on detector. */ + for( int i = 0; (funcs = mp4sys_importer_tbl[i]) != NULL; i++ ) + { + if( !funcs->detectable ) + continue; + if( !funcs->probe( importer ) || lsmash_fseek( importer->stream, 0, SEEK_SET ) ) + break; + } + } + else + { + /* needs name matching. */ + for( int i = 0; (funcs = mp4sys_importer_tbl[i]) != NULL; i++ ) + { + if( strcmp( funcs->name, format ) ) + continue; + if( funcs->probe( importer ) ) + funcs = NULL; + break; + } + } + if( !funcs ) + { + mp4sys_importer_close( importer ); + return NULL; + } + importer->funcs = *funcs; + return importer; +} + +/* 0 if success, positive if changed, negative if failed */ +int mp4sys_importer_get_access_unit( mp4sys_importer_t* importer, uint32_t track_number, lsmash_sample_t *buffered_sample ) +{ + if( !importer || !importer->funcs.get_accessunit || !buffered_sample->data || buffered_sample->length == 0 ) + return -1; + return importer->funcs.get_accessunit( importer, track_number, buffered_sample ); +} + +/* Return 0 if failed, otherwise succeeded. */ +uint32_t mp4sys_importer_get_last_delta( mp4sys_importer_t *importer, uint32_t track_number ) +{ + if( !importer || !importer->funcs.get_last_delta ) + return -1; + return importer->funcs.get_last_delta( importer, track_number ); +} + +uint32_t mp4sys_importer_get_track_count( mp4sys_importer_t *importer ) +{ + if( !importer || !importer->summaries ) + return 0; + return importer->summaries->entry_count; +} + +lsmash_summary_t *mp4sys_duplicate_summary( mp4sys_importer_t *importer, uint32_t track_number ) +{ + if( !importer ) + return NULL; + lsmash_summary_t *src_summary = lsmash_get_entry_data( importer->summaries, track_number ); + if( !src_summary ) + return NULL; + lsmash_summary_t *summary = lsmash_create_summary( src_summary->stream_type ); + if( !summary ) + return NULL; + switch( src_summary->stream_type ) + { + case MP4SYS_STREAM_TYPE_VisualStream : + memcpy( summary, src_summary, sizeof(lsmash_video_summary_t) ); + break; + case MP4SYS_STREAM_TYPE_AudioStream : + memcpy( summary, src_summary, sizeof(lsmash_audio_summary_t) ); + break; + default : + lsmash_cleanup_summary( summary ); + return NULL; + } + summary->exdata = NULL; + summary->exdata_length = 0; + if( lsmash_summary_add_exdata( summary, src_summary->exdata, src_summary->exdata_length ) ) + { + lsmash_cleanup_summary( summary ); + return NULL; + } + return summary; +} diff --git a/output/mp4/importer.h b/output/mp4/importer.h new file mode 100644 index 0000000..089f656 --- /dev/null +++ b/output/mp4/importer.h @@ -0,0 +1,48 @@ +/***************************************************************************** + * importer.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_IMPORTER_H +#define LSMASH_IMPORTER_H + +/*************************************************************************** + importer +***************************************************************************/ + +#ifndef LSMASH_IMPORTER_INTERNAL + +typedef void mp4sys_importer_t; + +/* importing functions */ +mp4sys_importer_t *mp4sys_importer_open( const char *identifier, const char *format ); +void mp4sys_importer_close( mp4sys_importer_t *importer ); +int mp4sys_importer_get_access_unit( mp4sys_importer_t *importer, uint32_t track_number, lsmash_sample_t *buffered_sample ); +uint32_t mp4sys_importer_get_last_delta( mp4sys_importer_t *importer, uint32_t track_number ); +uint32_t mp4sys_importer_get_track_count( mp4sys_importer_t *importer ); +lsmash_summary_t *mp4sys_duplicate_summary( mp4sys_importer_t *importer, uint32_t track_number ); + +int mp4sys_amr_create_damr( lsmash_audio_summary_t *summary ); +int mp4sys_create_dac3_from_syncframe( lsmash_audio_summary_t *summary, uint8_t *data, uint32_t data_length ); + +#endif /* #ifndef LSMASH_IMPORTER_INTERNAL */ + +#endif /* #ifndef LSMASH_IMPORTER_H */ diff --git a/output/mp4/internal.h b/output/mp4/internal.h new file mode 100644 index 0000000..6eed005 --- /dev/null +++ b/output/mp4/internal.h @@ -0,0 +1,37 @@ +/***************************************************************************** + * internal.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef INTERNAL_H +#define INTERNAL_H + +#include "common/osdep.h" /* must be placed before stdio.h */ +#include +#include + +#ifndef lsmash_fseek +#define lsmash_fseek fseek +#define lsmash_ftell ftell +#endif + +#include "lsmash.h" + +#endif diff --git a/output/mp4/isom.c b/output/mp4/isom.c new file mode 100644 index 0000000..da4cf1d --- /dev/null +++ b/output/mp4/isom.c @@ -0,0 +1,8513 @@ +/***************************************************************************** + * isom.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * Contributors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "isom.h" +#include "mp4a.h" +#include "mp4sys.h" +#include "write.h" +#ifdef LSMASH_DEMUXER_ENABLED +#include "read.h" +#include "print.h" +#include "timeline.h" +#endif + + +/*---- ----*/ +/* Return 1 if the box is fullbox, Otherwise return 0. */ +int isom_is_fullbox( void *box ) +{ + isom_box_t *current = (isom_box_t *)box; + uint32_t type = current->type; + static const uint32_t fullbox_table[] = { + ISOM_BOX_TYPE_MVHD, + ISOM_BOX_TYPE_IODS, + ISOM_BOX_TYPE_ESDS, + ISOM_BOX_TYPE_TKHD, + QT_BOX_TYPE_CLEF, + QT_BOX_TYPE_PROF, + QT_BOX_TYPE_ENOF, + ISOM_BOX_TYPE_ELST, + ISOM_BOX_TYPE_MDHD, + ISOM_BOX_TYPE_HDLR, + ISOM_BOX_TYPE_VMHD, + ISOM_BOX_TYPE_SMHD, + ISOM_BOX_TYPE_HMHD, + ISOM_BOX_TYPE_NMHD, + QT_BOX_TYPE_GMIN, + ISOM_BOX_TYPE_DREF, + ISOM_BOX_TYPE_URL , + ISOM_BOX_TYPE_STSD, + ISOM_BOX_TYPE_STSL, + QT_BOX_TYPE_CHAN, + ISOM_BOX_TYPE_STTS, + ISOM_BOX_TYPE_CTTS, + ISOM_BOX_TYPE_CSLG, + ISOM_BOX_TYPE_STSS, + QT_BOX_TYPE_STPS, + ISOM_BOX_TYPE_SDTP, + ISOM_BOX_TYPE_STSC, + ISOM_BOX_TYPE_STSZ, + ISOM_BOX_TYPE_STCO, + ISOM_BOX_TYPE_CO64, + ISOM_BOX_TYPE_SGPD, + ISOM_BOX_TYPE_SBGP, + ISOM_BOX_TYPE_CHPL, + ISOM_BOX_TYPE_META, + QT_BOX_TYPE_KEYS, + ISOM_BOX_TYPE_MEAN, + ISOM_BOX_TYPE_NAME, + ISOM_BOX_TYPE_MEHD, + ISOM_BOX_TYPE_TREX, + ISOM_BOX_TYPE_MFHD, + ISOM_BOX_TYPE_TFHD, + ISOM_BOX_TYPE_TRUN, + ISOM_BOX_TYPE_TFRA, + ISOM_BOX_TYPE_MFRO, + 0 + }; + for( int i = 0; fullbox_table[i]; i++ ) + if( type == fullbox_table[i] ) + return 1; + return (type == ISOM_BOX_TYPE_CPRT) && current->parent && (current->parent->type == ISOM_BOX_TYPE_UDTA); +} + +/* Return 1 if the sample type is LPCM audio, Otherwise return 0. */ +int isom_is_lpcm_audio( uint32_t type ) +{ + return type == QT_CODEC_TYPE_23NI_AUDIO + || type == QT_CODEC_TYPE_NONE_AUDIO + || type == QT_CODEC_TYPE_LPCM_AUDIO + || type == QT_CODEC_TYPE_RAW_AUDIO + || type == QT_CODEC_TYPE_SOWT_AUDIO + || type == QT_CODEC_TYPE_TWOS_AUDIO + || type == QT_CODEC_TYPE_FL32_AUDIO + || type == QT_CODEC_TYPE_FL64_AUDIO + || type == QT_CODEC_TYPE_IN24_AUDIO + || type == QT_CODEC_TYPE_IN32_AUDIO + || type == QT_CODEC_TYPE_NOT_SPECIFIED; +} + +char *isom_4cc2str( uint32_t fourcc ) +{ + static char str[5]; + str[0] = (fourcc >> 24) & 0xff; + str[1] = (fourcc >> 16) & 0xff; + str[2] = (fourcc >> 8) & 0xff; + str[3] = fourcc & 0xff; + str[4] = 0; + return str; +} + +static inline void isom_init_basebox_common( isom_box_t *box, isom_box_t *parent, uint32_t type ) +{ + box->root = parent->root; + box->parent = parent; + box->size = 0; + box->type = type; + box->usertype = NULL; +} + +static inline void isom_init_fullbox_common( isom_box_t *box, isom_box_t *parent, uint32_t type ) +{ + box->root = parent->root; + box->parent = parent; + box->size = 0; + box->type = type; + box->usertype = NULL; + box->version = 0; + box->flags = 0; +} + +void isom_init_box_common( void *box, void *parent, uint32_t type ) +{ + assert( parent && ((isom_box_t *)parent)->root ); + if( ((isom_box_t *)parent)->type == ISOM_BOX_TYPE_STSD ) + { + isom_init_basebox_common( (isom_box_t *)box, (isom_box_t *)parent, type ); + return; + } + if( isom_is_fullbox( box ) ) + isom_init_fullbox_common( (isom_box_t *)box, (isom_box_t *)parent, type ); + else + isom_init_basebox_common( (isom_box_t *)box, (isom_box_t *)parent, type ); +} + +isom_trak_entry_t *isom_get_trak( lsmash_root_t *root, uint32_t track_ID ) +{ + if( !track_ID || !root || !root->moov || !root->moov->trak_list ) + return NULL; + for( lsmash_entry_t *entry = root->moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak || !trak->tkhd ) + return NULL; + if( trak->tkhd->track_ID == track_ID ) + return trak; + } + return NULL; +} + +static isom_trex_entry_t *isom_get_trex( isom_mvex_t *mvex, uint32_t track_ID ) +{ + if( !track_ID || !mvex || !mvex->trex_list ) + return NULL; + for( lsmash_entry_t *entry = mvex->trex_list->head; entry; entry = entry->next ) + { + isom_trex_entry_t *trex = (isom_trex_entry_t *)entry->data; + if( !trex ) + return NULL; + if( trex->track_ID == track_ID ) + return trex; + } + return NULL; +} + +static isom_traf_entry_t *isom_get_traf( isom_moof_entry_t *moof, uint32_t track_ID ) +{ + if( !track_ID || !moof || !moof->traf_list ) + return NULL; + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + { + isom_traf_entry_t *traf = (isom_traf_entry_t *)entry->data; + if( !traf || !traf->tfhd ) + return NULL; + if( traf->tfhd->track_ID == track_ID ) + return traf; + } + return NULL; +} + +static isom_tfra_entry_t *isom_get_tfra( isom_mfra_t *mfra, uint32_t track_ID ) +{ + if( !track_ID || !mfra || !mfra->tfra_list ) + return NULL; + for( lsmash_entry_t *entry = mfra->tfra_list->head; entry; entry = entry->next ) + { + isom_tfra_entry_t *tfra = (isom_tfra_entry_t *)entry->data; + if( !tfra ) + return NULL; + if( tfra->track_ID == track_ID ) + return tfra; + } + return NULL; +} + +static int isom_add_elst_entry( isom_elst_t *elst, uint64_t segment_duration, int64_t media_time, int32_t media_rate ) +{ + isom_elst_entry_t *data = malloc( sizeof(isom_elst_entry_t) ); + if( !data ) + return -1; + data->segment_duration = segment_duration; + data->media_time = media_time; + data->media_rate = media_rate; + if( lsmash_add_entry( elst->list, data ) ) + { + free( data ); + return -1; + } + if( data->segment_duration > UINT32_MAX || data->media_time > INT32_MAX || data->media_time < INT32_MIN ) + elst->version = 1; + return 0; +} + +isom_tref_type_t *isom_add_track_reference_type( isom_tref_t *tref, isom_track_reference_type type, uint32_t ref_count, uint32_t *track_ID ) +{ + if( !tref || !tref->ref_list ) + return NULL; + isom_tref_type_t *ref = malloc( sizeof(isom_tref_type_t) ); + if( !ref ) + return NULL; + isom_init_box_common( ref, tref, type ); + ref->ref_count = ref_count; + ref->track_ID = track_ID; + if( lsmash_add_entry( tref->ref_list, ref ) ) + { + free( ref ); + return NULL; + } + return ref; +} + +static int isom_add_dref_entry( isom_dref_t *dref, uint32_t flags, char *name, char *location ) +{ + if( !dref || !dref->list ) + return -1; + isom_dref_entry_t *data = lsmash_malloc_zero( sizeof(isom_dref_entry_t) ); + if( !data ) + return -1; + isom_init_box_common( data, dref, name ? ISOM_BOX_TYPE_URN : ISOM_BOX_TYPE_URL ); + data->flags = flags; + if( location ) + { + data->location_length = strlen( location ) + 1; + data->location = lsmash_memdup( location, data->location_length ); + if( !data->location ) + { + free( data ); + return -1; + } + } + if( name ) + { + data->name_length = strlen( name ) + 1; + data->name = lsmash_memdup( name, data->name_length ); + if( !data->name ) + { + if( data->location ) + free( data->location ); + free( data ); + return -1; + } + } + if( lsmash_add_entry( dref->list, data ) ) + { + if( data->location ) + free( data->location ); + if( data->name ) + free( data->name ); + free( data ); + return -1; + } + return 0; +} + +isom_avcC_ps_entry_t *isom_create_ps_entry( uint8_t *ps, uint32_t ps_size ) +{ + isom_avcC_ps_entry_t *entry = malloc( sizeof(isom_avcC_ps_entry_t) ); + if( !entry ) + return NULL; + entry->parameterSetNALUnit = lsmash_memdup( ps, ps_size ); + if( !entry->parameterSetNALUnit ) + { + free( entry ); + return NULL; + } + entry->parameterSetLength = ps_size; + return entry; +} + +void isom_remove_avcC_ps( isom_avcC_ps_entry_t *ps ) +{ + if( !ps ) + return; + if( ps->parameterSetNALUnit ) + free( ps->parameterSetNALUnit ); + free( ps ); +} + +int lsmash_add_sps_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *sps, uint32_t sps_size ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return -1; + isom_visual_entry_t *data = (isom_visual_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, entry_number ); + if( !data || !data->avcC ) + return -1; + isom_avcC_t *avcC = (isom_avcC_t *)data->avcC; + isom_avcC_ps_entry_t *ps = isom_create_ps_entry( sps, sps_size ); + if( !ps ) + return -1; + if( lsmash_add_entry( avcC->sequenceParameterSets, ps ) ) + { + isom_remove_avcC_ps( ps ); + return -1; + } + avcC->numOfSequenceParameterSets = avcC->sequenceParameterSets->entry_count; + return 0; +} + +int lsmash_add_pps_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *pps, uint32_t pps_size ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return -1; + isom_visual_entry_t *data = (isom_visual_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, entry_number ); + if( !data || !data->avcC ) + return -1; + isom_avcC_t *avcC = (isom_avcC_t *)data->avcC; + isom_avcC_ps_entry_t *ps = isom_create_ps_entry( pps, pps_size ); + if( !ps ) + return -1; + if( lsmash_add_entry( avcC->pictureParameterSets, ps ) ) + { + isom_remove_avcC_ps( ps ); + return -1; + } + avcC->numOfPictureParameterSets = avcC->pictureParameterSets->entry_count; + return 0; +} + +int lsmash_add_spsext_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *spsext, uint32_t spsext_size ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return -1; + isom_visual_entry_t *data = (isom_visual_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, entry_number ); + if( !data || !data->avcC ) + return -1; + isom_avcC_t *avcC = (isom_avcC_t *)data->avcC; + isom_avcC_ps_entry_t *ps = isom_create_ps_entry( spsext, spsext_size ); + if( !ps ) + return -1; + if( lsmash_add_entry( avcC->sequenceParameterSetExt, ps ) ) + { + isom_remove_avcC_ps( ps ); + return -1; + } + avcC->numOfSequenceParameterSetExt = avcC->sequenceParameterSetExt->entry_count; + return 0; +} + +int isom_add_avcC( isom_visual_entry_t *visual ) +{ + if( !visual ) + return -1; + isom_create_box( avcC, visual, ISOM_BOX_TYPE_AVCC ); + avcC->sequenceParameterSets = lsmash_create_entry_list(); + if( !avcC->sequenceParameterSets ) + { + free( avcC ); + return -1; + } + avcC->pictureParameterSets = lsmash_create_entry_list(); + if( !avcC->pictureParameterSets ) + { + isom_remove_avcC( avcC ); + return -1; + } + avcC->sequenceParameterSetExt = lsmash_create_entry_list(); + if( !avcC->sequenceParameterSetExt ) + { + isom_remove_avcC( avcC ); + return -1; + } + visual->avcC = avcC; + return 0; +} + +int isom_add_clap( isom_visual_entry_t *visual ) +{ + if( !visual || visual->clap ) + return -1; + isom_create_box( clap, visual, ISOM_BOX_TYPE_CLAP ); + clap->cleanApertureWidthN = 1; + clap->cleanApertureWidthD = 1; + clap->cleanApertureHeightN = 1; + clap->cleanApertureHeightD = 1; + clap->horizOffN = 0; + clap->horizOffD = 1; + clap->vertOffN = 0; + clap->vertOffD = 1; + visual->clap = clap; + return 0; +} + +int isom_add_pasp( isom_visual_entry_t *visual ) +{ + if( !visual || visual->pasp ) + return -1; + isom_create_box( pasp, visual, ISOM_BOX_TYPE_PASP ); + pasp->hSpacing = 1; + pasp->vSpacing = 1; + visual->pasp = pasp; + return 0; +} + +int isom_add_colr( isom_visual_entry_t *visual ) +{ + if( !visual || visual->colr ) + return -1; + isom_create_box( colr, visual, QT_BOX_TYPE_COLR ); + isom_color_parameter_t *param = (isom_color_parameter_t *)(&isom_color_parameter_tbl[0]); + colr->color_parameter_type = QT_COLOR_PARAMETER_TYPE_NCLC; + colr->primaries_index = param->primaries; + colr->transfer_function_index = param->transfer; + colr->matrix_index = param->matrix; + visual->colr = colr; + return 0; +} + +int isom_add_stsl( isom_visual_entry_t *visual ) +{ + if( !visual || visual->stsl ) + return -1; + isom_create_box( stsl, visual, ISOM_BOX_TYPE_STSL ); + stsl->scale_method = ISOM_SCALING_METHOD_HIDDEN; + visual->stsl = stsl; + return 0; +} + +static void isom_remove_esds( isom_esds_t *esds ); +static void isom_remove_visual_extensions( isom_visual_entry_t *visual ); + +static int isom_add_visual_extensions( isom_visual_entry_t *visual, lsmash_video_summary_t *summary ) +{ + /* Check if set up Track Aperture Modes. */ + isom_trak_entry_t *trak = (isom_trak_entry_t *)visual->parent->parent->parent->parent->parent; + int qt_compatible = trak->root->qt_compatible; + isom_tapt_t *tapt = trak->tapt; + int set_aperture_modes = qt_compatible /* Track Aperture Modes is only available under QuickTime file format. */ + && !summary->scaling_method /* Sample scaling method might conflict with this feature. */ + && tapt && tapt->clef && tapt->prof && tapt->enof /* Check if required boxes exist. */ + && !((isom_stsd_t *)visual->parent)->list->entry_count; /* Multiple sample description might conflict with this, so in that case, disable this feature. + * Note: this sample description isn't added yet here. */ + if( !set_aperture_modes ) + isom_remove_tapt( trak->tapt ); + /* Set up Clean Aperture. */ + if( set_aperture_modes || summary->crop_top || summary->crop_left || summary->crop_bottom || summary->crop_right ) + { + if( isom_add_clap( visual ) ) + { + isom_remove_visual_extensions( visual ); + return -1; + } + isom_clap_t *clap = visual->clap; + clap->cleanApertureWidthN = summary->width - (summary->crop_left + summary->crop_right); + clap->cleanApertureHeightN = summary->height - (summary->crop_top + summary->crop_bottom); + clap->horizOffN = (int64_t)summary->crop_left - summary->crop_right; + clap->vertOffN = (int64_t)summary->crop_top - summary->crop_bottom; + if( !(clap->horizOffN & 0x1) ) + { + clap->horizOffN /= 2; + clap->horizOffD = 1; + } + else + clap->horizOffD = 2; + if( !(clap->vertOffN & 0x1) ) + { + clap->vertOffN /= 2; + clap->vertOffD = 1; + } + else + clap->vertOffD = 2; + } + /* Set up Pixel Aspect Ratio. */ + if( set_aperture_modes || (summary->par_h && summary->par_v) ) + { + if( isom_add_pasp( visual ) ) + { + isom_remove_visual_extensions( visual ); + return -1; + } + isom_pasp_t *pasp = visual->pasp; + pasp->hSpacing = summary->par_h; + pasp->vSpacing = summary->par_v; + } + /* Set up Color Parameter. */ + if( qt_compatible && (summary->primaries || summary->transfer || summary->matrix) ) + { + if( isom_add_colr( visual ) ) + { + isom_remove_visual_extensions( visual ); + return -1; + } + isom_colr_t *colr = visual->colr; + uint16_t primaries = summary->primaries; + uint16_t transfer = summary->transfer; + uint16_t matrix = summary->matrix; + /* Set 'nclc' to parameter type, we don't support 'prof'. */ + colr->color_parameter_type = QT_COLOR_PARAMETER_TYPE_NCLC; + /* primaries */ + if( primaries >= QT_COLOR_PARAMETER_END ) + return -1; + else if( primaries > UINT16_MAX ) + colr->primaries_index = isom_color_parameter_tbl[primaries - UINT16_MAX_PLUS_ONE].primaries; + else + colr->primaries_index = (primaries == 1 || primaries == 5 || primaries == 6) ? primaries : 2; + /* transfer */ + if( transfer >= QT_COLOR_PARAMETER_END ) + return -1; + else if( transfer > UINT16_MAX ) + colr->transfer_function_index = isom_color_parameter_tbl[transfer - UINT16_MAX_PLUS_ONE].transfer; + else + colr->transfer_function_index = (transfer == 1 || transfer == 7) ? transfer : 2; + /* matrix */ + if( matrix >= QT_COLOR_PARAMETER_END ) + return -1; + else if( matrix > UINT16_MAX ) + colr->matrix_index = isom_color_parameter_tbl[matrix - UINT16_MAX_PLUS_ONE].matrix; + else + colr->matrix_index = (matrix == 1 || matrix == 6 || matrix == 7) ? matrix : 2; + } + /* Set up Sample Scaling. */ + if( !qt_compatible && summary->scaling_method ) + { + if( isom_add_stsl( visual ) ) + { + isom_remove_visual_extensions( visual ); + return -1; + } + isom_stsl_t *stsl = visual->stsl; + stsl->constraint_flag = 1; + stsl->scale_method = summary->scaling_method; + } + /* Set up Decoder Specific Information. */ + static const struct + { + uint32_t codec_type; + uint32_t minimum_length; + uint32_t fourcc; + int (*add_func)( isom_visual_entry_t * ); + } dsi_table[] = + { + { ISOM_CODEC_TYPE_AVC1_VIDEO, 15, ISOM_BOX_TYPE_AVCC, isom_add_avcC }, + { ISOM_CODEC_TYPE_AVC2_VIDEO, 15, ISOM_BOX_TYPE_AVCC, isom_add_avcC }, + { ISOM_CODEC_TYPE_AVCP_VIDEO, 15, ISOM_BOX_TYPE_AVCC, isom_add_avcC }, + { ISOM_CODEC_TYPE_VC_1_VIDEO, 11, ISOM_BOX_TYPE_DVC1, NULL }, + { 0 } + }; + for( int i = 0; dsi_table[i].codec_type; i++ ) + if( visual->type == dsi_table[i].codec_type ) + { + if( summary->exdata_length >= dsi_table[i].minimum_length ) + { + /* Chech if Decoder Specific Information is constructed as exdata. */ + uint8_t *exdata = (uint8_t *)summary->exdata; + uint32_t length = (exdata[0] << 24) | (exdata[1] << 16) | (exdata[2] << 8) | exdata[3]; + if( length == summary->exdata_length + && LSMASH_4CC( exdata[4], exdata[5], exdata[6], exdata[7] ) == dsi_table[i].fourcc ) + { + visual->exdata = lsmash_memdup( summary->exdata, summary->exdata_length ); + if( !visual->exdata ) + return -1; + visual->exdata_length = summary->exdata_length; + break; + } + } + if( dsi_table[i].add_func && dsi_table[i].add_func( visual ) ) + return -1; + break; + } + /* Set up Track Apeture Modes. */ + if( set_aperture_modes ) + { + uint32_t width = visual->width << 16; + uint32_t height = visual->height << 16; + double clap_width = ((double)visual->clap->cleanApertureWidthN / visual->clap->cleanApertureWidthD) * (1<<16); + double clap_height = ((double)visual->clap->cleanApertureHeightN / visual->clap->cleanApertureHeightD) * (1<<16); + double par = (double)visual->pasp->hSpacing / visual->pasp->vSpacing; + if( par >= 1.0 ) + { + tapt->clef->width = clap_width * par; + tapt->clef->height = clap_height; + tapt->prof->width = width * par; + tapt->prof->height = height; + } + else + { + tapt->clef->width = clap_width; + tapt->clef->height = clap_height / par; + tapt->prof->width = width; + tapt->prof->height = height / par; + } + tapt->enof->width = width; + tapt->enof->height = height; + } + return 0; +} + +static int isom_add_visual_entry( isom_stsd_t *stsd, uint32_t sample_type, lsmash_video_summary_t *summary ) +{ + if( !stsd || !stsd->list || !summary ) + return -1; + lsmash_entry_list_t *list = stsd->list; + isom_visual_entry_t *visual = lsmash_malloc_zero( sizeof(isom_visual_entry_t) ); + if( !visual ) + return -1; + isom_init_box_common( visual, stsd, sample_type ); + visual->data_reference_index = 1; + visual->width = (uint16_t)summary->width; + visual->height = (uint16_t)summary->height; + visual->horizresolution = visual->vertresolution = 0x00480000; + visual->frame_count = 1; + switch( sample_type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_AVC2_VIDEO : + strcpy( visual->compressorname, "\012AVC Coding" ); + break; + case ISOM_CODEC_TYPE_AVCP_VIDEO : + strcpy( visual->compressorname, "\016AVC Parameters" ); + break; + default : + break; + } + visual->depth = 0x0018; + visual->color_table_ID = -1; + if( isom_add_visual_extensions( visual, summary ) + || lsmash_add_entry( list, visual ) ) + { + isom_remove_visual_extensions( visual ); + free( visual ); + return -1; + } + return 0; +} + +#if 0 +static int isom_add_mp4s_entry( isom_stsd_t *stsd ) +{ + if( !stsd || !stsd->list ) + return -1; + isom_mp4s_entry_t *mp4s = lsmash_malloc_zero( sizeof(isom_mp4s_entry_t) ); + if( !mp4s ) + return -1; + isom_init_box_common( mp4s, stsd, ISOM_CODEC_TYPE_MP4S_SYSTEM ); + mp4s->data_reference_index = 1; + if( lsmash_add_entry( stsd->list, mp4s ) ) + { + free( mp4s ); + return -1; + } + return 0; +} +#endif + +int isom_add_wave( isom_audio_entry_t *audio ) +{ + if( !audio || audio->wave ) + return -1; + isom_create_box( wave, audio, QT_BOX_TYPE_WAVE ); + audio->wave = wave; + return 0; +} + +int isom_add_frma( isom_wave_t *wave ) +{ + if( !wave || wave->frma ) + return -1; + isom_create_box( frma, wave, QT_BOX_TYPE_FRMA ); + wave->frma = frma; + return 0; +} + +int isom_add_enda( isom_wave_t *wave ) +{ + if( !wave || wave->enda ) + return -1; + isom_create_box( enda, wave, QT_BOX_TYPE_ENDA ); + wave->enda = enda; + return 0; +} + +int isom_add_mp4a( isom_wave_t *wave ) +{ + if( !wave || wave->mp4a ) + return -1; + isom_create_box( mp4a, wave, QT_BOX_TYPE_MP4A ); + wave->mp4a = mp4a; + return 0; +} + +int isom_add_terminator( isom_wave_t *wave ) +{ + if( !wave || wave->terminator ) + return -1; + isom_create_box( terminator, wave, QT_BOX_TYPE_TERMINATOR ); + wave->terminator = terminator; + return 0; +} + +int isom_add_chan( isom_audio_entry_t *audio ) +{ + if( !audio || audio->chan ) + return -1; + isom_create_box( chan, audio, QT_BOX_TYPE_CHAN ); + chan->channelLayoutTag = QT_CHANNEL_LAYOUT_UNKNOWN; + audio->chan = chan; + return 0; +} + +static int isom_set_qtff_mp4a_description( isom_audio_entry_t *audio ) +{ + lsmash_audio_summary_t *summary = &audio->summary; + if( isom_add_wave( audio ) + || isom_add_frma( audio->wave ) + || isom_add_mp4a( audio->wave ) + || isom_add_terminator( audio->wave ) ) + return -1; + audio->data_reference_index = 1; + audio->version = (summary->channels > 2 || summary->frequency > UINT16_MAX) ? 2 : 1; + audio->channelcount = audio->version == 2 ? 3 : LSMASH_MIN( summary->channels, 2 ); + audio->samplesize = 16; + audio->compression_ID = QT_COMPRESSION_ID_VARIABLE_COMPRESSION; + audio->packet_size = 0; + if( audio->version == 1 ) + { + audio->samplerate = summary->frequency << 16; + audio->samplesPerPacket = summary->samples_in_frame; + audio->bytesPerPacket = 1; /* Apparently, this field is set to 1. */ + audio->bytesPerFrame = audio->bytesPerPacket * summary->channels; + audio->bytesPerSample = 1 + (summary->bit_depth != 8); + } + else /* audio->version == 2 */ + { + audio->samplerate = 0x00010000; + audio->sizeOfStructOnly = 72; + audio->audioSampleRate = (union {double d; uint64_t i;}){summary->frequency}.i; + audio->numAudioChannels = summary->channels; + audio->always7F000000 = 0x7F000000; + audio->constBitsPerChannel = 0; /* compressed audio */ + audio->formatSpecificFlags = 0; + audio->constBytesPerAudioPacket = 0; /* variable */ + audio->constLPCMFramesPerAudioPacket = summary->samples_in_frame; + } + audio->wave->frma->data_format = audio->type; + /* create ES Descriptor */ + isom_esds_t *esds = lsmash_malloc_zero( sizeof(isom_esds_t) ); + if( !esds ) + return -1; + isom_init_box_common( esds, audio->wave, ISOM_BOX_TYPE_ESDS ); + mp4sys_ES_Descriptor_params_t esd_param; + memset( &esd_param, 0, sizeof(mp4sys_ES_Descriptor_params_t) ); + esd_param.objectTypeIndication = summary->object_type_indication; + esd_param.streamType = summary->stream_type; + esd_param.dsi_payload = summary->exdata; + esd_param.dsi_payload_length = summary->exdata_length; + esds->ES = mp4sys_setup_ES_Descriptor( &esd_param ); + if( !esds->ES ) + return -1; + audio->wave->esds = esds; + return 0; +} + +static int isom_set_isom_mp4a_description( isom_audio_entry_t *audio ) +{ + lsmash_audio_summary_t *summary = &audio->summary; + if( summary->stream_type != MP4SYS_STREAM_TYPE_AudioStream ) + return -1; + switch( summary->object_type_indication ) + { + case MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_LC_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_SSR_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3: /* Legacy Interface */ + case MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3: /* Legacy Interface */ + break; + default: + return -1; + } + isom_create_box( esds, audio, ISOM_BOX_TYPE_ESDS ); + mp4sys_ES_Descriptor_params_t esd_param; + esd_param.ES_ID = 0; /* This is esds internal, so 0 is allowed. */ + esd_param.objectTypeIndication = summary->object_type_indication; + esd_param.streamType = summary->stream_type; + esd_param.bufferSizeDB = 0; /* NOTE: ISO/IEC 14496-3 does not mention this, so we use 0. */ + esd_param.maxBitrate = 0; /* This will be updated later if needed. or... I think this can be arbitrary value. */ + esd_param.avgBitrate = 0; /* FIXME: 0 if VBR. */ + esd_param.dsi_payload = summary->exdata; + esd_param.dsi_payload_length = summary->exdata_length; + esds->ES = mp4sys_setup_ES_Descriptor( &esd_param ); + if( !esds->ES ) + return -1; + audio->data_reference_index = 1; + /* WARNING: This field cannot retain frequency above 65535Hz. + This is not "FIXME", I just honestly implemented what the spec says. + BTW, who ever expects sampling frequency takes fixed-point decimal??? */ + audio->samplerate = summary->frequency <= UINT16_MAX ? summary->frequency << 16 : 0; + /* In pure mp4 file, these "template" fields shall be default values according to the spec. + But not pure - hybrid with other spec - mp4 file can take other values. + Which is to say, these template values shall be ignored in terms of mp4, except some object_type_indications. + see 14496-14, "Template fields used". */ + audio->channelcount = 2; + audio->samplesize = 16; + audio->esds = esds; + return 0; +} + +static int isom_set_qtff_lpcm_description( isom_audio_entry_t *audio ) +{ + uint32_t sample_type = audio->type; + lsmash_audio_summary_t *summary = &audio->summary; + /* Convert the sample type into 'lpcm' if the description doesn't match the format or version = 2 fields are needed. */ + if( (sample_type == QT_CODEC_TYPE_RAW_AUDIO && (summary->bit_depth != 8 || summary->sample_format)) + || (sample_type == QT_CODEC_TYPE_FL32_AUDIO && (summary->bit_depth != 32 || !summary->sample_format)) + || (sample_type == QT_CODEC_TYPE_FL64_AUDIO && (summary->bit_depth != 64 || !summary->sample_format)) + || (sample_type == QT_CODEC_TYPE_IN24_AUDIO && (summary->bit_depth != 24 || summary->sample_format)) + || (sample_type == QT_CODEC_TYPE_IN32_AUDIO && (summary->bit_depth != 32 || summary->sample_format)) + || (sample_type == QT_CODEC_TYPE_23NI_AUDIO && (summary->bit_depth != 32 || summary->sample_format || !summary->endianness)) + || (sample_type == QT_CODEC_TYPE_SOWT_AUDIO && (summary->bit_depth != 16 || summary->sample_format || !summary->endianness)) + || (sample_type == QT_CODEC_TYPE_TWOS_AUDIO && ((summary->bit_depth != 16 && summary->bit_depth != 8) || summary->sample_format || summary->endianness)) + || (sample_type == QT_CODEC_TYPE_NONE_AUDIO && ((summary->bit_depth != 16 && summary->bit_depth != 8) || summary->sample_format || summary->endianness)) + || (sample_type == QT_CODEC_TYPE_NOT_SPECIFIED && ((summary->bit_depth != 16 && summary->bit_depth != 8) || summary->sample_format || summary->endianness)) + || (summary->channels > 2 || summary->frequency > UINT16_MAX || summary->bit_depth % 8) ) + { + audio->type = QT_CODEC_TYPE_LPCM_AUDIO; + audio->version = 2; + } + else if( sample_type == QT_CODEC_TYPE_LPCM_AUDIO ) + audio->version = 2; + else if( summary->bit_depth > 16 + || (sample_type != QT_CODEC_TYPE_RAW_AUDIO && sample_type != QT_CODEC_TYPE_TWOS_AUDIO + && sample_type != QT_CODEC_TYPE_NONE_AUDIO && sample_type != QT_CODEC_TYPE_NOT_SPECIFIED) ) + audio->version = 1; + audio->data_reference_index = 1; + /* Set up constBytesPerAudioPacket field. + * We use constBytesPerAudioPacket as the actual size of audio frame even when version is not 2. */ + audio->constBytesPerAudioPacket = (summary->bit_depth * summary->channels) / 8; + /* Set up other fields in this description by its version. */ + if( audio->version == 2 ) + { + audio->channelcount = 3; + audio->samplesize = 16; + audio->compression_ID = -2; + audio->samplerate = 0x00010000; + audio->sizeOfStructOnly = 72; + audio->audioSampleRate = (union {double d; uint64_t i;}){summary->frequency}.i; + audio->numAudioChannels = summary->channels; + audio->always7F000000 = 0x7F000000; + audio->constBitsPerChannel = summary->bit_depth; + audio->constLPCMFramesPerAudioPacket = 1; + if( summary->sample_format ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_FLOAT; + if( sample_type == QT_CODEC_TYPE_TWOS_AUDIO || !summary->endianness ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_BIG_ENDIAN; + if( !summary->sample_format && summary->signedness ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER; + if( summary->packed ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_PACKED; + if( !summary->packed && summary->alignment ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_ALIGNED_HIGH; + if( !summary->interleaved ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_NON_INTERLEAVED; + } + else if( audio->version == 1 ) + { + audio->channelcount = summary->channels; + audio->samplesize = 16; + /* Audio formats other than 'raw ' and 'twos' are treated as compressed audio. */ + if( sample_type == QT_CODEC_TYPE_RAW_AUDIO || sample_type == QT_CODEC_TYPE_TWOS_AUDIO ) + audio->compression_ID = QT_COMPRESSION_ID_NOT_COMPRESSED; + else + audio->compression_ID = QT_COMPRESSION_ID_FIXED_COMPRESSION; + audio->samplerate = summary->frequency << 16; + audio->samplesPerPacket = 1; + audio->bytesPerPacket = summary->bit_depth / 8; + audio->bytesPerFrame = audio->bytesPerPacket * summary->channels; /* sample_size field in stsz box is NOT used. */ + audio->bytesPerSample = 1 + (summary->bit_depth != 8); + if( sample_type == QT_CODEC_TYPE_FL32_AUDIO || sample_type == QT_CODEC_TYPE_FL64_AUDIO + || sample_type == QT_CODEC_TYPE_IN24_AUDIO || sample_type == QT_CODEC_TYPE_IN32_AUDIO ) + { + if( isom_add_wave( audio ) + || isom_add_frma( audio->wave ) + || isom_add_enda( audio->wave ) + || isom_add_terminator( audio->wave ) ) + return -1; + audio->wave->frma->data_format = sample_type; + audio->wave->enda->littleEndian = summary->endianness; + } + } + else /* audio->version == 0 */ + { + audio->channelcount = summary->channels; + audio->samplesize = summary->bit_depth; + audio->compression_ID = QT_COMPRESSION_ID_NOT_COMPRESSED; + audio->samplerate = summary->frequency << 16; + } + return 0; +} + +static int isom_set_extra_description( isom_audio_entry_t *audio ) +{ + lsmash_audio_summary_t *summary = &audio->summary; + audio->data_reference_index = 1; + audio->samplerate = summary->frequency <= UINT16_MAX ? summary->frequency << 16 : 0; + audio->channelcount = 2; + audio->samplesize = 16; + if( summary->exdata ) + { + audio->exdata = lsmash_memdup( summary->exdata, summary->exdata_length ); + if( !audio->exdata ) + return -1; + audio->exdata_length = summary->exdata_length; + } + else + { + /* No CODEC Specific Info */ + switch( audio->type ) + { + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : + return -1; + default : + break; + } + audio->exdata = NULL; + audio->exdata_length = 0; + } + return 0; +} + +static int isom_add_audio_entry( isom_stsd_t *stsd, uint32_t sample_type, lsmash_audio_summary_t *summary ) +{ + if( !stsd || !stsd->list || !summary ) + return -1; + isom_audio_entry_t *audio = lsmash_malloc_zero( sizeof(isom_audio_entry_t) ); + if( !audio ) + return -1; + isom_init_box_common( audio, stsd, sample_type ); + memcpy( &audio->summary, summary, sizeof(lsmash_audio_summary_t) ); + int ret = 0; + lsmash_root_t *root = stsd->root; + if( sample_type == ISOM_CODEC_TYPE_MP4A_AUDIO ) + { + if( root->ftyp && root->ftyp->major_brand == ISOM_BRAND_TYPE_QT ) + ret = isom_set_qtff_mp4a_description( audio ); + else + ret = isom_set_isom_mp4a_description( audio ); + } + else if( isom_is_lpcm_audio( sample_type ) ) + ret = isom_set_qtff_lpcm_description( audio ); + else + ret = isom_set_extra_description( audio ); + if( ret ) + goto fail; + if( root->qt_compatible ) + { + lsmash_channel_layout_tag layout_tag = summary->layout_tag; + lsmash_channel_bitmap bitmap = summary->bitmap; + if( layout_tag == QT_CHANNEL_LAYOUT_USE_CHANNEL_DESCRIPTIONS /* We don't support the feature of Channel Descriptions. */ + || (layout_tag == QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP && (!bitmap || bitmap > QT_CHANNEL_BIT_FULL)) ) + { + layout_tag = summary->layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN | summary->channels; + bitmap = summary->bitmap = 0; + } + /* Don't create Channel Compositor Box if the channel layout is unknown. */ + if( (layout_tag ^ QT_CHANNEL_LAYOUT_UNKNOWN) >> 16 ) + { + if( isom_add_chan( audio ) ) + goto fail; + audio->chan->channelLayoutTag = layout_tag; + audio->chan->channelBitmap = bitmap; + } + } + if( lsmash_add_entry( stsd->list, audio ) ) + goto fail; + return 0; +fail: + isom_remove_esds( audio->esds ); + isom_remove_wave( audio->wave ); + isom_remove_chan( audio->chan ); + if( audio->exdata ) + free( audio->exdata ); + free( audio ); + return -1; +} + +static int isom_add_text_entry( isom_stsd_t *stsd ) +{ + if( !stsd || !stsd->list ) + return -1; + isom_text_entry_t *text = lsmash_malloc_zero( sizeof(isom_text_entry_t) ); + if( !text ) + return -1; + isom_init_box_common( text, stsd, QT_CODEC_TYPE_TEXT_TEXT ); + text->data_reference_index = 1; + if( lsmash_add_entry( stsd->list, text ) ) + { + free( text ); + return -1; + } + return 0; +} + +int isom_add_ftab( isom_tx3g_entry_t *tx3g ) +{ + if( !tx3g ) + return -1; + isom_ftab_t *ftab = lsmash_malloc_zero( sizeof(isom_ftab_t) ); + if( !ftab ) + return -1; + isom_init_box_common( ftab, tx3g, ISOM_BOX_TYPE_FTAB ); + ftab->list = lsmash_create_entry_list(); + if( !ftab->list ) + { + free( ftab ); + return -1; + } + tx3g->ftab = ftab; + return 0; +} + +static int isom_add_tx3g_entry( isom_stsd_t *stsd ) +{ + if( !stsd || !stsd->list ) + return -1; + isom_tx3g_entry_t *tx3g = lsmash_malloc_zero( sizeof(isom_tx3g_entry_t) ); + if( !tx3g ) + return -1; + isom_init_box_common( tx3g, stsd, ISOM_CODEC_TYPE_TX3G_TEXT ); + tx3g->data_reference_index = 1; + if( isom_add_ftab( tx3g ) || + lsmash_add_entry( stsd->list, tx3g ) ) + { + free( tx3g ); + return -1; + } + return 0; +} + +/* This function returns 0 if failed, sample_entry_number if succeeded. */ +int lsmash_add_sample_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_type, void *summary ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->root || !trak->root->ftyp || !trak->mdia || !trak->mdia->minf + || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return 0; + isom_stsd_t *stsd = trak->mdia->minf->stbl->stsd; + lsmash_entry_list_t *list = stsd->list; + int ret = -1; + switch( sample_type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_VC_1_VIDEO : +#if 0 + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + case ISOM_CODEC_TYPE_SVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC2_VIDEO : + case ISOM_CODEC_TYPE_MP4V_VIDEO : + case ISOM_CODEC_TYPE_DRAC_VIDEO : + case ISOM_CODEC_TYPE_ENCV_VIDEO : + case ISOM_CODEC_TYPE_MJP2_VIDEO : + case ISOM_CODEC_TYPE_S263_VIDEO : +#endif + ret = isom_add_visual_entry( stsd, sample_type, (lsmash_video_summary_t *)summary ); + break; +#if 0 + case ISOM_CODEC_TYPE_MP4S_SYSTEM : + ret = isom_add_mp4s_entry( stsd ); + break; +#endif + case ISOM_CODEC_TYPE_MP4A_AUDIO : + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : + case QT_CODEC_TYPE_23NI_AUDIO : + case QT_CODEC_TYPE_NONE_AUDIO : + case QT_CODEC_TYPE_LPCM_AUDIO : + case QT_CODEC_TYPE_RAW_AUDIO : + case QT_CODEC_TYPE_SOWT_AUDIO : + case QT_CODEC_TYPE_TWOS_AUDIO : + case QT_CODEC_TYPE_FL32_AUDIO : + case QT_CODEC_TYPE_FL64_AUDIO : + case QT_CODEC_TYPE_IN24_AUDIO : + case QT_CODEC_TYPE_IN32_AUDIO : + case QT_CODEC_TYPE_NOT_SPECIFIED : +#if 0 + case ISOM_CODEC_TYPE_DRA1_AUDIO : + case ISOM_CODEC_TYPE_DTSC_AUDIO : + case ISOM_CODEC_TYPE_DTSH_AUDIO : + case ISOM_CODEC_TYPE_DTSL_AUDIO : + case ISOM_CODEC_TYPE_ENCA_AUDIO : + case ISOM_CODEC_TYPE_G719_AUDIO : + case ISOM_CODEC_TYPE_G726_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + case ISOM_CODEC_TYPE_MLPA_AUDIO : + case ISOM_CODEC_TYPE_RAW_AUDIO : + case ISOM_CODEC_TYPE_SAWP_AUDIO : + case ISOM_CODEC_TYPE_SEVC_AUDIO : + case ISOM_CODEC_TYPE_SQCP_AUDIO : + case ISOM_CODEC_TYPE_SSMV_AUDIO : + case ISOM_CODEC_TYPE_TWOS_AUDIO : +#endif + ret = isom_add_audio_entry( stsd, sample_type, (lsmash_audio_summary_t *)summary ); + break; + case ISOM_CODEC_TYPE_TX3G_TEXT : + ret = isom_add_tx3g_entry( stsd ); + break; + case QT_CODEC_TYPE_TEXT_TEXT : + ret = isom_add_text_entry( stsd ); + break; + default : + return 0; + } + return ret ? 0 : list->entry_count; +} + +static int isom_add_stts_entry( isom_stbl_t *stbl, uint32_t sample_delta ) +{ + if( !stbl || !stbl->stts || !stbl->stts->list ) + return -1; + isom_stts_entry_t *data = malloc( sizeof(isom_stts_entry_t) ); + if( !data ) + return -1; + data->sample_count = 1; + data->sample_delta = sample_delta; + if( lsmash_add_entry( stbl->stts->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_ctts_entry( isom_stbl_t *stbl, uint32_t sample_offset ) +{ + if( !stbl || !stbl->ctts || !stbl->ctts->list ) + return -1; + isom_ctts_entry_t *data = malloc( sizeof(isom_ctts_entry_t) ); + if( !data ) + return -1; + data->sample_count = 1; + data->sample_offset = sample_offset; + if( lsmash_add_entry( stbl->ctts->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_stsc_entry( isom_stbl_t *stbl, uint32_t first_chunk, uint32_t samples_per_chunk, uint32_t sample_description_index ) +{ + if( !stbl || !stbl->stsc || !stbl->stsc->list ) + return -1; + isom_stsc_entry_t *data = malloc( sizeof(isom_stsc_entry_t) ); + if( !data ) + return -1; + data->first_chunk = first_chunk; + data->samples_per_chunk = samples_per_chunk; + data->sample_description_index = sample_description_index; + if( lsmash_add_entry( stbl->stsc->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_stsz_entry( isom_stbl_t *stbl, uint32_t entry_size ) +{ + if( !stbl || !stbl->stsz ) + return -1; + isom_stsz_t *stsz = stbl->stsz; + /* retrieve initial sample_size */ + if( !stsz->sample_count ) + stsz->sample_size = entry_size; + /* if it seems constant access_unit size at present, update sample_count only */ + if( !stsz->list && stsz->sample_size == entry_size ) + { + ++ stsz->sample_count; + return 0; + } + /* found sample_size varies, create sample_size list */ + if( !stsz->list ) + { + stsz->list = lsmash_create_entry_list(); + if( !stsz->list ) + return -1; + for( uint32_t i = 0; i < stsz->sample_count; i++ ) + { + isom_stsz_entry_t *data = malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return -1; + data->entry_size = stsz->sample_size; + if( lsmash_add_entry( stsz->list, data ) ) + { + free( data ); + return -1; + } + } + stsz->sample_size = 0; + } + isom_stsz_entry_t *data = malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return -1; + data->entry_size = entry_size; + if( lsmash_add_entry( stsz->list, data ) ) + { + free( data ); + return -1; + } + ++ stsz->sample_count; + return 0; +} + +static int isom_add_stss_entry( isom_stbl_t *stbl, uint32_t sample_number ) +{ + if( !stbl || !stbl->stss || !stbl->stss->list ) + return -1; + isom_stss_entry_t *data = malloc( sizeof(isom_stss_entry_t) ); + if( !data ) + return -1; + data->sample_number = sample_number; + if( lsmash_add_entry( stbl->stss->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_stps_entry( isom_stbl_t *stbl, uint32_t sample_number ) +{ + if( !stbl || !stbl->stps || !stbl->stps->list ) + return -1; + isom_stps_entry_t *data = malloc( sizeof(isom_stps_entry_t) ); + if( !data ) + return -1; + data->sample_number = sample_number; + if( lsmash_add_entry( stbl->stps->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_sdtp_entry( isom_stbl_t *stbl, lsmash_sample_property_t *prop, uint8_t avc_extensions ) +{ + if( !prop ) + return -1; + if( !stbl || !stbl->sdtp || !stbl->sdtp->list ) + return -1; + isom_sdtp_entry_t *data = malloc( sizeof(isom_sdtp_entry_t) ); + if( !data ) + return -1; + /* isom_sdtp_entry_t is smaller than lsmash_sample_property_t. */ + data->is_leading = (avc_extensions ? prop->leading : prop->allow_earlier) & 0x03; + data->sample_depends_on = prop->independent & 0x03; + data->sample_is_depended_on = prop->disposable & 0x03; + data->sample_has_redundancy = prop->redundant & 0x03; + if( lsmash_add_entry( stbl->sdtp->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_add_co64( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stco ) + return -1; + isom_create_list_box( stco, stbl, ISOM_BOX_TYPE_CO64 ); + stco->large_presentation = 1; + stbl->stco = stco; + return 0; +} + +static int isom_add_stco( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stco ) + return -1; + isom_create_list_box( stco, stbl, ISOM_BOX_TYPE_STCO ); + stco->large_presentation = 0; + stbl->stco = stco; + return 0; +} + +static int isom_add_co64_entry( isom_stbl_t *stbl, uint64_t chunk_offset ) +{ + if( !stbl || !stbl->stco || !stbl->stco->list ) + return -1; + isom_co64_entry_t *data = malloc( sizeof(isom_co64_entry_t) ); + if( !data ) + return -1; + data->chunk_offset = chunk_offset; + if( lsmash_add_entry( stbl->stco->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +static int isom_convert_stco_to_co64( isom_stbl_t* stbl ) +{ + /* backup stco */ + isom_stco_t *stco = stbl->stco; + stbl->stco = NULL; + if( isom_add_co64( stbl ) ) + return -1; + /* move chunk_offset to co64 from stco */ + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + { + isom_stco_entry_t *data = (isom_stco_entry_t*)entry->data; + if( isom_add_co64_entry( stbl, data->chunk_offset ) ) + return -1; + } + lsmash_remove_list( stco->list, NULL ); + free( stco ); + return 0; +} + +static int isom_add_stco_entry( isom_stbl_t *stbl, uint64_t chunk_offset ) +{ + if( !stbl || !stbl->stco || !stbl->stco->list ) + return -1; + if( stbl->stco->large_presentation ) + return isom_add_co64_entry( stbl, chunk_offset ); + if( chunk_offset > UINT32_MAX ) + { + if( isom_convert_stco_to_co64( stbl ) ) + return -1; + return isom_add_co64_entry( stbl, chunk_offset ); + } + isom_stco_entry_t *data = malloc( sizeof(isom_stco_entry_t) ); + if( !data ) + return -1; + data->chunk_offset = (uint32_t)chunk_offset; + if( lsmash_add_entry( stbl->stco->list, data ) ) + { + free( data ); + return -1; + } + return 0; +} + +isom_sgpd_entry_t *isom_get_sample_group_description( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + if( !stbl->sgpd_list ) + return NULL; + for( lsmash_entry_t *entry = stbl->sgpd_list->head; entry; entry = entry->next ) + { + isom_sgpd_entry_t *sgpd = (isom_sgpd_entry_t *)entry->data; + if( !sgpd || !sgpd->list ) + return NULL; + if( sgpd->grouping_type == grouping_type ) + return sgpd; + } + return NULL; +} + +isom_sbgp_entry_t *isom_get_sample_to_group( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + if( !stbl->sbgp_list ) + return NULL; + for( lsmash_entry_t *entry = stbl->sbgp_list->head; entry; entry = entry->next ) + { + isom_sbgp_entry_t *sbgp = (isom_sbgp_entry_t *)entry->data; + if( !sbgp || !sbgp->list ) + return NULL; + if( sbgp->grouping_type == grouping_type ) + return sbgp; + } + return NULL; +} + +static isom_rap_entry_t *isom_add_rap_group_entry( isom_sgpd_entry_t *sgpd ) +{ + if( !sgpd ) + return NULL; + isom_rap_entry_t *data = malloc( sizeof(isom_rap_entry_t) ); + if( !data ) + return NULL; + data->description_length = 0; + data->num_leading_samples_known = 0; + data->num_leading_samples = 0; + if( lsmash_add_entry( sgpd->list, data ) ) + { + free( data ); + return NULL; + } + return data; +} + +static isom_roll_entry_t *isom_add_roll_group_entry( isom_sgpd_entry_t *sgpd, int16_t roll_distance ) +{ + if( !sgpd ) + return NULL; + isom_roll_entry_t *data = malloc( sizeof(isom_roll_entry_t) ); + if( !data ) + return NULL; + data->description_length = 0; + data->roll_distance = roll_distance; + if( lsmash_add_entry( sgpd->list, data ) ) + { + free( data ); + return NULL; + } + return data; +} + +static isom_group_assignment_entry_t *isom_add_group_assignment_entry( isom_sbgp_entry_t *sbgp, uint32_t sample_count, uint32_t group_description_index ) +{ + if( !sbgp ) + return NULL; + isom_group_assignment_entry_t *data = malloc( sizeof(isom_group_assignment_entry_t) ); + if( !data ) + return NULL; + data->sample_count = sample_count; + data->group_description_index = group_description_index; + if( lsmash_add_entry( sbgp->list, data ) ) + { + free( data ); + return NULL; + } + return data; +} + +int isom_add_chpl_entry( isom_chpl_t *chpl, isom_chapter_entry_t *chap_data ) +{ + if( !chap_data->chapter_name || !chpl || !chpl->list ) + return -1; + isom_chpl_entry_t *data = malloc( sizeof(isom_chpl_entry_t) ); + if( !data ) + return -1; + data->start_time = chap_data->start_time; + data->chapter_name_length = strlen( chap_data->chapter_name ); + data->chapter_name = (char *)malloc( data->chapter_name_length + 1 ); + if( !data->chapter_name ) + { + free( data ); + return -1; + } + memcpy( data->chapter_name, chap_data->chapter_name, data->chapter_name_length ); + data->chapter_name[data->chapter_name_length] = '\0'; + if( lsmash_add_entry( chpl->list, data ) ) + { + free( data->chapter_name ); + free( data ); + return -1; + } + return 0; +} + +static isom_trex_entry_t *isom_add_trex( isom_mvex_t *mvex ) +{ + if( !mvex ) + return NULL; + if( !mvex->trex_list ) + { + mvex->trex_list = lsmash_create_entry_list(); + if( !mvex->trex_list ) + return NULL; + } + isom_trex_entry_t *trex = lsmash_malloc_zero( sizeof(isom_trex_entry_t) ); + if( !trex ) + return NULL; + isom_init_box_common( trex, mvex, ISOM_BOX_TYPE_TREX ); + if( lsmash_add_entry( mvex->trex_list, trex ) ) + { + free( trex ); + return NULL; + } + return trex; +} + +static isom_trun_entry_t *isom_add_trun( isom_traf_entry_t *traf ) +{ + if( !traf ) + return NULL; + if( !traf->trun_list ) + { + traf->trun_list = lsmash_create_entry_list(); + if( !traf->trun_list ) + return NULL; + } + isom_trun_entry_t *trun = lsmash_malloc_zero( sizeof(isom_trun_entry_t) ); + if( !trun ) + return NULL; + isom_init_box_common( trun, traf, ISOM_BOX_TYPE_TRUN ); + if( lsmash_add_entry( traf->trun_list, trun ) ) + { + free( trun ); + return NULL; + } + return trun; +} + +static isom_traf_entry_t *isom_add_traf( lsmash_root_t *root, isom_moof_entry_t *moof ) +{ + if( !root || !root->moof_list || !moof ) + return NULL; + if( !moof->traf_list ) + { + moof->traf_list = lsmash_create_entry_list(); + if( !moof->traf_list ) + return NULL; + } + isom_traf_entry_t *traf = lsmash_malloc_zero( sizeof(isom_traf_entry_t) ); + if( !traf ) + return NULL; + isom_init_box_common( traf, moof, ISOM_BOX_TYPE_TRAF ); + isom_cache_t *cache = malloc( sizeof(isom_cache_t) ); + if( !cache ) + { + free( traf ); + return NULL; + } + memset( cache, 0, sizeof(isom_cache_t) ); + if( lsmash_add_entry( moof->traf_list, traf ) ) + { + free( cache ); + free( traf ); + return NULL; + } + traf->cache = cache; + return traf; +} + +static isom_moof_entry_t *isom_add_moof( lsmash_root_t *root ) +{ + if( !root ) + return NULL; + if( !root->moof_list ) + { + root->moof_list = lsmash_create_entry_list(); + if( !root->moof_list ) + return NULL; + } + isom_moof_entry_t *moof = lsmash_malloc_zero( sizeof(isom_moof_entry_t) ); + if( !moof ) + return NULL; + isom_init_box_common( moof, root, ISOM_BOX_TYPE_MOOF ); + if( lsmash_add_entry( root->moof_list, moof ) ) + { + free( moof ); + return NULL; + } + return moof; +} + +static isom_tfra_entry_t *isom_add_tfra( isom_mfra_t *mfra ) +{ + if( !mfra ) + return NULL; + if( !mfra->tfra_list ) + { + mfra->tfra_list = lsmash_create_entry_list(); + if( !mfra->tfra_list ) + return NULL; + } + isom_tfra_entry_t *tfra = lsmash_malloc_zero( sizeof(isom_tfra_entry_t) ); + if( !tfra ) + return NULL; + isom_init_box_common( tfra, mfra, ISOM_BOX_TYPE_TFRA ); + if( lsmash_add_entry( mfra->tfra_list, tfra ) ) + { + free( tfra ); + return NULL; + } + return tfra; +} + +static int isom_add_ftyp( lsmash_root_t *root ) +{ + if( root->ftyp ) + return -1; + isom_create_box( ftyp, root, ISOM_BOX_TYPE_FTYP ); + ftyp->size = ISOM_BASEBOX_COMMON_SIZE + 8; + root->ftyp = ftyp; + return 0; +} + +static int isom_add_moov( lsmash_root_t *root ) +{ + if( root->moov ) + return -1; + isom_create_box( moov, root, ISOM_BOX_TYPE_MOOV ); + root->moov = moov; + return 0; +} + +static int isom_add_mvhd( isom_moov_t *moov ) +{ + if( !moov || moov->mvhd ) + return -1; + isom_create_box( mvhd, moov, ISOM_BOX_TYPE_MVHD ); + mvhd->rate = 0x00010000; + mvhd->volume = 0x0100; + mvhd->matrix[0] = 0x00010000; + mvhd->matrix[4] = 0x00010000; + mvhd->matrix[8] = 0x40000000; + mvhd->next_track_ID = 1; + moov->mvhd = mvhd; + return 0; +} + +static int isom_scan_trak_profileLevelIndication( isom_trak_entry_t* trak, mp4a_audioProfileLevelIndication* audio_pli, mp4sys_visualProfileLevelIndication* visual_pli ) +{ + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + isom_stsd_t* stsd = trak->mdia->minf->stbl->stsd; + if( !stsd || !stsd->list || !stsd->list->head ) + return -1; + for( lsmash_entry_t *entry = stsd->list->head; entry; entry = entry->next ) + { + isom_sample_entry_t* sample_entry = (isom_sample_entry_t*)entry->data; + if( !sample_entry ) + return -1; + switch( sample_entry->type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : +#if 0 + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + case ISOM_CODEC_TYPE_SVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC2_VIDEO : +#endif + /* FIXME: Do we have to arbitrate like audio? */ + if( *visual_pli == MP4SYS_VISUAL_PLI_NONE_REQUIRED ) + *visual_pli = MP4SYS_VISUAL_PLI_H264_AVC; + break; + case ISOM_CODEC_TYPE_VC_1_VIDEO : + *visual_pli = MP4SYS_VISUAL_PLI_NOT_SPECIFIED; + break; + case ISOM_CODEC_TYPE_MP4A_AUDIO : + { + isom_audio_entry_t *audio = (isom_audio_entry_t *)sample_entry; +#ifdef LSMASH_DEMUXER_ENABLED + if( !audio->esds || !audio->esds->ES ) + return -1; + if( audio->summary.sample_type != ISOM_CODEC_TYPE_MP4A_AUDIO ) + /* This is needed when copying descriptions. */ + mp4sys_setup_summary_from_DecoderSpecificInfo( &audio->summary, audio->esds->ES ); +#endif + *audio_pli = mp4a_max_audioProfileLevelIndication( *audio_pli, mp4a_get_audioProfileLevelIndication( &audio->summary ) ); + break; + } +#if 0 + case ISOM_CODEC_TYPE_DRAC_VIDEO : + case ISOM_CODEC_TYPE_ENCV_VIDEO : + case ISOM_CODEC_TYPE_MJP2_VIDEO : + case ISOM_CODEC_TYPE_S263_VIDEO : + /* FIXME: Do we have to arbitrate like audio? */ + if( *visual_pli == MP4SYS_VISUAL_PLI_NONE_REQUIRED ) + *visual_pli = MP4SYS_VISUAL_PLI_NOT_SPECIFIED; + break; +#endif + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : +#if 0 + case ISOM_CODEC_TYPE_DRA1_AUDIO : + case ISOM_CODEC_TYPE_DTSC_AUDIO : + case ISOM_CODEC_TYPE_DTSH_AUDIO : + case ISOM_CODEC_TYPE_DTSL_AUDIO : + case ISOM_CODEC_TYPE_ENCA_AUDIO : + case ISOM_CODEC_TYPE_G719_AUDIO : + case ISOM_CODEC_TYPE_G726_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + case ISOM_CODEC_TYPE_MLPA_AUDIO : + case ISOM_CODEC_TYPE_RAW_AUDIO : + case ISOM_CODEC_TYPE_SAWP_AUDIO : + case ISOM_CODEC_TYPE_SEVC_AUDIO : + case ISOM_CODEC_TYPE_SQCP_AUDIO : + case ISOM_CODEC_TYPE_SSMV_AUDIO : + case ISOM_CODEC_TYPE_TWOS_AUDIO : +#endif + /* NOTE: These audio codecs other than mp4a does not have appropriate pli. */ + *audio_pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + break; +#if 0 + case ISOM_CODEC_TYPE_FDP_HINT : + case ISOM_CODEC_TYPE_M2TS_HINT : + case ISOM_CODEC_TYPE_PM2T_HINT : + case ISOM_CODEC_TYPE_PRTP_HINT : + case ISOM_CODEC_TYPE_RM2T_HINT : + case ISOM_CODEC_TYPE_RRTP_HINT : + case ISOM_CODEC_TYPE_RSRP_HINT : + case ISOM_CODEC_TYPE_RTP_HINT : + case ISOM_CODEC_TYPE_SM2T_HINT : + case ISOM_CODEC_TYPE_SRTP_HINT : + /* FIXME: Do we have to set OD_profileLevelIndication? */ + break; + case ISOM_CODEC_TYPE_IXSE_META : + case ISOM_CODEC_TYPE_METT_META : + case ISOM_CODEC_TYPE_METX_META : + case ISOM_CODEC_TYPE_MLIX_META : + case ISOM_CODEC_TYPE_OKSD_META : + case ISOM_CODEC_TYPE_SVCM_META : + case ISOM_CODEC_TYPE_TEXT_META : + case ISOM_CODEC_TYPE_URIM_META : + case ISOM_CODEC_TYPE_XML_META : + /* FIXME: Do we have to set OD_profileLevelIndication? */ + break; +#endif + } + } + return 0; +} + +static int isom_add_iods( isom_moov_t *moov ) +{ + if( !moov || !moov->trak_list || moov->iods ) + return -1; + isom_create_box( iods, moov, ISOM_BOX_TYPE_IODS ); + iods->OD = mp4sys_create_ObjectDescriptor( 1 ); /* NOTE: Use 1 for ObjectDescriptorID of IOD. */ + if( !iods->OD ) + { + free( iods ); + return -1; + } + mp4a_audioProfileLevelIndication audio_pli = MP4A_AUDIO_PLI_NONE_REQUIRED; + mp4sys_visualProfileLevelIndication visual_pli = MP4SYS_VISUAL_PLI_NONE_REQUIRED; + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t* trak = (isom_trak_entry_t*)entry->data; + if( !trak || !trak->tkhd ) + return -1; + if( isom_scan_trak_profileLevelIndication( trak, &audio_pli, &visual_pli ) ) + return -1; + if( mp4sys_add_ES_ID_Inc( iods->OD, trak->tkhd->track_ID ) ) + return -1; + } + if( mp4sys_to_InitialObjectDescriptor( iods->OD, + 0, /* FIXME: I'm not quite sure what the spec says. */ + MP4SYS_OD_PLI_NONE_REQUIRED, MP4SYS_SCENE_PLI_NONE_REQUIRED, + audio_pli, visual_pli, + MP4SYS_GRAPHICS_PLI_NONE_REQUIRED ) ) + { + free( iods ); + return -1; + } + moov->iods = iods; + return 0; +} + +static int isom_add_tkhd( isom_trak_entry_t *trak, uint32_t handler_type ) +{ + if( !trak || !trak->root || !trak->root->moov || !trak->root->moov->mvhd || !trak->root->moov->trak_list ) + return -1; + if( !trak->tkhd ) + { + isom_create_box( tkhd, trak, ISOM_BOX_TYPE_TKHD ); + if( handler_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + tkhd->volume = 0x0100; + tkhd->matrix[0] = 0x00010000; + tkhd->matrix[4] = 0x00010000; + tkhd->matrix[8] = 0x40000000; + tkhd->duration = 0xffffffff; + tkhd->track_ID = trak->root->moov->mvhd->next_track_ID; + ++ trak->root->moov->mvhd->next_track_ID; + trak->tkhd = tkhd; + } + return 0; +} + +static int isom_add_clef( isom_tapt_t *tapt ) +{ + if( tapt->clef ) + return 0; + isom_create_box( clef, tapt, QT_BOX_TYPE_CLEF ); + tapt->clef = clef; + return 0; +} + +static int isom_add_prof( isom_tapt_t *tapt ) +{ + if( tapt->prof ) + return 0; + isom_create_box( prof, tapt, QT_BOX_TYPE_PROF ); + tapt->prof = prof; + return 0; +} + +static int isom_add_enof( isom_tapt_t *tapt ) +{ + if( tapt->enof ) + return 0; + isom_create_box( enof, tapt, QT_BOX_TYPE_ENOF ); + tapt->enof = enof; + return 0; +} + +static int isom_add_tapt( isom_trak_entry_t *trak ) +{ + if( trak->tapt ) + return 0; + isom_create_box( tapt, trak, QT_BOX_TYPE_TAPT ); + trak->tapt = tapt; + return 0; +} + +int isom_add_elst( isom_edts_t *edts ) +{ + if( edts->elst ) + return 0; + isom_create_list_box( elst, edts, ISOM_BOX_TYPE_ELST ); + edts->elst = elst; + return 0; +} + +int isom_add_edts( isom_trak_entry_t *trak ) +{ + if( trak->edts ) + return 0; + isom_create_box( edts, trak, ISOM_BOX_TYPE_EDTS ); + trak->edts = edts; + return 0; +} + +int isom_add_tref( isom_trak_entry_t *trak ) +{ + if( trak->tref ) + return 0; + isom_create_box( tref, trak, ISOM_BOX_TYPE_TREF ); + tref->ref_list = lsmash_create_entry_list(); + if( !tref->ref_list ) + { + free( tref ); + return -1; + } + trak->tref = tref; + return 0; +} + +static int isom_add_mdhd( isom_mdia_t *mdia, uint16_t default_language ) +{ + if( !mdia || mdia->mdhd ) + return -1; + isom_create_box( mdhd, mdia, ISOM_BOX_TYPE_MDHD ); + mdhd->language = default_language; + mdia->mdhd = mdhd; + return 0; +} + +static int isom_add_mdia( isom_trak_entry_t *trak ) +{ + if( !trak || trak->mdia ) + return -1; + isom_create_box( mdia, trak, ISOM_BOX_TYPE_MDIA ); + trak->mdia = mdia; + return 0; +} + +static int isom_add_hdlr( isom_mdia_t *mdia, isom_meta_t *meta, isom_minf_t *minf, uint32_t media_type ) +{ + if( (!mdia && !meta && !minf) || (mdia && meta) || (meta && minf) || (minf && mdia) ) + return -1; /* Either one must be given. */ + if( (mdia && mdia->hdlr) || (meta && meta->hdlr) || (minf && minf->hdlr) ) + return -1; /* Selected one must not have hdlr yet. */ + isom_box_t *parent = mdia ? (isom_box_t *)mdia : meta ? (isom_box_t *)meta : (isom_box_t *)minf; + isom_create_box( hdlr, parent, ISOM_BOX_TYPE_HDLR ); + lsmash_root_t *root = hdlr->root; + uint32_t type = mdia ? (root->qt_compatible ? QT_HANDLER_TYPE_MEDIA : 0) : (meta ? 0 : QT_HANDLER_TYPE_DATA); + uint32_t subtype = media_type; + hdlr->componentType = type; + hdlr->componentSubtype = subtype; + char *type_name = NULL; + char *subtype_name = NULL; + uint8_t type_name_length = 0; + uint8_t subtype_name_length = 0; + if( mdia ) + type_name = "Media "; + else if( meta ) + type_name = "Metadata "; + else /* if( minf ) */ + type_name = "Data "; + type_name_length = strlen( type_name ); + struct + { + uint32_t subtype; + char *subtype_name; + uint8_t subtype_name_length; + } subtype_table[] = + { + { ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK, "Sound ", 6 }, + { ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK, "Video", 6 }, + { ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK, "Hint ", 5 }, + { ISOM_MEDIA_HANDLER_TYPE_TIMED_METADATA_TRACK, "Metadata ", 9 }, + { ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK, "Text ", 5 }, + { ISOM_META_HANDLER_TYPE_ITUNES_METADATA, "iTunes ", 7 }, + { QT_REFERENCE_HANDLER_TYPE_ALIAS, "Alias ", 6 }, + { QT_REFERENCE_HANDLER_TYPE_RESOURCE, "Resource ", 9 }, + { QT_REFERENCE_HANDLER_TYPE_URL, "URL ", 4 }, + { subtype, "Unknown ", 8 } + }; + for( int i = 0; subtype_table[i].subtype; i++ ) + if( subtype == subtype_table[i].subtype ) + { + subtype_name = subtype_table[i].subtype_name; + subtype_name_length = subtype_table[i].subtype_name_length; + break; + } + uint32_t name_length = 15 + subtype_name_length + type_name_length + root->isom_compatible + root->qt_compatible; + uint8_t *name = malloc( name_length ); + if( !name ) + return -1; + if( root->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + root->qt_compatible, "L-SMASH ", 8 ); + memcpy( name + root->qt_compatible + 8, subtype_name, subtype_name_length ); + memcpy( name + root->qt_compatible + 8 + subtype_name_length, type_name, type_name_length ); + memcpy( name + root->qt_compatible + 8 + subtype_name_length + type_name_length, "Handler", 7 ); + if( root->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + if( mdia ) + mdia->hdlr = hdlr; + else if( meta ) + meta->hdlr = hdlr; + else + minf->hdlr = hdlr; + return 0; +} + +static int isom_add_minf( isom_mdia_t *mdia ) +{ + if( !mdia || mdia->minf ) + return -1; + isom_create_box( minf, mdia, ISOM_BOX_TYPE_MINF ); + mdia->minf = minf; + return 0; +} + +static int isom_add_vmhd( isom_minf_t *minf ) +{ + if( !minf || minf->vmhd ) + return -1; + isom_create_box( vmhd, minf, ISOM_BOX_TYPE_VMHD ); + vmhd->flags = 0x000001; + minf->vmhd = vmhd; + return 0; +} + +static int isom_add_smhd( isom_minf_t *minf ) +{ + if( !minf || minf->smhd ) + return -1; + isom_create_box( smhd, minf, ISOM_BOX_TYPE_SMHD ); + minf->smhd = smhd; + return 0; +} + +static int isom_add_hmhd( isom_minf_t *minf ) +{ + if( !minf || minf->hmhd ) + return -1; + isom_create_box( hmhd, minf, ISOM_BOX_TYPE_HMHD ); + minf->hmhd = hmhd; + return 0; +} + +static int isom_add_nmhd( isom_minf_t *minf ) +{ + if( !minf || minf->nmhd ) + return -1; + isom_create_box( nmhd, minf, ISOM_BOX_TYPE_NMHD ); + minf->nmhd = nmhd; + return 0; +} + +static int isom_add_gmin( isom_gmhd_t *gmhd ) +{ + if( !gmhd || gmhd->gmin ) + return -1; + isom_create_box( gmin, gmhd, QT_BOX_TYPE_GMIN ); + gmhd->gmin = gmin; + return 0; +} + +static int isom_add_text( isom_gmhd_t *gmhd ) +{ + if( !gmhd || gmhd->text ) + return -1; + isom_create_box( text, gmhd, QT_BOX_TYPE_TEXT ); + text->matrix[0] = 0x00010000; + text->matrix[4] = 0x00010000; + text->matrix[8] = 0x40000000; + gmhd->text = text; + return 0; +} + +static int isom_add_gmhd( isom_minf_t *minf ) +{ + if( !minf || minf->gmhd ) + return -1; + isom_create_box( gmhd, minf, QT_BOX_TYPE_GMHD ); + minf->gmhd = gmhd; + return 0; +} + +static int isom_add_dinf( isom_minf_t *minf ) +{ + if( !minf || minf->dinf ) + return -1; + isom_create_box( dinf, minf, ISOM_BOX_TYPE_DINF ); + minf->dinf = dinf; + return 0; +} + +static int isom_add_dref( isom_dinf_t *dinf ) +{ + if( !dinf || dinf->dref ) + return -1; + isom_create_list_box( dref, dinf, ISOM_BOX_TYPE_DREF ); + dinf->dref = dref; + if( isom_add_dref_entry( dref, 0x000001, NULL, NULL ) ) + return -1; + return 0; +} + +static int isom_add_stsd( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stsd ) + return -1; + isom_create_list_box( stsd, stbl, ISOM_BOX_TYPE_STSD ); + stbl->stsd = stsd; + return 0; +} + +int isom_add_btrt( isom_visual_entry_t *visual ) +{ + if( !visual || visual->btrt ) + return -1; + isom_create_box( btrt, visual, ISOM_BOX_TYPE_BTRT ); + visual->btrt = btrt; + return 0; +} + +int lsmash_add_btrt( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return -1; + isom_visual_entry_t *data = (isom_visual_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, entry_number ); + return isom_add_btrt( data ); +} + +static int isom_add_stts( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stts ) + return -1; + isom_create_list_box( stts, stbl, ISOM_BOX_TYPE_STTS ); + stbl->stts = stts; + return 0; +} + +static int isom_add_ctts( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->ctts ) + return -1; + isom_create_list_box( ctts, stbl, ISOM_BOX_TYPE_CTTS ); + stbl->ctts = ctts; + return 0; +} + +static int isom_add_cslg( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->cslg ) + return -1; + isom_create_box( cslg, stbl, ISOM_BOX_TYPE_CSLG ); + stbl->cslg = cslg; + return 0; +} + +static int isom_add_stsc( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stsc ) + return -1; + isom_create_list_box( stsc, stbl, ISOM_BOX_TYPE_STSC ); + stbl->stsc = stsc; + return 0; +} + +static int isom_add_stsz( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stsz ) + return -1; + isom_create_box( stsz, stbl, ISOM_BOX_TYPE_STSZ ); /* We don't create a list here. */ + stbl->stsz = stsz; + return 0; +} + +static int isom_add_stss( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stss ) + return -1; + isom_create_list_box( stss, stbl, ISOM_BOX_TYPE_STSS ); + stbl->stss = stss; + return 0; +} + +static int isom_add_stps( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->stps ) + return -1; + isom_create_list_box( stps, stbl, QT_BOX_TYPE_STPS ); + stbl->stps = stps; + return 0; +} + +static int isom_add_sdtp( isom_stbl_t *stbl ) +{ + if( !stbl || stbl->sdtp ) + return -1; + isom_create_list_box( sdtp, stbl, ISOM_BOX_TYPE_SDTP ); + stbl->sdtp = sdtp; + return 0; +} + +static isom_sgpd_entry_t *isom_add_sgpd( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + if( !stbl ) + return NULL; + if( !stbl->sgpd_list ) + { + stbl->sgpd_list = lsmash_create_entry_list(); + if( !stbl->sgpd_list ) + return NULL; + } + isom_sgpd_entry_t *sgpd = lsmash_malloc_zero( sizeof(isom_sgpd_entry_t) ); + if( !sgpd ) + return NULL; + isom_init_box_common( sgpd, stbl, ISOM_BOX_TYPE_SGPD ); + sgpd->list = lsmash_create_entry_list(); + if( !sgpd->list || lsmash_add_entry( stbl->sgpd_list, sgpd ) ) + { + free( sgpd ); + return NULL; + } + sgpd->grouping_type = grouping_type; + sgpd->version = 1; /* We use version 1 because it is recommended in the spec. */ + switch( grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + sgpd->default_length = 1; + break; + case ISOM_GROUP_TYPE_ROLL : + sgpd->default_length = 2; + break; + default : + /* We don't consider other grouping types currently. */ + break; + } + return sgpd; +} + +static isom_sbgp_entry_t *isom_add_sbgp( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + if( !stbl ) + return NULL; + if( !stbl->sbgp_list ) + { + stbl->sbgp_list = lsmash_create_entry_list(); + if( !stbl->sbgp_list ) + return NULL; + } + isom_sbgp_entry_t *sbgp = lsmash_malloc_zero( sizeof(isom_sbgp_entry_t) ); + if( !sbgp ) + return NULL; + isom_init_box_common( sbgp, stbl, ISOM_BOX_TYPE_SBGP ); + sbgp->list = lsmash_create_entry_list(); + if( !sbgp->list || lsmash_add_entry( stbl->sbgp_list, sbgp ) ) + { + free( sbgp ); + return NULL; + } + sbgp->grouping_type = grouping_type; + return sbgp; +} + +static int isom_add_stbl( isom_minf_t *minf ) +{ + if( !minf || minf->stbl ) + return -1; + isom_create_box( stbl, minf, ISOM_BOX_TYPE_STBL ); + minf->stbl = stbl; + return 0; +} + +int isom_add_chpl( isom_moov_t *moov ) +{ + if( !moov || !moov->udta || moov->udta->chpl ) + return -1; + isom_create_list_box( chpl, moov->udta, ISOM_BOX_TYPE_CHPL ); + chpl->version = 1; /* version = 1 is popular. */ + moov->udta->chpl = chpl; + return 0; +} + +static int isom_add_metaitem( isom_ilst_t *ilst, uint32_t type ) +{ + if( !ilst || !ilst->item_list ) + return -1; + isom_create_box( metaitem, ilst, type ); + if( lsmash_add_entry( ilst->item_list, metaitem ) ) + { + free( metaitem ); + return -1; + } + return 0; +} + +static int isom_add_mean( isom_metaitem_t *metaitem ) +{ + if( !metaitem || metaitem->mean ) + return -1; + isom_create_box( mean, metaitem, ISOM_BOX_TYPE_MEAN ); + metaitem->mean = mean; + return 0; +} + +static int isom_add_name( isom_metaitem_t *metaitem ) +{ + if( !metaitem || metaitem->name ) + return -1; + isom_create_box( name, metaitem, ISOM_BOX_TYPE_NAME ); + metaitem->name = name; + return 0; +} + +static int isom_add_data( isom_metaitem_t *metaitem ) +{ + if( !metaitem || metaitem->data ) + return -1; + isom_create_box( data, metaitem, ISOM_BOX_TYPE_DATA ); + metaitem->data = data; + return 0; +} + +static int isom_add_ilst( isom_moov_t *moov ) +{ + if( !moov || !moov->udta || !moov->udta->meta || moov->udta->meta->ilst ) + return -1; + isom_create_box( ilst, moov->udta->meta, ISOM_BOX_TYPE_ILST ); + ilst->item_list = lsmash_create_entry_list(); + if( !ilst->item_list ) + { + free( ilst ); + return -1; + } + moov->udta->meta->ilst = ilst; + return 0; +} + +static int isom_add_meta( isom_box_t *parent ) +{ + if( !parent ) + return -1; + isom_create_box( meta, parent, ISOM_BOX_TYPE_META ); + if( !parent->type ) + { + lsmash_root_t *root = (lsmash_root_t *)root; + if( root->meta ) + return -1; + root->meta = meta; + } + else if( parent->type == ISOM_BOX_TYPE_MOOV ) + { + isom_moov_t *moov = (isom_moov_t *)parent; + if( moov->meta ) + return -1; + moov->meta = meta; + } + else if( parent->type == ISOM_BOX_TYPE_TRAK ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)trak; + if( trak->meta ) + return -1; + trak->meta = meta; + } + else + { + isom_udta_t *udta = (isom_udta_t *)parent; + if( udta->meta ) + return -1; + udta->meta = meta; + } + return 0; +} + +static int isom_add_cprt( isom_udta_t *udta ) +{ + if( !udta ) + return -1; + if( !udta->cprt_list ) + { + udta->cprt_list = lsmash_create_entry_list(); + if( !udta->cprt_list ) + return -1; + } + isom_create_box( cprt, udta, ISOM_BOX_TYPE_CPRT ); + if( lsmash_add_entry( udta->cprt_list, cprt ) ) + { + free( cprt ); + return -1; + } + return 0; +} + +int isom_add_udta( lsmash_root_t *root, uint32_t track_ID ) +{ + /* track_ID == 0 means the direct addition to moov box */ + if( !track_ID ) + { + if( !root || !root->moov ) + return -1; + if( root->moov->udta ) + return 0; + isom_create_box( udta, root->moov, ISOM_BOX_TYPE_UDTA ); + root->moov->udta = udta; + return 0; + } + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak ) + return -1; + if( trak->udta ) + return 0; + isom_create_box( udta, trak, ISOM_BOX_TYPE_UDTA ); + trak->udta = udta; + return 0; +} + +static isom_trak_entry_t *isom_add_trak( lsmash_root_t *root ) +{ + if( !root || !root->moov ) + return NULL; + isom_moov_t *moov = root->moov; + if( !moov->trak_list ) + { + moov->trak_list = lsmash_create_entry_list(); + if( !moov->trak_list ) + return NULL; + } + isom_trak_entry_t *trak = lsmash_malloc_zero( sizeof(isom_trak_entry_t) ); + if( !trak ) + return NULL; + isom_init_box_common( trak, moov, ISOM_BOX_TYPE_TRAK ); + isom_cache_t *cache = lsmash_malloc_zero( sizeof(isom_cache_t) ); + if( !cache ) + { + free( trak ); + return NULL; + } + isom_fragment_t *fragment = NULL; + if( root->fragment ) + { + fragment = lsmash_malloc_zero( sizeof(isom_fragment_t) ); + if( !fragment ) + { + free( cache ); + free( trak ); + return NULL; + } + cache->fragment = fragment; + } + if( lsmash_add_entry( moov->trak_list, trak ) ) + { + if( fragment ) + free( fragment ); + free( cache ); + free( trak ); + return NULL; + } + trak->cache = cache; + return trak; +} + +static int isom_add_mvex( isom_moov_t *moov ) +{ + if( !moov || moov->mvex ) + return -1; + isom_create_box( mvex, moov, ISOM_BOX_TYPE_MVEX ); + moov->mvex = mvex; + return 0; +} + +static int isom_add_mehd( isom_mvex_t *mvex ) +{ + if( !mvex || mvex->mehd ) + return -1; + isom_create_box( mehd, mvex, ISOM_BOX_TYPE_MEHD ); + mvex->mehd = mehd; + return 0; +} + +static int isom_add_tfhd( isom_traf_entry_t *traf ) +{ + if( !traf || traf->tfhd ) + return -1; + isom_create_box( tfhd, traf, ISOM_BOX_TYPE_TFHD ); + traf->tfhd = tfhd; + return 0; +} + +static int isom_add_mfhd( isom_moof_entry_t *moof ) +{ + if( !moof || moof->mfhd ) + return -1; + isom_create_box( mfhd, moof, ISOM_BOX_TYPE_MFHD ); + moof->mfhd = mfhd; + return 0; +} + +static int isom_add_mfra( lsmash_root_t *root ) +{ + if( !root || root->mfra ) + return -1; + isom_create_box( mfra, root, ISOM_BOX_TYPE_MFRA ); + root->mfra = mfra; + return 0; +} + +static int isom_add_mfro( isom_mfra_t *mfra ) +{ + if( !mfra || mfra->mfro ) + return -1; + isom_create_box( mfro, mfra, ISOM_BOX_TYPE_MFRO ); + mfra->mfro = mfro; + return 0; +} + +#define isom_remove_box( box_name, parent_type ) \ + do \ + { \ + parent_type *parent = (parent_type *)box_name->parent; \ + free( box_name ); \ + if( parent ) \ + parent->box_name = NULL; \ + } while( 0 ) + +static void isom_remove_ftyp( isom_ftyp_t *ftyp ) +{ + if( !ftyp ) + return; + if( ftyp->compatible_brands ) + free( ftyp->compatible_brands ); + isom_remove_box( ftyp, lsmash_root_t ); +} + +static void isom_remove_tkhd( isom_tkhd_t *tkhd ) +{ + if( !tkhd ) + return; + isom_remove_box( tkhd, isom_trak_entry_t ); +} + +static void isom_remove_clef( isom_clef_t *clef ) +{ + if( !clef ) + return; + isom_remove_box( clef, isom_tapt_t ); +} + +static void isom_remove_prof( isom_prof_t *prof ) +{ + if( !prof ) + return; + isom_remove_box( prof, isom_tapt_t ); +} + +static void isom_remove_enof( isom_enof_t *enof ) +{ + if( !enof ) + return; + isom_remove_box( enof, isom_tapt_t ); +} + +void isom_remove_tapt( isom_tapt_t *tapt ) +{ + if( !tapt ) + return; + isom_remove_clef( tapt->clef ); + isom_remove_prof( tapt->prof ); + isom_remove_enof( tapt->enof ); + isom_remove_box( tapt, isom_trak_entry_t ); +} + +static void isom_remove_elst( isom_elst_t *elst ) +{ + if( !elst ) + return; + lsmash_remove_list( elst->list, NULL ); + isom_remove_box( elst, isom_edts_t ); +} + +static void isom_remove_edts( isom_edts_t *edts ) +{ + if( !edts ) + return; + isom_remove_elst( edts->elst ); + isom_remove_box( edts, isom_trak_entry_t ); +} + +void isom_remove_track_reference_type( isom_tref_type_t *ref ) +{ + if( !ref ) + return; + if( ref->track_ID ) + free( ref->track_ID ); + free( ref ); +} + +void isom_remove_tref( isom_tref_t *tref ) +{ + if( !tref ) + return; + lsmash_remove_list( tref->ref_list, isom_remove_track_reference_type ); + isom_remove_box( tref, isom_trak_entry_t ); +} + +static void isom_remove_mdhd( isom_mdhd_t *mdhd ) +{ + if( !mdhd ) + return; + isom_remove_box( mdhd, isom_mdia_t ); +} + +static void isom_remove_vmhd( isom_vmhd_t *vmhd ) +{ + if( !vmhd ) + return; + isom_remove_box( vmhd, isom_minf_t ); +} + +static void isom_remove_smhd( isom_smhd_t *smhd ) +{ + if( !smhd ) + return; + isom_remove_box( smhd, isom_minf_t ); +} + +static void isom_remove_hmhd( isom_hmhd_t *hmhd ) +{ + if( !hmhd ) + return; + isom_remove_box( hmhd, isom_minf_t ); +} + +static void isom_remove_nmhd( isom_nmhd_t *nmhd ) +{ + if( !nmhd ) + return; + isom_remove_box( nmhd, isom_minf_t ); +} + +static void isom_remove_gmin( isom_gmin_t *gmin ) +{ + if( !gmin ) + return; + isom_remove_box( gmin, isom_gmhd_t ); +} + +static void isom_remove_text( isom_text_t *text ) +{ + if( !text ) + return; + isom_remove_box( text, isom_gmhd_t ); +} + +static void isom_remove_gmhd( isom_gmhd_t *gmhd ) +{ + if( !gmhd ) + return; + isom_remove_gmin( gmhd->gmin ); + isom_remove_text( gmhd->text ); + isom_remove_box( gmhd, isom_minf_t ); +} + +static void isom_remove_hdlr( isom_hdlr_t *hdlr ) +{ + if( !hdlr ) + return; + if( hdlr->componentName ) + free( hdlr->componentName ); + if( hdlr->parent ) + { + if( hdlr->parent->type == ISOM_BOX_TYPE_MDIA ) + isom_remove_box( hdlr, isom_mdia_t ); + else if( hdlr->parent->type == ISOM_BOX_TYPE_META ) + isom_remove_box( hdlr, isom_meta_t ); + else if( hdlr->parent->type == ISOM_BOX_TYPE_MINF ) + isom_remove_box( hdlr, isom_minf_t ); + else + assert( 0 ); + return; + } + free( hdlr ); +} + +void isom_remove_clap( isom_clap_t *clap ) +{ + if( !clap ) + return; + isom_remove_box( clap, isom_visual_entry_t ); +} + +void isom_remove_pasp( isom_pasp_t *pasp ) +{ + if( !pasp ) + return; + isom_remove_box( pasp, isom_visual_entry_t ); +} + +void isom_remove_colr( isom_colr_t *colr ) +{ + if( !colr ) + return; + isom_remove_box( colr, isom_visual_entry_t ); +} + +void isom_remove_stsl( isom_stsl_t *stsl ) +{ + if( !stsl ) + return; + isom_remove_box( stsl, isom_visual_entry_t ); +} + +static void isom_remove_esds( isom_esds_t *esds ) +{ + if( !esds ) + return; + mp4sys_remove_ES_Descriptor( esds->ES ); + if( esds->parent ) + { + switch( esds->parent->type ) + { + case ISOM_CODEC_TYPE_MP4V_VIDEO : + isom_remove_box( esds, isom_visual_entry_t ); + break; + case ISOM_CODEC_TYPE_MP4A_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + isom_remove_box( esds, isom_audio_entry_t ); + break; + case QT_BOX_TYPE_WAVE : + isom_remove_box( esds, isom_wave_t ); + break; + case ISOM_CODEC_TYPE_MP4S_SYSTEM : + isom_remove_box( esds, isom_mp4s_entry_t ); + break; + default : + assert( 0 ); + } + return; + } + free( esds ); +} + +void isom_remove_avcC( isom_avcC_t *avcC ) +{ + if( !avcC ) + return; + lsmash_remove_list( avcC->sequenceParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( avcC->pictureParameterSets, isom_remove_avcC_ps ); + lsmash_remove_list( avcC->sequenceParameterSetExt, isom_remove_avcC_ps ); + isom_remove_box( avcC, isom_visual_entry_t ); +} + +void isom_remove_btrt( isom_btrt_t *btrt ) +{ + if( !btrt ) + return; + isom_remove_box( btrt, isom_visual_entry_t ); +} + +static void isom_remove_visual_extensions( isom_visual_entry_t *visual ) +{ + if( !visual ) + return; + isom_remove_avcC( visual->avcC ); + isom_remove_btrt( visual->btrt ); + isom_remove_esds( visual->esds ); + isom_remove_colr( visual->colr ); + isom_remove_stsl( visual->stsl ); + isom_remove_clap( visual->clap ); + isom_remove_pasp( visual->pasp ); +} + +static void isom_remove_font_record( isom_font_record_t *font_record ) +{ + if( !font_record ) + return; + if( font_record->font_name ) + free( font_record->font_name ); + free( font_record ); +} + +void isom_remove_ftab( isom_ftab_t *ftab ) +{ + if( !ftab ) + return; + lsmash_remove_list( ftab->list, isom_remove_font_record ); + isom_remove_box( ftab, isom_tx3g_entry_t ); +} + +void isom_remove_frma( isom_frma_t *frma ) +{ + if( !frma ) + return; + isom_remove_box( frma, isom_wave_t ); +} + +void isom_remove_enda( isom_enda_t *enda ) +{ + if( !enda ) + return; + isom_remove_box( enda, isom_wave_t ); +} + +void isom_remove_mp4a( isom_mp4a_t *mp4a ) +{ + if( !mp4a ) + return; + isom_remove_box( mp4a, isom_wave_t ); +} + +void isom_remove_terminator( isom_terminator_t *terminator ) +{ + if( !terminator ) + return; + isom_remove_box( terminator, isom_wave_t ); +} + +void isom_remove_wave( isom_wave_t *wave ) +{ + if( !wave ) + return; + isom_remove_frma( wave->frma ); + isom_remove_enda( wave->enda ); + isom_remove_mp4a( wave->mp4a ); + isom_remove_esds( wave->esds ); + isom_remove_terminator( wave->terminator ); + if( wave->exdata ) + free( wave->exdata ); + isom_remove_box( wave, isom_audio_entry_t ); +} + +void isom_remove_chan( isom_chan_t *chan ) +{ + if( !chan ) + return; + if( chan->channelDescriptions ) + free( chan->channelDescriptions ); + isom_remove_box( chan, isom_audio_entry_t ); +} + +void isom_remove_sample_description( isom_sample_entry_t *sample ) +{ + if( !sample ) + return; + switch( sample->type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + case ISOM_CODEC_TYPE_SVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC2_VIDEO : + case ISOM_CODEC_TYPE_MP4V_VIDEO : + case ISOM_CODEC_TYPE_DRAC_VIDEO : + case ISOM_CODEC_TYPE_ENCV_VIDEO : + case ISOM_CODEC_TYPE_MJP2_VIDEO : + case ISOM_CODEC_TYPE_S263_VIDEO : + case ISOM_CODEC_TYPE_VC_1_VIDEO : + case QT_CODEC_TYPE_CFHD_VIDEO : + case QT_CODEC_TYPE_DV10_VIDEO : + case QT_CODEC_TYPE_DVOO_VIDEO : + case QT_CODEC_TYPE_DVOR_VIDEO : + case QT_CODEC_TYPE_DVTV_VIDEO : + case QT_CODEC_TYPE_DVVT_VIDEO : + case QT_CODEC_TYPE_HD10_VIDEO : + case QT_CODEC_TYPE_M105_VIDEO : + case QT_CODEC_TYPE_PNTG_VIDEO : + case QT_CODEC_TYPE_SVQ1_VIDEO : + case QT_CODEC_TYPE_SVQ3_VIDEO : + case QT_CODEC_TYPE_SHR0_VIDEO : + case QT_CODEC_TYPE_SHR1_VIDEO : + case QT_CODEC_TYPE_SHR2_VIDEO : + case QT_CODEC_TYPE_SHR3_VIDEO : + case QT_CODEC_TYPE_SHR4_VIDEO : + case QT_CODEC_TYPE_WRLE_VIDEO : + case QT_CODEC_TYPE_APCH_VIDEO : + case QT_CODEC_TYPE_APCN_VIDEO : + case QT_CODEC_TYPE_APCS_VIDEO : + case QT_CODEC_TYPE_APCO_VIDEO : + case QT_CODEC_TYPE_AP4H_VIDEO : + case QT_CODEC_TYPE_CIVD_VIDEO : + //case QT_CODEC_TYPE_DRAC_VIDEO : + case QT_CODEC_TYPE_DVH5_VIDEO : + case QT_CODEC_TYPE_DVH6_VIDEO : + case QT_CODEC_TYPE_DVHP_VIDEO : + case QT_CODEC_TYPE_FLIC_VIDEO : + case QT_CODEC_TYPE_GIF_VIDEO : + case QT_CODEC_TYPE_H261_VIDEO : + case QT_CODEC_TYPE_H263_VIDEO : + case QT_CODEC_TYPE_JPEG_VIDEO : + case QT_CODEC_TYPE_MJPA_VIDEO : + case QT_CODEC_TYPE_MJPB_VIDEO : + case QT_CODEC_TYPE_PNG_VIDEO : + case QT_CODEC_TYPE_RLE_VIDEO : + case QT_CODEC_TYPE_RPZA_VIDEO : + case QT_CODEC_TYPE_TGA_VIDEO : + case QT_CODEC_TYPE_TIFF_VIDEO : + { + isom_visual_entry_t *visual = (isom_visual_entry_t *)sample; + isom_remove_visual_extensions( (isom_visual_entry_t *)visual ); + free( visual ); + break; + } + case ISOM_CODEC_TYPE_MP4A_AUDIO : + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : + case QT_CODEC_TYPE_23NI_AUDIO : + case QT_CODEC_TYPE_NONE_AUDIO : + case QT_CODEC_TYPE_LPCM_AUDIO : + case QT_CODEC_TYPE_RAW_AUDIO : + case QT_CODEC_TYPE_SOWT_AUDIO : + case QT_CODEC_TYPE_TWOS_AUDIO : + case QT_CODEC_TYPE_FL32_AUDIO : + case QT_CODEC_TYPE_FL64_AUDIO : + case QT_CODEC_TYPE_IN24_AUDIO : + case QT_CODEC_TYPE_IN32_AUDIO : + case QT_CODEC_TYPE_NOT_SPECIFIED : + case ISOM_CODEC_TYPE_DRA1_AUDIO : + case ISOM_CODEC_TYPE_DTSC_AUDIO : + case ISOM_CODEC_TYPE_DTSH_AUDIO : + case ISOM_CODEC_TYPE_DTSL_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_ENCA_AUDIO : + case ISOM_CODEC_TYPE_G719_AUDIO : + case ISOM_CODEC_TYPE_G726_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + case ISOM_CODEC_TYPE_MLPA_AUDIO : + //case ISOM_CODEC_TYPE_RAW_AUDIO : + case ISOM_CODEC_TYPE_SAWP_AUDIO : + case ISOM_CODEC_TYPE_SEVC_AUDIO : + case ISOM_CODEC_TYPE_SQCP_AUDIO : + case ISOM_CODEC_TYPE_SSMV_AUDIO : + //case ISOM_CODEC_TYPE_TWOS_AUDIO : + { + isom_audio_entry_t *audio = (isom_audio_entry_t *)sample; + isom_remove_esds( audio->esds ); + isom_remove_wave( audio->wave ); + isom_remove_chan( audio->chan ); + if( audio->exdata ) + free( audio->exdata ); + free( audio ); + break; + } + case ISOM_CODEC_TYPE_FDP_HINT : + case ISOM_CODEC_TYPE_M2TS_HINT : + case ISOM_CODEC_TYPE_PM2T_HINT : + case ISOM_CODEC_TYPE_PRTP_HINT : + case ISOM_CODEC_TYPE_RM2T_HINT : + case ISOM_CODEC_TYPE_RRTP_HINT : + case ISOM_CODEC_TYPE_RSRP_HINT : + case ISOM_CODEC_TYPE_RTP_HINT : + case ISOM_CODEC_TYPE_SM2T_HINT : + case ISOM_CODEC_TYPE_SRTP_HINT : + { + isom_hint_entry_t *hint = (isom_hint_entry_t *)sample; + if( hint->data ) + free( hint->data ); + free( hint ); + break; + } + case ISOM_CODEC_TYPE_IXSE_META : + case ISOM_CODEC_TYPE_METT_META : + case ISOM_CODEC_TYPE_METX_META : + case ISOM_CODEC_TYPE_MLIX_META : + case ISOM_CODEC_TYPE_OKSD_META : + case ISOM_CODEC_TYPE_SVCM_META : + //case ISOM_CODEC_TYPE_TEXT_META : + case ISOM_CODEC_TYPE_URIM_META : + case ISOM_CODEC_TYPE_XML_META : + { + isom_metadata_entry_t *metadata = (isom_metadata_entry_t *)sample; + free( metadata ); + break; + } + case ISOM_CODEC_TYPE_TX3G_TEXT : + { + isom_tx3g_entry_t *tx3g = (isom_tx3g_entry_t *)sample; + if( tx3g->ftab ) + isom_remove_ftab( tx3g->ftab ); + free( tx3g ); + break; + } + case QT_CODEC_TYPE_TEXT_TEXT : + { + isom_text_entry_t *text = (isom_text_entry_t *)sample; + if( text->font_name ) + free( text->font_name ); + free( text ); + break; + } + case ISOM_CODEC_TYPE_MP4S_SYSTEM : + { + isom_mp4s_entry_t *mp4s = (isom_mp4s_entry_t *)sample; + isom_remove_esds( mp4s->esds ); + free( mp4s ); + break; + } + default : + break; + } +} + +static void isom_remove_stsd( isom_stsd_t *stsd ) +{ + if( !stsd ) + return; + lsmash_remove_list( stsd->list, isom_remove_sample_description ); + isom_remove_box( stsd, isom_stbl_t ); +} + +static void isom_remove_stts( isom_stts_t *stts ) +{ + if( !stts ) + return; + lsmash_remove_list( stts->list, NULL ); + isom_remove_box( stts, isom_stbl_t ); +} + +static void isom_remove_ctts( isom_ctts_t *ctts ) +{ + if( !ctts ) + return; + lsmash_remove_list( ctts->list, NULL ); + isom_remove_box( ctts, isom_stbl_t ); +} + +static void isom_remove_cslg( isom_cslg_t *cslg ) +{ + if( !cslg ) + return; + isom_remove_box( cslg, isom_stbl_t ); +} + +static void isom_remove_stsc( isom_stsc_t *stsc ) +{ + if( !stsc ) + return; + lsmash_remove_list( stsc->list, NULL ); + isom_remove_box( stsc, isom_stbl_t ); +} + +static void isom_remove_stsz( isom_stsz_t *stsz ) +{ + if( !stsz ) + return; + lsmash_remove_list( stsz->list, NULL ); + isom_remove_box( stsz, isom_stbl_t ); +} + +static void isom_remove_stss( isom_stss_t *stss ) +{ + if( !stss ) + return; + lsmash_remove_list( stss->list, NULL ); + isom_remove_box( stss, isom_stbl_t ); +} + +static void isom_remove_stps( isom_stps_t *stps ) +{ + if( !stps ) + return; + lsmash_remove_list( stps->list, NULL ); + isom_remove_box( stps, isom_stbl_t ); +} + +static void isom_remove_sdtp( isom_sdtp_t *sdtp ) +{ + if( !sdtp ) + return; + lsmash_remove_list( sdtp->list, NULL ); + isom_remove_box( sdtp, isom_stbl_t ); +} + +static void isom_remove_stco( isom_stco_t *stco ) +{ + if( !stco ) + return; + lsmash_remove_list( stco->list, NULL ); + isom_remove_box( stco, isom_stbl_t ); +} + +static void isom_remove_sgpd( isom_sgpd_entry_t *sgpd ) +{ + if( !sgpd ) + return; + lsmash_remove_list( sgpd->list, NULL ); + free( sgpd ); +} + +static void isom_remove_sbgp( isom_sbgp_entry_t *sbgp ) +{ + if( !sbgp ) + return; + lsmash_remove_list( sbgp->list, NULL ); + free( sbgp ); +} + +static void isom_remove_stbl( isom_stbl_t *stbl ) +{ + if( !stbl ) + return; + isom_remove_stsd( stbl->stsd ); + isom_remove_stts( stbl->stts ); + isom_remove_ctts( stbl->ctts ); + isom_remove_cslg( stbl->cslg ); + isom_remove_stsc( stbl->stsc ); + isom_remove_stsz( stbl->stsz ); + isom_remove_stss( stbl->stss ); + isom_remove_stps( stbl->stps ); + isom_remove_sdtp( stbl->sdtp ); + isom_remove_stco( stbl->stco ); + lsmash_remove_list( stbl->sgpd_list, isom_remove_sgpd ); + lsmash_remove_list( stbl->sbgp_list, isom_remove_sbgp ); + isom_remove_box( stbl, isom_minf_t ); +} + +static void isom_remove_dref( isom_dref_t *dref ) +{ + if( !dref ) + return; + if( !dref->list ) + { + free( dref ); + return; + } + for( lsmash_entry_t *entry = dref->list->head; entry; ) + { + isom_dref_entry_t *data = (isom_dref_entry_t *)entry->data; + if( data ) + { + if( data->name ) + free( data->name ); + if( data->location ) + free( data->location ); + free( data ); + } + lsmash_entry_t *next = entry->next; + free( entry ); + entry = next; + } + free( dref->list ); + isom_remove_box( dref, isom_dinf_t ); +} + +static void isom_remove_dinf( isom_dinf_t *dinf ) +{ + if( !dinf ) + return; + isom_remove_dref( dinf->dref ); + isom_remove_box( dinf, isom_minf_t ); +} + +static void isom_remove_minf( isom_minf_t *minf ) +{ + if( !minf ) + return; + isom_remove_vmhd( minf->vmhd ); + isom_remove_smhd( minf->smhd ); + isom_remove_hmhd( minf->hmhd ); + isom_remove_nmhd( minf->nmhd ); + isom_remove_gmhd( minf->gmhd ); + isom_remove_hdlr( minf->hdlr ); + isom_remove_dinf( minf->dinf ); + isom_remove_stbl( minf->stbl ); + isom_remove_box( minf, isom_mdia_t ); +} + +static void isom_remove_mdia( isom_mdia_t *mdia ) +{ + if( !mdia ) + return; + isom_remove_mdhd( mdia->mdhd ); + isom_remove_minf( mdia->minf ); + isom_remove_hdlr( mdia->hdlr ); + isom_remove_box( mdia, isom_trak_entry_t ); +} + +static void isom_remove_chpl( isom_chpl_t *chpl ) +{ + if( !chpl ) + return; + if( !chpl->list ) + { + free( chpl ); + return; + } + for( lsmash_entry_t *entry = chpl->list->head; entry; ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + if( data ) + { + if( data->chapter_name ) + free( data->chapter_name ); + free( data ); + } + lsmash_entry_t *next = entry->next; + free( entry ); + entry = next; + } + free( chpl->list ); + isom_remove_box( chpl, isom_udta_t ); +} + +static void isom_remove_keys_entry( isom_keys_entry_t *data ) +{ + if( !data ) + return; + if( data->key_value ) + free( data->key_value ); + free( data ); +} + +static void isom_remove_keys( isom_keys_t *keys ) +{ + if( !keys ) + return; + lsmash_remove_list( keys->list, isom_remove_keys_entry ); + isom_remove_box( keys, isom_meta_t ); +} + +static void isom_remove_mean( isom_mean_t *mean ) +{ + if( !mean ) + return; + if( mean->meaning_string ) + free( mean->meaning_string ); + isom_remove_box( mean, isom_metaitem_t ); +} + +static void isom_remove_name( isom_name_t *name ) +{ + if( !name ) + return; + if( name->name ) + free( name->name ); + isom_remove_box( name, isom_metaitem_t ); +} + +static void isom_remove_data( isom_data_t *data ) +{ + if( !data ) + return; + if( data->value ) + free( data->value ); + isom_remove_box( data, isom_metaitem_t ); +} + +static void isom_remove_metaitem( isom_metaitem_t *metaitem ) +{ + if( !metaitem ) + return; + isom_remove_mean( metaitem->mean ); + isom_remove_name( metaitem->name ); + isom_remove_data( metaitem->data ); + free( metaitem ); +} + +static void isom_remove_ilst( isom_ilst_t *ilst ) +{ + if( !ilst ) + return; + lsmash_remove_list( ilst->item_list, isom_remove_metaitem ); + isom_remove_box( ilst, isom_meta_t ); +} + +static void isom_remove_meta( isom_meta_t *meta ) +{ + if( !meta ) + return; + isom_remove_hdlr( meta->hdlr ); + isom_remove_dinf( meta->dinf ); + isom_remove_keys( meta->keys ); + isom_remove_ilst( meta->ilst ); + if( meta->parent ) + { + if( !meta->parent->type ) + isom_remove_box( meta, lsmash_root_t ); + else if( meta->parent->type == ISOM_BOX_TYPE_MOOV ) + isom_remove_box( meta, isom_moov_t ); + else if( meta->parent->type == ISOM_BOX_TYPE_TRAK ) + isom_remove_box( meta, isom_trak_entry_t ); + else if( meta->parent->type == ISOM_BOX_TYPE_UDTA ) + isom_remove_box( meta, isom_udta_t ); + else + assert( 0 ); + return; + } + free( meta ); +} + +static void isom_remove_cprt( isom_cprt_t *cprt ) +{ + if( !cprt ) + return; + if( cprt->notice ) + free( cprt->notice ); + free( cprt ); +} + +static void isom_remove_udta( isom_udta_t *udta ) +{ + if( !udta ) + return; + isom_remove_chpl( udta->chpl ); + isom_remove_meta( udta->meta ); + free( udta->WLOC ); + free( udta->LOOP ); + free( udta->SelO ); + free( udta->AllF ); + lsmash_remove_list( udta->cprt_list, isom_remove_cprt ); + if( udta->parent ) + { + if( udta->parent->type == ISOM_BOX_TYPE_MOOV ) + isom_remove_box( udta, isom_moov_t ); + else if( udta->parent->type == ISOM_BOX_TYPE_TRAK ) + isom_remove_box( udta, isom_trak_entry_t ); + else + assert( 0 ); + return; + } + free( udta ); +} + +static void isom_remove_sample_pool( isom_sample_pool_t *pool ); + +void isom_remove_trak( isom_trak_entry_t *trak ) +{ + if( !trak ) + return; + isom_remove_tkhd( trak->tkhd ); + isom_remove_tapt( trak->tapt ); + isom_remove_edts( trak->edts ); + isom_remove_tref( trak->tref ); + isom_remove_mdia( trak->mdia ); + isom_remove_udta( trak->udta ); + isom_remove_meta( trak->meta ); + if( trak->cache ) + { + isom_remove_sample_pool( trak->cache->chunk.pool ); + lsmash_remove_list( trak->cache->roll.pool, NULL ); + if( trak->cache->rap ) + free( trak->cache->rap ); + free( trak->cache ); + } + free( trak ); /* Note: the list that contains this trak still has the address of the entry. */ +} + +static void isom_remove_iods( isom_iods_t *iods ) +{ + if( !iods ) + return; + mp4sys_remove_ObjectDescriptor( iods->OD ); + isom_remove_box( iods, isom_moov_t ); +} + +static void isom_remove_mehd( isom_mehd_t *mehd ) +{ + if( !mehd ) + return; + isom_remove_box( mehd, isom_mvex_t ); +} + +static void isom_remove_mvex( isom_mvex_t *mvex ) +{ + if( !mvex ) + return; + isom_remove_mehd( mvex->mehd ); + lsmash_remove_list( mvex->trex_list, NULL ); + isom_remove_box( mvex, isom_moov_t ); +} + +static void isom_remove_moov( lsmash_root_t *root ) +{ + if( !root || !root->moov ) + return; + isom_moov_t *moov = root->moov; + if( moov->mvhd ) + free( moov->mvhd ); + isom_remove_iods( moov->iods ); + lsmash_remove_list( moov->trak_list, isom_remove_trak ); + isom_remove_udta( moov->udta ); + isom_remove_meta( moov->meta ); + isom_remove_mvex( moov->mvex ); + free( moov ); + root->moov = NULL; +} + +static void isom_remove_mfhd( isom_mfhd_t *mfhd ) +{ + if( !mfhd ) + return; + isom_remove_box( mfhd, isom_moof_entry_t ); +} + +static void isom_remove_tfhd( isom_tfhd_t *tfhd ) +{ + if( !tfhd ) + return; + isom_remove_box( tfhd, isom_traf_entry_t ); +} + +static void isom_remove_trun( isom_trun_entry_t *trun ) +{ + if( !trun ) + return; + lsmash_remove_list( trun->optional, NULL ); + free( trun ); /* Note: the list that contains this trun still has the address of the entry. */ +} + +static void isom_remove_traf( isom_traf_entry_t *traf ) +{ + if( !traf ) + return; + isom_remove_tfhd( traf->tfhd ); + lsmash_remove_list( traf->trun_list, isom_remove_trun ); + free( traf ); /* Note: the list that contains this traf still has the address of the entry. */ +} + +static void isom_remove_moof( isom_moof_entry_t *moof ) +{ + if( !moof ) + return; + isom_remove_mfhd( moof->mfhd ); + lsmash_remove_list( moof->traf_list, isom_remove_traf ); + free( moof ); +} + +static void isom_remove_mdat( isom_mdat_t *mdat ) +{ + if( !mdat ) + return; + isom_remove_box( mdat, lsmash_root_t ); +} + +static void isom_remove_free( isom_free_t *skip ) +{ + if( !skip ) + return; + if( skip->data ) + free( skip->data ); + lsmash_root_t *root = (lsmash_root_t *)skip->parent; + free( skip ); + root->free = NULL; +} + +static void isom_remove_tfra( isom_tfra_entry_t *tfra ) +{ + if( !tfra ) + return; + lsmash_remove_list( tfra->list, NULL ); + free( tfra ); +} + +static void isom_remove_mfro( isom_mfro_t *mfro ) +{ + if( !mfro ) + return; + isom_remove_box( mfro, isom_mfra_t ); +} + +static void isom_remove_mfra( isom_mfra_t *mfra ) +{ + if( !mfra ) + return; + lsmash_remove_list( mfra->tfra_list, isom_remove_tfra ); + isom_remove_mfro( mfra->mfro ); + isom_remove_box( mfra, lsmash_root_t ); +} + +/* We put a placeholder for 64-bit media data if the media_size of the argument is set to 0. + * If a Media Data Box already exists and we don't pick movie fragments structure, + * write the actual size of the current one and start a new one. */ +static int isom_new_mdat( lsmash_root_t *root, uint64_t media_size ) +{ + if( !root ) + return 0; + if( root->mdat ) + { + /* Write the actual size of the current Media Data Box. */ + if( !root->fragment && isom_write_mdat_size( root ) ) + return -1; + } + else + { + isom_create_box( mdat, root, ISOM_BOX_TYPE_MDAT ); + root->mdat = mdat; + } + /* Start a new Media Data Box. */ + return isom_write_mdat_header( root, media_size ); +} + +int isom_check_compatibility( lsmash_root_t *root ) +{ + if( !root ) + return -1; + root->qt_compatible = 0; + /* Check brand to decide mandatory boxes. */ + if( !root->ftyp || !root->ftyp->brand_count ) + { + /* No brand declaration means this file is a MP4 version 1 or QuickTime file format. */ + if( root->moov && root->moov->iods ) + { + root->mp4_version1 = 1; + root->isom_compatible = 1; + } + else + root->qt_compatible = 1; + return 0; + } + for( uint32_t i = 0; i < root->ftyp->brand_count; i++ ) + { + switch( root->ftyp->compatible_brands[i] ) + { + case ISOM_BRAND_TYPE_QT : + root->qt_compatible = 1; + break; + case ISOM_BRAND_TYPE_MP41 : + root->mp4_version1 = 1; + break; + case ISOM_BRAND_TYPE_MP42 : + root->mp4_version2 = 1; + break; + case ISOM_BRAND_TYPE_AVC1 : + case ISOM_BRAND_TYPE_ISOM : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 1 ); + break; + case ISOM_BRAND_TYPE_ISO2 : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 2 ); + break; + case ISOM_BRAND_TYPE_ISO3 : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 3 ); + break; + case ISOM_BRAND_TYPE_ISO4 : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 4 ); + break; + case ISOM_BRAND_TYPE_ISO5 : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 5 ); + break; + case ISOM_BRAND_TYPE_ISO6 : + root->max_isom_version = LSMASH_MAX( root->max_isom_version, 6 ); + break; + case ISOM_BRAND_TYPE_M4A : + case ISOM_BRAND_TYPE_M4B : + case ISOM_BRAND_TYPE_M4P : + case ISOM_BRAND_TYPE_M4V : + root->itunes_movie = 1; + break; + case ISOM_BRAND_TYPE_3GP4 : + root->max_3gpp_version = LSMASH_MAX( root->max_3gpp_version, 4 ); + break; + case ISOM_BRAND_TYPE_3GP5 : + root->max_3gpp_version = LSMASH_MAX( root->max_3gpp_version, 5 ); + break; + case ISOM_BRAND_TYPE_3GE6 : + case ISOM_BRAND_TYPE_3GG6 : + case ISOM_BRAND_TYPE_3GP6 : + case ISOM_BRAND_TYPE_3GR6 : + case ISOM_BRAND_TYPE_3GS6 : + root->max_3gpp_version = LSMASH_MAX( root->max_3gpp_version, 6 ); + break; + default : + break; + } + switch( root->ftyp->compatible_brands[i] ) + { + case ISOM_BRAND_TYPE_AVC1 : + case ISOM_BRAND_TYPE_ISO2 : + case ISOM_BRAND_TYPE_ISO3 : + case ISOM_BRAND_TYPE_ISO4 : + case ISOM_BRAND_TYPE_ISO5 : + case ISOM_BRAND_TYPE_ISO6 : + root->avc_extensions = 1; + break; + default : + break; + } + } + root->isom_compatible = !root->qt_compatible || root->mp4_version1 || root->mp4_version2 || root->itunes_movie || root->max_3gpp_version; + return 0; +} + +static uint32_t isom_get_sample_count( isom_trak_entry_t *trak ) +{ + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsz ) + return 0; + return trak->mdia->minf->stbl->stsz->sample_count; +} + +static uint64_t isom_get_dts( isom_stts_t *stts, uint32_t sample_number ) +{ + if( !stts || !stts->list ) + return 0; + uint64_t dts = 0; + uint32_t i = 1; + lsmash_entry_t *entry; + isom_stts_entry_t *data; + for( entry = stts->list->head; entry; entry = entry->next ) + { + data = (isom_stts_entry_t *)entry->data; + if( !data ) + return 0; + if( i + data->sample_count > sample_number ) + break; + dts += (uint64_t)data->sample_delta * data->sample_count; + i += data->sample_count; + } + if( !entry ) + return 0; + dts += (uint64_t)data->sample_delta * (sample_number - i); + return dts; +} + +#if 0 +static uint64_t isom_get_cts( isom_stts_t *stts, isom_ctts_t *ctts, uint32_t sample_number ) +{ + if( !stts || !stts->list ) + return 0; + if( !ctts ) + return isom_get_dts( stts, sample_number ); + uint32_t i = 1; /* This can be 0 (and then condition below shall be changed) but I dare use same algorithm with isom_get_dts. */ + lsmash_entry_t *entry; + isom_ctts_entry_t *data; + if( sample_number == 0 ) + return 0; + for( entry = ctts->list->head; entry; entry = entry->next ) + { + data = (isom_ctts_entry_t *)entry->data; + if( !data ) + return 0; + if( i + data->sample_count > sample_number ) + break; + i += data->sample_count; + } + if( !entry ) + return 0; + return isom_get_dts( stts, sample_number ) + data->sample_offset; +} +#endif + +static int isom_replace_last_sample_delta( isom_stbl_t *stbl, uint32_t sample_delta ) +{ + if( !stbl || !stbl->stts || !stbl->stts->list || !stbl->stts->list->tail || !stbl->stts->list->tail->data ) + return -1; + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)stbl->stts->list->tail->data; + if( sample_delta != last_stts_data->sample_delta ) + { + if( last_stts_data->sample_count > 1 ) + { + last_stts_data->sample_count -= 1; + if( isom_add_stts_entry( stbl, sample_delta ) ) + return -1; + } + else + last_stts_data->sample_delta = sample_delta; + } + return 0; +} + +static int isom_update_mdhd_duration( isom_trak_entry_t *trak, uint32_t last_sample_delta ) +{ + if( !trak || !trak->root || !trak->cache || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->minf + || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stts || !trak->mdia->minf->stbl->stts->list ) + return -1; + lsmash_root_t *root = trak->root; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_stts_t *stts = stbl->stts; + isom_ctts_t *ctts = stbl->ctts; + isom_cslg_t *cslg = stbl->cslg; + mdhd->duration = 0; + uint32_t sample_count = isom_get_sample_count( trak ); + if( !sample_count ) + { + /* Return error if non-fragmented movie has no samples. */ + if( !root->fragment && !stts->list->entry_count ) + return -1; + return 0; + } + /* Now we have at least 1 sample, so do stts_entry. */ + lsmash_entry_t *last_stts = stts->list->tail; + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)last_stts->data; + if( sample_count == 1 ) + mdhd->duration = last_stts_data->sample_delta; + /* Now we have at least 2 samples, + * but dunno whether 1 stts_entry which has 2 samples or 2 stts_entry which has 1 samle each. */ + else if( !ctts ) + { + /* use dts instead of cts */ + mdhd->duration = isom_get_dts( stts, sample_count ); + if( last_sample_delta ) + { + mdhd->duration += last_sample_delta; + if( isom_replace_last_sample_delta( stbl, last_sample_delta ) ) + return -1; + } + else if( last_stts_data->sample_count > 1 ) + mdhd->duration += last_stts_data->sample_delta; /* no need to update last_stts_data->sample_delta */ + else + { + /* Remove the last entry. */ + if( lsmash_remove_entry( stts->list, stts->list->entry_count, NULL ) ) + return -1; + /* copy the previous sample_delta. */ + ++ ((isom_stts_entry_t *)stts->list->tail->data)->sample_count; + mdhd->duration += ((isom_stts_entry_t *)stts->list->tail->data)->sample_delta; + } + } + else + { + if( !ctts->list || ctts->list->entry_count == 0 ) + return -1; + uint64_t dts = 0; + uint64_t max_cts = 0, max2_cts = 0, min_cts = UINT64_MAX; + uint32_t max_offset = 0, min_offset = UINT32_MAX; + int32_t ctd_shift = trak->cache->timestamp.ctd_shift; + uint32_t j, k; + lsmash_entry_t *stts_entry = stts->list->head; + lsmash_entry_t *ctts_entry = ctts->list->head; + j = k = 0; + for( uint32_t i = 0; i < sample_count; i++ ) + { + if( !ctts_entry || !stts_entry ) + return -1; + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)stts_entry->data; + isom_ctts_entry_t *ctts_data = (isom_ctts_entry_t *)ctts_entry->data; + if( !stts_data || !ctts_data ) + return -1; + uint64_t cts; + if( ctd_shift ) + { + /* Anyway, add composition to decode timeline shift for calculating maximum and minimum CTS correctly. */ + int32_t sample_offset = (int32_t)ctts_data->sample_offset; + cts = dts + sample_offset + ctd_shift; + max_offset = LSMASH_MAX( (int32_t)max_offset, sample_offset ); + min_offset = LSMASH_MIN( (int32_t)min_offset, sample_offset ); + } + else + { + cts = dts + ctts_data->sample_offset; + max_offset = LSMASH_MAX( max_offset, ctts_data->sample_offset ); + min_offset = LSMASH_MIN( min_offset, ctts_data->sample_offset ); + } + min_cts = LSMASH_MIN( min_cts, cts ); + if( max_cts < cts ) + { + max2_cts = max_cts; + max_cts = cts; + } + else if( max2_cts < cts ) + max2_cts = cts; + dts += stts_data->sample_delta; + /* If finished sample_count of current entry, move to next. */ + if( ++j == ctts_data->sample_count ) + { + ctts_entry = ctts_entry->next; + j = 0; + } + if( ++k == stts_data->sample_count ) + { + stts_entry = stts_entry->next; + k = 0; + } + } + dts -= last_stts_data->sample_delta; + if( root->fragment ) + /* Overall presentation is extended exceeding this initial movie. + * So, any players shall display the movie exceeding the durations + * indicated in Movie Header Box, Track Header Boxes and Media Header Boxes. + * Samples up to the duration indicated in Movie Extends Header Box shall be displayed. + * In the absence of Movie Extends Header Box, all samples shall be displayed. */ + mdhd->duration += dts + last_sample_delta; + else + { + if( !last_sample_delta ) + { + /* The spec allows an arbitrary value for the duration of the last sample. So, we pick last-1 sample's. */ + last_sample_delta = max_cts - max2_cts; + } + mdhd->duration = max_cts - min_cts + last_sample_delta; + /* To match dts and media duration, update stts and mdhd relatively. */ + if( mdhd->duration > dts ) + last_sample_delta = mdhd->duration - dts; + else + mdhd->duration = dts + last_sample_delta; /* media duration must not less than last dts. */ + } + if( isom_replace_last_sample_delta( stbl, last_sample_delta ) ) + return -1; + /* Explicit composition information and timeline shifting */ + if( cslg || root->qt_compatible || root->max_isom_version >= 4 ) + { + if( ctd_shift ) + { + /* Remove composition to decode timeline shift. */ + max_cts -= ctd_shift; + max2_cts -= ctd_shift; + min_cts -= ctd_shift; + } + int64_t composition_end_time = max_cts + (max_cts - max2_cts); + if( !root->fragment + && ((int32_t)min_offset <= INT32_MAX) && ((int32_t)max_offset <= INT32_MAX) + && ((int64_t)min_cts <= INT32_MAX) && (composition_end_time <= INT32_MAX) ) + { + if( !cslg ) + { + if( isom_add_cslg( trak->mdia->minf->stbl ) ) + return -1; + cslg = stbl->cslg; + } + cslg->compositionToDTSShift = ctd_shift; + cslg->leastDecodeToDisplayDelta = min_offset; + cslg->greatestDecodeToDisplayDelta = max_offset; + cslg->compositionStartTime = min_cts; + cslg->compositionEndTime = composition_end_time; + } + else + { + if( cslg ) + free( cslg ); + stbl->cslg = NULL; + } + } + } + if( mdhd->duration > UINT32_MAX ) + mdhd->version = 1; + return 0; +} + +static int isom_update_mvhd_duration( isom_moov_t *moov ) +{ + if( !moov || !moov->mvhd ) + return -1; + isom_mvhd_t *mvhd = moov->mvhd; + mvhd->duration = 0; + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + { + /* We pick maximum track duration as movie duration. */ + isom_trak_entry_t *data = (isom_trak_entry_t *)entry->data; + if( !data || !data->tkhd ) + return -1; + mvhd->duration = entry != moov->trak_list->head ? LSMASH_MAX( mvhd->duration, data->tkhd->duration ) : data->tkhd->duration; + } + if( mvhd->duration > UINT32_MAX ) + mvhd->version = 1; + return 0; +} + +static int isom_update_tkhd_duration( isom_trak_entry_t *trak ) +{ + if( !trak || !trak->tkhd || !trak->root || !trak->root->moov ) + return -1; + lsmash_root_t *root = trak->root; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->duration = 0; + if( root->fragment || !trak->edts || !trak->edts->elst ) + { + /* If this presentation might be extended or this track doesn't have edit list, calculate track duration from media duration. */ + if( !trak->mdia || !trak->mdia->mdhd || !root->moov->mvhd || !trak->mdia->mdhd->timescale ) + return -1; + if( !trak->mdia->mdhd->duration && isom_update_mdhd_duration( trak, 0 ) ) + return -1; + tkhd->duration = trak->mdia->mdhd->duration * ((double)root->moov->mvhd->timescale / trak->mdia->mdhd->timescale); + } + else + { + /* If the presentation won't be extended and this track has any edit, then track duration is just the sum of the segment_duartions. */ + for( lsmash_entry_t *entry = trak->edts->elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( !data ) + return -1; + tkhd->duration += data->segment_duration; + } + } + if( tkhd->duration > UINT32_MAX ) + tkhd->version = 1; + if( !root->fragment && !tkhd->duration ) + tkhd->duration = tkhd->version == 1 ? 0xffffffffffffffff : 0xffffffff; + return isom_update_mvhd_duration( root->moov ); +} + +int lsmash_update_track_duration( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak ) + return -1; + if( isom_update_mdhd_duration( trak, last_sample_delta ) ) + return -1; + /* If the presentation won't be extended and this track has any edit, we don't change or update duration in tkhd. */ + return (!root->fragment && trak->edts && trak->edts->elst) + ? isom_update_mvhd_duration( root->moov ) /* Only update movie duration. */ + : isom_update_tkhd_duration( trak ); /* Also update movie duration internally. */ +} + +int lsmash_set_avc_config( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, + uint8_t configurationVersion, uint8_t AVCProfileIndication, uint8_t profile_compatibility, uint8_t AVCLevelIndication, uint8_t lengthSizeMinusOne, + uint8_t chroma_format, uint8_t bit_depth_luma_minus8, uint8_t bit_depth_chroma_minus8 ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list ) + return -1; + isom_visual_entry_t *data = (isom_visual_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, entry_number ); + if( !data ) + return -1; + isom_avcC_t *avcC = (isom_avcC_t *)data->avcC; + if( !avcC ) + return -1; + avcC->configurationVersion = configurationVersion; + avcC->AVCProfileIndication = AVCProfileIndication; + avcC->profile_compatibility = profile_compatibility; + avcC->AVCLevelIndication = AVCLevelIndication; + avcC->lengthSizeMinusOne = lengthSizeMinusOne; + if( ISOM_REQUIRES_AVCC_EXTENSION( AVCProfileIndication ) ) + { + avcC->chroma_format = chroma_format; + avcC->bit_depth_luma_minus8 = bit_depth_luma_minus8; + avcC->bit_depth_chroma_minus8 = bit_depth_chroma_minus8; + } + return 0; +} + +static inline int isom_increment_sample_number_in_entry( uint32_t *sample_number_in_entry, uint32_t sample_count_in_entry, lsmash_entry_t **entry ) +{ + if( *sample_number_in_entry != sample_count_in_entry ) + { + *sample_number_in_entry += 1; + return 0; + } + /* Precede the next entry. */ + *sample_number_in_entry = 1; + if( *entry ) + { + *entry = (*entry)->next; + if( *entry && !(*entry)->data ) + return -1; + } + return 0; +} + +static int isom_calculate_bitrate_description( isom_mdia_t *mdia, uint32_t *bufferSizeDB, uint32_t *maxBitrate, uint32_t *avgBitrate, uint32_t sample_description_index ) +{ + isom_stsz_t *stsz = mdia->minf->stbl->stsz; + lsmash_entry_t *stsz_entry = stsz->list ? stsz->list->head : NULL; + lsmash_entry_t *stts_entry = mdia->minf->stbl->stts->list->head; + lsmash_entry_t *stsc_entry = NULL; + lsmash_entry_t *next_stsc_entry = mdia->minf->stbl->stsc->list->head; + isom_stts_entry_t *stts_data = NULL; + isom_stsc_entry_t *stsc_data = NULL; + if( next_stsc_entry && !next_stsc_entry->data ) + return -1; + uint32_t rate = 0; + uint64_t dts = 0; + uint32_t time_wnd = 0; + uint32_t timescale = mdia->mdhd->timescale; + uint32_t chunk_number = 0; + uint32_t sample_number_in_stts = 1; + uint32_t sample_number_in_chunk = 1; + *bufferSizeDB = 0; + *maxBitrate = 0; + *avgBitrate = 0; + while( stts_entry ) + { + if( !stsc_data || sample_number_in_chunk == stsc_data->samples_per_chunk ) + { + /* Move the next chunk. */ + sample_number_in_chunk = 1; + ++chunk_number; + /* Check if the next entry is broken. */ + while( next_stsc_entry && ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk < chunk_number ) + { + /* Just skip broken next entry. */ + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return -1; + } + /* Check if the next chunk belongs to the next sequence of chunks. */ + if( next_stsc_entry && ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk == chunk_number ) + { + stsc_entry = next_stsc_entry; + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return -1; + stsc_data = (isom_stsc_entry_t *)stsc_entry->data; + /* Check if the next contiguous chunks belong to given sample description. */ + if( stsc_data->sample_description_index != sample_description_index ) + { + /* Skip chunks which don't belong to given sample description. */ + uint32_t number_of_skips = 0; + uint32_t first_chunk = stsc_data->first_chunk; + uint32_t samples_per_chunk = stsc_data->samples_per_chunk; + while( next_stsc_entry ) + { + if( ((isom_stsc_entry_t *)next_stsc_entry->data)->sample_description_index != sample_description_index ) + { + stsc_data = (isom_stsc_entry_t *)next_stsc_entry->data; + number_of_skips += (stsc_data->first_chunk - first_chunk) * samples_per_chunk; + first_chunk = stsc_data->first_chunk; + samples_per_chunk = stsc_data->samples_per_chunk; + } + else if( ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk <= first_chunk ) + ; /* broken entry */ + else + break; + /* Just skip the next entry. */ + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return -1; + } + if( !next_stsc_entry ) + break; /* There is no more chunks which don't belong to given sample description. */ + number_of_skips += (((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk - first_chunk) * samples_per_chunk; + for( uint32_t i = 0; i < number_of_skips; i++ ) + { + if( stsz->list ) + { + if( !stsz_entry ) + break; + stsz_entry = stsz_entry->next; + } + if( !stts_entry ) + break; + if( isom_increment_sample_number_in_entry( &sample_number_in_stts, ((isom_stts_entry_t *)stts_entry->data)->sample_count, &stts_entry ) ) + return -1; + } + chunk_number = stsc_data->first_chunk; + } + } + } + else + ++sample_number_in_chunk; + /* Get current sample's size. */ + uint32_t size; + if( stsz->list ) + { + if( !stsz_entry ) + break; + isom_stsz_entry_t *stsz_data = (isom_stsz_entry_t *)stsz_entry->data; + if( !stsz_data ) + return -1; + size = stsz_data->entry_size; + stsz_entry = stsz_entry->next; + } + else + size = stsz->sample_size; + /* Get current sample's DTS. */ + if( stts_data ) + dts += stts_data->sample_delta; + stts_data = (isom_stts_entry_t *)stts_entry->data; + if( !stts_data ) + return -1; + isom_increment_sample_number_in_entry( &sample_number_in_stts, stts_data->sample_count, &stts_entry ); + /* Calculate bitrate description. */ + if( *bufferSizeDB < size ) + *bufferSizeDB = size; + *avgBitrate += size; + rate += size; + if( dts > time_wnd + timescale ) + { + if( rate > *maxBitrate ) + *maxBitrate = rate; + time_wnd = dts; + rate = 0; + } + } + double duration = (double)mdia->mdhd->duration / timescale; + *avgBitrate = (uint32_t)(*avgBitrate / duration); + if( !*maxBitrate ) + *maxBitrate = *avgBitrate; + /* Convert to bits per second. */ + *maxBitrate *= 8; + *avgBitrate *= 8; + return 0; +} + +static int isom_update_bitrate_description( isom_mdia_t *mdia ) +{ + if( !mdia || !mdia->mdhd || !mdia->minf || !mdia->minf->stbl ) + return -1; + isom_stbl_t *stbl = mdia->minf->stbl; + if( !stbl->stsd || !stbl->stsd->list + || !stbl->stsz + || !stbl->stsc || !stbl->stsc->list + || !stbl->stts || !stbl->stts->list ) + return -1; + uint32_t sample_description_index = 0; + for( lsmash_entry_t *entry = stbl->stsd->list->head; entry; entry = entry->next ) + { + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)entry->data; + if( !sample_entry ) + return -1; + ++sample_description_index; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + /* set bitrate info */ + switch( sample_entry->type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + { + isom_visual_entry_t *stsd_data = (isom_visual_entry_t *)sample_entry; + if( !stsd_data ) + return -1; + isom_btrt_t *btrt = stsd_data->btrt; + if( btrt ) + { + if( isom_calculate_bitrate_description( mdia, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ) ) + return -1; + btrt->bufferSizeDB = bufferSizeDB; + btrt->maxBitrate = maxBitrate; + btrt->avgBitrate = avgBitrate; + } + break; + } + case ISOM_CODEC_TYPE_MP4V_VIDEO : + { + isom_visual_entry_t *stsd_data = (isom_visual_entry_t *)sample_entry; + if( !stsd_data || !stsd_data->esds || !stsd_data->esds->ES ) + return -1; + isom_esds_t *esds = stsd_data->esds; + if( isom_calculate_bitrate_description( mdia, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ) ) + return -1; + /* FIXME: avgBitrate is 0 only if VBR in proper. */ + if( mp4sys_update_DecoderConfigDescriptor( esds->ES, bufferSizeDB, maxBitrate, 0 ) ) + return -1; + break; + } + case ISOM_CODEC_TYPE_MP4A_AUDIO : + { + isom_esds_t *esds = NULL; + if( ((isom_audio_entry_t *)sample_entry)->version ) + { + /* MPEG-4 Audio in QTFF */ + isom_audio_entry_t *stsd_data = (isom_audio_entry_t *)sample_entry; + if( !stsd_data || !stsd_data->wave || !stsd_data->wave->esds || !stsd_data->wave->esds->ES ) + return -1; + esds = stsd_data->wave->esds; + } + else + { + isom_audio_entry_t *stsd_data = (isom_audio_entry_t *)sample_entry; + if( !stsd_data || !stsd_data->esds || !stsd_data->esds->ES ) + return -1; + esds = stsd_data->esds; + } + if( isom_calculate_bitrate_description( mdia, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ) ) + return -1; + /* FIXME: avgBitrate is 0 only if VBR in proper. */ + if( mp4sys_update_DecoderConfigDescriptor( esds->ES, bufferSizeDB, maxBitrate, 0 ) ) + return -1; + break; + } + case ISOM_CODEC_TYPE_ALAC_AUDIO : + { + isom_audio_entry_t *alac = (isom_audio_entry_t *)sample_entry; + if( !alac ) + return -1; + if( alac->exdata_length < 36 || !alac->exdata ) + { + isom_wave_t *wave = alac->wave; + if( !wave || wave->exdata_length < 36 || !wave->exdata ) + return -1; + break; /* Apparently, average bitrate field is 0. */ + } + if( isom_calculate_bitrate_description( mdia, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ) ) + return -1; + uint8_t *exdata = (uint8_t *)alac->exdata + 28; + exdata[0] = (avgBitrate >> 24) & 0xff; + exdata[1] = (avgBitrate >> 16) & 0xff; + exdata[2] = (avgBitrate >> 8) & 0xff; + exdata[3] = avgBitrate & 0xff; + break; + } + case ISOM_CODEC_TYPE_EC_3_AUDIO : + { + isom_audio_entry_t *eac3 = (isom_audio_entry_t *)sample_entry; + if( !eac3 ) + return -1; + if( eac3->exdata_length < 10 || !eac3->exdata ) + return -1; + uint16_t bitrate; + if( stbl->stsz->list ) + { + if( isom_calculate_bitrate_description( mdia, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ) ) + return -1; + bitrate = maxBitrate / 1000; /* Use maximum bitrate if VBR. */ + } + else + bitrate = stbl->stsz->sample_size * (eac3->samplerate >> 16) / 192000; /* 192000 == 1536 * 1000 / 8 */ + uint8_t *exdata = (uint8_t *)eac3->exdata + 8; + exdata[0] = (bitrate >> 5) & 0xff; + exdata[1] = (bitrate & 0x1f) << 3; + break; + } + default : + break; + } + } + return sample_description_index ? 0 : -1; +} + +static int isom_check_mandatory_boxes( lsmash_root_t *root ) +{ + if( !root ) + return -1; + if( !root->moov || !root->moov->mvhd ) + return -1; + if( !root->moov->trak_list ) + return -1; + /* A movie requires at least one track. */ + if( !root->moov->trak_list->head ) + return -1; + for( lsmash_entry_t *entry = root->moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak + || !trak->tkhd + || !trak->mdia + || !trak->mdia->mdhd + || !trak->mdia->hdlr + || !trak->mdia->minf + || !trak->mdia->minf->dinf + || !trak->mdia->minf->dinf->dref + || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stsd + || !trak->mdia->minf->stbl->stsz + || !trak->mdia->minf->stbl->stts + || !trak->mdia->minf->stbl->stsc + || !trak->mdia->minf->stbl->stco ) + return -1; + if( root->qt_compatible && !trak->mdia->minf->hdlr ) + return -1; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stsd->list || !stbl->stsd->list->head ) + return -1; + if( !root->fragment + && (!stbl->stsd->list || !stbl->stsd->list->head + || !stbl->stts->list || !stbl->stts->list->head + || !stbl->stsc->list || !stbl->stsc->list->head + || !stbl->stco->list || !stbl->stco->list->head) ) + return -1; + } + if( !root->fragment ) + return 0; + if( !root->moov->mvex || !root->moov->mvex->trex_list ) + return -1; + for( lsmash_entry_t *entry = root->moov->mvex->trex_list->head; entry; entry = entry->next ) + if( !entry->data ) /* trex */ + return -1; + return 0; +} + +static inline uint64_t isom_get_current_mp4time( void ) +{ + return (uint64_t)time( NULL ) + ISOM_MAC_EPOCH_OFFSET; +} + +static int isom_set_media_creation_time( isom_trak_entry_t *trak, uint64_t current_mp4time ) +{ + if( !trak->mdia || !trak->mdia->mdhd ) + return -1; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + if( !mdhd->creation_time ) + mdhd->creation_time = mdhd->modification_time = current_mp4time; + return 0; +} + +static int isom_set_track_creation_time( isom_trak_entry_t *trak, uint64_t current_mp4time ) +{ + if( !trak || !trak->tkhd ) + return -1; + isom_tkhd_t *tkhd = trak->tkhd; + if( !tkhd->creation_time ) + tkhd->creation_time = tkhd->modification_time = current_mp4time; + if( isom_set_media_creation_time( trak, current_mp4time ) ) + return -1; + return 0; +} + +static int isom_set_movie_creation_time( lsmash_root_t *root ) +{ + if( !root || !root->moov || !root->moov->mvhd || !root->moov->trak_list ) + return -1; + uint64_t current_mp4time = isom_get_current_mp4time(); + for( uint32_t i = 1; i <= root->moov->trak_list->entry_count; i++ ) + if( isom_set_track_creation_time( isom_get_trak( root, i ), current_mp4time ) ) + return -1; + isom_mvhd_t *mvhd = root->moov->mvhd; + if( !mvhd->creation_time ) + mvhd->creation_time = mvhd->modification_time = current_mp4time; + return 0; +} + +#define CHECK_LARGESIZE( size ) if( (size) > UINT32_MAX ) (size) += 8 + +static uint64_t isom_update_mvhd_size( isom_mvhd_t *mvhd ) +{ + if( !mvhd ) + return 0; + mvhd->version = 0; + if( mvhd->creation_time > UINT32_MAX || mvhd->modification_time > UINT32_MAX || mvhd->duration > UINT32_MAX ) + mvhd->version = 1; + mvhd->size = ISOM_FULLBOX_COMMON_SIZE + 96 + (uint64_t)mvhd->version * 12; + CHECK_LARGESIZE( mvhd->size ); + return mvhd->size; +} + +static uint64_t isom_update_iods_size( isom_iods_t *iods ) +{ + if( !iods || !iods->OD ) + return 0; + iods->size = ISOM_FULLBOX_COMMON_SIZE + mp4sys_update_ObjectDescriptor_size( iods->OD ); + CHECK_LARGESIZE( iods->size ); + return iods->size; +} + +static uint64_t isom_update_tkhd_size( isom_tkhd_t *tkhd ) +{ + if( !tkhd ) + return 0; + tkhd->version = 0; + if( tkhd->creation_time > UINT32_MAX || tkhd->modification_time > UINT32_MAX || tkhd->duration > UINT32_MAX ) + tkhd->version = 1; + tkhd->size = ISOM_FULLBOX_COMMON_SIZE + 80 + (uint64_t)tkhd->version * 12; + CHECK_LARGESIZE( tkhd->size ); + return tkhd->size; +} + +static uint64_t isom_update_clef_size( isom_clef_t *clef ) +{ + if( !clef ) + return 0; + clef->size = ISOM_FULLBOX_COMMON_SIZE + 8; + CHECK_LARGESIZE( clef->size ); + return clef->size; +} + +static uint64_t isom_update_prof_size( isom_prof_t *prof ) +{ + if( !prof ) + return 0; + prof->size = ISOM_FULLBOX_COMMON_SIZE + 8; + CHECK_LARGESIZE( prof->size ); + return prof->size; +} + +static uint64_t isom_update_enof_size( isom_enof_t *enof ) +{ + if( !enof ) + return 0; + enof->size = ISOM_FULLBOX_COMMON_SIZE + 8; + CHECK_LARGESIZE( enof->size ); + return enof->size; +} + +static uint64_t isom_update_tapt_size( isom_tapt_t *tapt ) +{ + if( !tapt ) + return 0; + tapt->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_clef_size( tapt->clef ) + + isom_update_prof_size( tapt->prof ) + + isom_update_enof_size( tapt->enof ); + CHECK_LARGESIZE( tapt->size ); + return tapt->size; +} + +static uint64_t isom_update_elst_size( isom_elst_t *elst ) +{ + if( !elst || !elst->list ) + return 0; + uint32_t i = 0; + elst->version = 0; + for( lsmash_entry_t *entry = elst->list->head; entry; entry = entry->next, i++ ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( data->segment_duration > UINT32_MAX || data->media_time > INT32_MAX || data->media_time < INT32_MIN ) + elst->version = 1; + } + elst->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)i * ( elst->version ? 20 : 12 ); + CHECK_LARGESIZE( elst->size ); + return elst->size; +} + +static uint64_t isom_update_edts_size( isom_edts_t *edts ) +{ + if( !edts ) + return 0; + edts->size = ISOM_BASEBOX_COMMON_SIZE + isom_update_elst_size( edts->elst ); + CHECK_LARGESIZE( edts->size ); + return edts->size; +} + +static uint64_t isom_update_tref_size( isom_tref_t *tref ) +{ + if( !tref ) + return 0; + tref->size = ISOM_BASEBOX_COMMON_SIZE; + if( tref->ref_list ) + for( lsmash_entry_t *entry = tref->ref_list->head; entry; entry = entry->next ) + { + isom_tref_type_t *ref = (isom_tref_type_t *)entry->data; + ref->size = ISOM_BASEBOX_COMMON_SIZE + (uint64_t)ref->ref_count * 4; + CHECK_LARGESIZE( ref->size ); + tref->size += ref->size; + } + CHECK_LARGESIZE( tref->size ); + return tref->size; +} + +static uint64_t isom_update_mdhd_size( isom_mdhd_t *mdhd ) +{ + if( !mdhd ) + return 0; + mdhd->version = 0; + if( mdhd->creation_time > UINT32_MAX || mdhd->modification_time > UINT32_MAX || mdhd->duration > UINT32_MAX ) + mdhd->version = 1; + mdhd->size = ISOM_FULLBOX_COMMON_SIZE + 20 + (uint64_t)mdhd->version * 12; + CHECK_LARGESIZE( mdhd->size ); + return mdhd->size; +} + +static uint64_t isom_update_hdlr_size( isom_hdlr_t *hdlr ) +{ + if( !hdlr ) + return 0; + hdlr->size = ISOM_FULLBOX_COMMON_SIZE + 20 + (uint64_t)hdlr->componentName_length; + CHECK_LARGESIZE( hdlr->size ); + return hdlr->size; +} + +static uint64_t isom_update_dref_entry_size( isom_dref_entry_t *urln ) +{ + if( !urln ) + return 0; + urln->size = ISOM_FULLBOX_COMMON_SIZE + (uint64_t)urln->name_length + urln->location_length; + CHECK_LARGESIZE( urln->size ); + return urln->size; +} + +static uint64_t isom_update_dref_size( isom_dref_t *dref ) +{ + if( !dref || !dref->list ) + return 0; + dref->size = ISOM_LIST_FULLBOX_COMMON_SIZE; + if( dref->list ) + for( lsmash_entry_t *entry = dref->list->head; entry; entry = entry->next ) + { + isom_dref_entry_t *data = (isom_dref_entry_t *)entry->data; + dref->size += isom_update_dref_entry_size( data ); + } + CHECK_LARGESIZE( dref->size ); + return dref->size; +} + +static uint64_t isom_update_dinf_size( isom_dinf_t *dinf ) +{ + if( !dinf ) + return 0; + dinf->size = ISOM_BASEBOX_COMMON_SIZE + isom_update_dref_size( dinf->dref ); + CHECK_LARGESIZE( dinf->size ); + return dinf->size; +} + +static uint64_t isom_update_vmhd_size( isom_vmhd_t *vmhd ) +{ + if( !vmhd ) + return 0; + vmhd->size = ISOM_FULLBOX_COMMON_SIZE + 8; + CHECK_LARGESIZE( vmhd->size ); + return vmhd->size; +} + +static uint64_t isom_update_smhd_size( isom_smhd_t *smhd ) +{ + if( !smhd ) + return 0; + smhd->size = ISOM_FULLBOX_COMMON_SIZE + 4; + CHECK_LARGESIZE( smhd->size ); + return smhd->size; +} + +static uint64_t isom_update_hmhd_size( isom_hmhd_t *hmhd ) +{ + if( !hmhd ) + return 0; + hmhd->size = ISOM_FULLBOX_COMMON_SIZE + 16; + CHECK_LARGESIZE( hmhd->size ); + return hmhd->size; +} + +static uint64_t isom_update_nmhd_size( isom_nmhd_t *nmhd ) +{ + if( !nmhd ) + return 0; + nmhd->size = ISOM_FULLBOX_COMMON_SIZE; + CHECK_LARGESIZE( nmhd->size ); + return nmhd->size; +} + +static uint64_t isom_update_gmin_size( isom_gmin_t *gmin ) +{ + if( !gmin ) + return 0; + gmin->size = ISOM_FULLBOX_COMMON_SIZE + 12; + CHECK_LARGESIZE( gmin->size ); + return gmin->size; +} + +static uint64_t isom_update_text_size( isom_text_t *text ) +{ + if( !text ) + return 0; + text->size = ISOM_BASEBOX_COMMON_SIZE + 36; + CHECK_LARGESIZE( text->size ); + return text->size; +} + +static uint64_t isom_update_gmhd_size( isom_gmhd_t *gmhd ) +{ + if( !gmhd ) + return 0; + gmhd->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_gmin_size( gmhd->gmin ) + + isom_update_text_size( gmhd->text ); + CHECK_LARGESIZE( gmhd->size ); + return gmhd->size; +} + +static uint64_t isom_update_pasp_size( isom_pasp_t *pasp ) +{ + if( !pasp ) + return 0; + pasp->size = ISOM_BASEBOX_COMMON_SIZE + 8; + CHECK_LARGESIZE( pasp->size ); + return pasp->size; +} + +static uint64_t isom_update_clap_size( isom_clap_t *clap ) +{ + if( !clap ) + return 0; + clap->size = ISOM_BASEBOX_COMMON_SIZE + 32; + CHECK_LARGESIZE( clap->size ); + return clap->size; +} + +static uint64_t isom_update_colr_size( isom_colr_t *colr ) +{ + if( !colr || colr->color_parameter_type == QT_COLOR_PARAMETER_TYPE_PROF ) + return 0; + colr->size = ISOM_BASEBOX_COMMON_SIZE + 10; + CHECK_LARGESIZE( colr->size ); + return colr->size; +} + +static uint64_t isom_update_stsl_size( isom_stsl_t *stsl ) +{ + if( !stsl ) + return 0; + stsl->size = ISOM_FULLBOX_COMMON_SIZE + 6; + CHECK_LARGESIZE( stsl->size ); + return stsl->size; +} + +static uint64_t isom_update_esds_size( isom_esds_t *esds ) +{ + if( !esds ) + return 0; + esds->size = ISOM_FULLBOX_COMMON_SIZE + mp4sys_update_ES_Descriptor_size( esds->ES ); + CHECK_LARGESIZE( esds->size ); + return esds->size; +} + +static uint64_t isom_update_avcC_size( isom_avcC_t *avcC ) +{ + if( !avcC || !avcC->sequenceParameterSets || !avcC->pictureParameterSets ) + return 0; + uint64_t size = ISOM_BASEBOX_COMMON_SIZE + 7; + for( lsmash_entry_t *entry = avcC->sequenceParameterSets->head; entry; entry = entry->next ) + { + isom_avcC_ps_entry_t *data = (isom_avcC_ps_entry_t *)entry->data; + size += 2 + data->parameterSetLength; + } + for( lsmash_entry_t *entry = avcC->pictureParameterSets->head; entry; entry = entry->next ) + { + isom_avcC_ps_entry_t *data = (isom_avcC_ps_entry_t *)entry->data; + size += 2 + data->parameterSetLength; + } + if( ISOM_REQUIRES_AVCC_EXTENSION( avcC->AVCProfileIndication ) ) + { + size += 4; + for( lsmash_entry_t *entry = avcC->sequenceParameterSetExt->head; entry; entry = entry->next ) + { + isom_avcC_ps_entry_t *data = (isom_avcC_ps_entry_t *)entry->data; + size += 2 + data->parameterSetLength; + } + } + avcC->size = size; + CHECK_LARGESIZE( avcC->size ); + return avcC->size; +} + +static uint64_t isom_update_btrt_size( isom_btrt_t *btrt ) +{ + if( !btrt ) + return 0; + btrt->size = ISOM_BASEBOX_COMMON_SIZE + 12; + CHECK_LARGESIZE( btrt->size ); + return btrt->size; +} + +static uint64_t isom_update_visual_entry_size( isom_visual_entry_t *visual ) +{ + if( !visual ) + return 0; + visual->size = ISOM_BASEBOX_COMMON_SIZE + 78 + + isom_update_avcC_size( visual->avcC ) + + isom_update_btrt_size( visual->btrt ) + + isom_update_esds_size( visual->esds ) + + isom_update_colr_size( visual->colr ) + + isom_update_stsl_size( visual->stsl ) + + isom_update_clap_size( visual->clap ) + + isom_update_pasp_size( visual->pasp ) + + (uint64_t)visual->exdata_length; + CHECK_LARGESIZE( visual->size ); + return visual->size; +} + +#if 0 +static uint64_t isom_update_mp4s_entry_size( isom_mp4s_entry_t *mp4s ) +{ + if( !mp4s || mp4s->type != ISOM_CODEC_TYPE_MP4S_SYSTEM ) + return 0; + mp4s->size = ISOM_BASEBOX_COMMON_SIZE + 8 + isom_update_esds_size( mp4s->esds ); + CHECK_LARGESIZE( mp4s->size ); + return mp4s->size; +} +#endif + +static uint64_t isom_update_frma_size( isom_frma_t *frma ) +{ + if( !frma ) + return 0; + frma->size = ISOM_BASEBOX_COMMON_SIZE + 4; + CHECK_LARGESIZE( frma->size ); + return frma->size; +} + +static uint64_t isom_update_enda_size( isom_enda_t *enda ) +{ + if( !enda ) + return 0; + enda->size = ISOM_BASEBOX_COMMON_SIZE + 2; + CHECK_LARGESIZE( enda->size ); + return enda->size; +} + +static uint64_t isom_update_mp4a_size( isom_mp4a_t *mp4a ) +{ + if( !mp4a ) + return 0; + mp4a->size = ISOM_BASEBOX_COMMON_SIZE + 4; + CHECK_LARGESIZE( mp4a->size ); + return mp4a->size; +} + +static uint64_t isom_update_terminator_size( isom_terminator_t *terminator ) +{ + if( !terminator ) + return 0; + terminator->size = ISOM_BASEBOX_COMMON_SIZE; + CHECK_LARGESIZE( terminator->size ); + return terminator->size; +} + +static uint64_t isom_update_wave_size( isom_wave_t *wave ) +{ + if( !wave ) + return 0; + wave->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_frma_size( wave->frma ) + + isom_update_enda_size( wave->enda ) + + isom_update_mp4a_size( wave->mp4a ) + + isom_update_esds_size( wave->esds ) + + isom_update_terminator_size( wave->terminator ) + + (uint64_t)wave->exdata_length; + CHECK_LARGESIZE( wave->size ); + return wave->size; +} + +static uint64_t isom_update_chan_size( isom_chan_t *chan ) +{ + if( !chan ) + return 0; + chan->size = ISOM_FULLBOX_COMMON_SIZE + 12 + 20 * (uint64_t)chan->numberChannelDescriptions; + CHECK_LARGESIZE( chan->size ); + return chan->size; +} + +static uint64_t isom_update_audio_entry_size( isom_audio_entry_t *audio ) +{ + if( !audio ) + return 0; + audio->size = ISOM_BASEBOX_COMMON_SIZE + 28 + + isom_update_esds_size( audio->esds ) + + isom_update_wave_size( audio->wave ) + + isom_update_chan_size( audio->chan ) + + (uint64_t)audio->exdata_length; + if( audio->version == 1 ) + audio->size += 16; + else if( audio->version == 2 ) + audio->size += 36; + CHECK_LARGESIZE( audio->size ); + return audio->size; +} + +static uint64_t isom_update_text_entry_size( isom_text_entry_t *text ) +{ + if( !text ) + return 0; + text->size = ISOM_BASEBOX_COMMON_SIZE + 51 + (uint64_t)text->font_name_length; + CHECK_LARGESIZE( text->size ); + return text->size; +} + +static uint64_t isom_update_ftab_size( isom_ftab_t *ftab ) +{ + if( !ftab || !ftab->list ) + return 0; + ftab->size = ISOM_BASEBOX_COMMON_SIZE + 2; + for( lsmash_entry_t *entry = ftab->list->head; entry; entry = entry->next ) + { + isom_font_record_t *data = (isom_font_record_t *)entry->data; + ftab->size += 3 + data->font_name_length; + } + CHECK_LARGESIZE( ftab->size ); + return ftab->size; +} + +static uint64_t isom_update_tx3g_entry_size( isom_tx3g_entry_t *tx3g ) +{ + if( !tx3g ) + return 0; + tx3g->size = ISOM_BASEBOX_COMMON_SIZE + 38 + isom_update_ftab_size( tx3g->ftab ); + CHECK_LARGESIZE( tx3g->size ); + return tx3g->size; +} + +static uint64_t isom_update_stsd_size( isom_stsd_t *stsd ) +{ + if( !stsd || !stsd->list ) + return 0; + uint64_t size = ISOM_LIST_FULLBOX_COMMON_SIZE; + for( lsmash_entry_t *entry = stsd->list->head; entry; entry = entry->next ) + { + isom_sample_entry_t *data = (isom_sample_entry_t *)entry->data; + switch( data->type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_VC_1_VIDEO : + case QT_CODEC_TYPE_APCH_VIDEO : + case QT_CODEC_TYPE_APCN_VIDEO : + case QT_CODEC_TYPE_APCS_VIDEO : + case QT_CODEC_TYPE_APCO_VIDEO : + case QT_CODEC_TYPE_AP4H_VIDEO : +#ifdef LSMASH_DEMUXER_ENABLED + case ISOM_CODEC_TYPE_MP4V_VIDEO : +#endif +#if 0 + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + case ISOM_CODEC_TYPE_SVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC2_VIDEO : + case ISOM_CODEC_TYPE_DRAC_VIDEO : + case ISOM_CODEC_TYPE_ENCV_VIDEO : + case ISOM_CODEC_TYPE_MJP2_VIDEO : + case ISOM_CODEC_TYPE_S263_VIDEO : +#endif + size += isom_update_visual_entry_size( (isom_visual_entry_t *)data ); + break; +#if 0 + case ISOM_CODEC_TYPE_MP4S_SYSTEM : + size += isom_update_mp4s_entry_size( (isom_mp4s_entry_t *)data ); + break; +#endif + case ISOM_CODEC_TYPE_MP4A_AUDIO : + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : + case QT_CODEC_TYPE_23NI_AUDIO : + case QT_CODEC_TYPE_NONE_AUDIO : + case QT_CODEC_TYPE_LPCM_AUDIO : + case QT_CODEC_TYPE_RAW_AUDIO : + case QT_CODEC_TYPE_SOWT_AUDIO : + case QT_CODEC_TYPE_TWOS_AUDIO : + case QT_CODEC_TYPE_FL32_AUDIO : + case QT_CODEC_TYPE_FL64_AUDIO : + case QT_CODEC_TYPE_IN24_AUDIO : + case QT_CODEC_TYPE_IN32_AUDIO : + case QT_CODEC_TYPE_NOT_SPECIFIED : +#if 0 + case ISOM_CODEC_TYPE_DRA1_AUDIO : + case ISOM_CODEC_TYPE_DTSC_AUDIO : + case ISOM_CODEC_TYPE_DTSH_AUDIO : + case ISOM_CODEC_TYPE_DTSL_AUDIO : + case ISOM_CODEC_TYPE_ENCA_AUDIO : + case ISOM_CODEC_TYPE_G719_AUDIO : + case ISOM_CODEC_TYPE_G726_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + case ISOM_CODEC_TYPE_MLPA_AUDIO : + case ISOM_CODEC_TYPE_RAW_AUDIO : + case ISOM_CODEC_TYPE_SAWP_AUDIO : + case ISOM_CODEC_TYPE_SEVC_AUDIO : + case ISOM_CODEC_TYPE_SQCP_AUDIO : + case ISOM_CODEC_TYPE_SSMV_AUDIO : + case ISOM_CODEC_TYPE_TWOS_AUDIO : +#endif + size += isom_update_audio_entry_size( (isom_audio_entry_t *)data ); + break; + case ISOM_CODEC_TYPE_TX3G_TEXT : + size += isom_update_tx3g_entry_size( (isom_tx3g_entry_t *)data ); + break; + case QT_CODEC_TYPE_TEXT_TEXT : + size += isom_update_text_entry_size( (isom_text_entry_t *)data ); + break; + default : + break; + } + } + stsd->size = size; + CHECK_LARGESIZE( stsd->size ); + return stsd->size; +} + +static uint64_t isom_update_stts_size( isom_stts_t *stts ) +{ + if( !stts || !stts->list ) + return 0; + stts->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)stts->list->entry_count * 8; + CHECK_LARGESIZE( stts->size ); + return stts->size; +} + +static uint64_t isom_update_ctts_size( isom_ctts_t *ctts ) +{ + if( !ctts || !ctts->list ) + return 0; + ctts->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)ctts->list->entry_count * 8; + CHECK_LARGESIZE( ctts->size ); + return ctts->size; +} + +static uint64_t isom_update_cslg_size( isom_cslg_t *cslg ) +{ + if( !cslg ) + return 0; + cslg->size = ISOM_FULLBOX_COMMON_SIZE + 20; + CHECK_LARGESIZE( cslg->size ); + return cslg->size; +} + +static uint64_t isom_update_stsz_size( isom_stsz_t *stsz ) +{ + if( !stsz ) + return 0; + stsz->size = ISOM_FULLBOX_COMMON_SIZE + 8 + ( stsz->list ? (uint64_t)stsz->list->entry_count * 4 : 0 ); + CHECK_LARGESIZE( stsz->size ); + return stsz->size; +} + +static uint64_t isom_update_stss_size( isom_stss_t *stss ) +{ + if( !stss || !stss->list ) + return 0; + stss->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)stss->list->entry_count * 4; + CHECK_LARGESIZE( stss->size ); + return stss->size; +} + +static uint64_t isom_update_stps_size( isom_stps_t *stps ) +{ + if( !stps || !stps->list ) + return 0; + stps->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)stps->list->entry_count * 4; + CHECK_LARGESIZE( stps->size ); + return stps->size; +} + +static uint64_t isom_update_sdtp_size( isom_sdtp_t *sdtp ) +{ + if( !sdtp || !sdtp->list ) + return 0; + sdtp->size = ISOM_FULLBOX_COMMON_SIZE + (uint64_t)sdtp->list->entry_count; + CHECK_LARGESIZE( sdtp->size ); + return sdtp->size; +} + +static uint64_t isom_update_stsc_size( isom_stsc_t *stsc ) +{ + if( !stsc || !stsc->list ) + return 0; + stsc->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)stsc->list->entry_count * 12; + CHECK_LARGESIZE( stsc->size ); + return stsc->size; +} + +static uint64_t isom_update_stco_size( isom_stco_t *stco ) +{ + if( !stco || !stco->list ) + return 0; + stco->size = ISOM_LIST_FULLBOX_COMMON_SIZE + (uint64_t)stco->list->entry_count * (stco->large_presentation ? 8 : 4); + CHECK_LARGESIZE( stco->size ); + return stco->size; +} + +static uint64_t isom_update_sbgp_size( isom_sbgp_entry_t *sbgp ) +{ + if( !sbgp || !sbgp->list ) + return 0; + sbgp->size = ISOM_LIST_FULLBOX_COMMON_SIZE + 4 + (uint64_t)sbgp->list->entry_count * 8; + CHECK_LARGESIZE( sbgp->size ); + return sbgp->size; +} + +static uint64_t isom_update_sgpd_size( isom_sgpd_entry_t *sgpd ) +{ + if( !sgpd || !sgpd->list ) + return 0; + uint64_t size = ISOM_LIST_FULLBOX_COMMON_SIZE + (1 + (sgpd->version == 1)) * 4; + size += (uint64_t)sgpd->list->entry_count * ((sgpd->version == 1) && !sgpd->default_length) * 4; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + size += sgpd->list->entry_count; + break; + case ISOM_GROUP_TYPE_ROLL : + size += (uint64_t)sgpd->list->entry_count * 2; + break; + default : + /* We don't consider other grouping types currently. */ + break; + } + sgpd->size = size; + CHECK_LARGESIZE( sgpd->size ); + return sgpd->size; +} + +static uint64_t isom_update_stbl_size( isom_stbl_t *stbl ) +{ + if( !stbl ) + return 0; + stbl->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_stsd_size( stbl->stsd ) + + isom_update_stts_size( stbl->stts ) + + isom_update_ctts_size( stbl->ctts ) + + isom_update_cslg_size( stbl->cslg ) + + isom_update_stsz_size( stbl->stsz ) + + isom_update_stss_size( stbl->stss ) + + isom_update_stps_size( stbl->stps ) + + isom_update_sdtp_size( stbl->sdtp ) + + isom_update_stsc_size( stbl->stsc ) + + isom_update_stco_size( stbl->stco ); + if( stbl->sgpd_list ) + for( lsmash_entry_t *entry = stbl->sgpd_list->head; entry; entry = entry->next ) + stbl->size += isom_update_sgpd_size( (isom_sgpd_entry_t *)entry->data ); + if( stbl->sbgp_list ) + for( lsmash_entry_t *entry = stbl->sbgp_list->head; entry; entry = entry->next ) + stbl->size += isom_update_sbgp_size( (isom_sbgp_entry_t *)entry->data ); + CHECK_LARGESIZE( stbl->size ); + return stbl->size; +} + +static uint64_t isom_update_minf_size( isom_minf_t *minf ) +{ + if( !minf ) + return 0; + minf->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_vmhd_size( minf->vmhd ) + + isom_update_smhd_size( minf->smhd ) + + isom_update_hmhd_size( minf->hmhd ) + + isom_update_nmhd_size( minf->nmhd ) + + isom_update_gmhd_size( minf->gmhd ) + + isom_update_hdlr_size( minf->hdlr ) + + isom_update_dinf_size( minf->dinf ) + + isom_update_stbl_size( minf->stbl ); + CHECK_LARGESIZE( minf->size ); + return minf->size; +} + +static uint64_t isom_update_mdia_size( isom_mdia_t *mdia ) +{ + if( !mdia ) + return 0; + mdia->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_mdhd_size( mdia->mdhd ) + + isom_update_hdlr_size( mdia->hdlr ) + + isom_update_minf_size( mdia->minf ); + CHECK_LARGESIZE( mdia->size ); + return mdia->size; +} + +static uint64_t isom_update_chpl_size( isom_chpl_t *chpl ) +{ + if( !chpl ) + return 0; + chpl->size = ISOM_FULLBOX_COMMON_SIZE + 4 * (chpl->version == 1) + 1; + for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + chpl->size += 9 + data->chapter_name_length; + } + CHECK_LARGESIZE( chpl->size ); + return chpl->size; +} + +static uint64_t isom_update_mean_size( isom_mean_t *mean ) +{ + if( !mean ) + return 0; + mean->size = ISOM_FULLBOX_COMMON_SIZE + mean->meaning_string_length; + CHECK_LARGESIZE( mean->size ); + return mean->size; +} + +static uint64_t isom_update_name_size( isom_name_t *name ) +{ + if( !name ) + return 0; + name->size = ISOM_FULLBOX_COMMON_SIZE + name->name_length; + CHECK_LARGESIZE( name->size ); + return name->size; +} + +static uint64_t isom_update_data_size( isom_data_t *data ) +{ + if( !data ) + return 0; + data->size = ISOM_BASEBOX_COMMON_SIZE + 8 + data->value_length; + CHECK_LARGESIZE( data->size ); + return data->size; +} + +static uint64_t isom_update_metaitem_size( isom_metaitem_t *metaitem ) +{ + if( !metaitem ) + return 0; + metaitem->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_mean_size( metaitem->mean ) + + isom_update_name_size( metaitem->name ) + + isom_update_data_size( metaitem->data ); + CHECK_LARGESIZE( metaitem->size ); + return metaitem->size; +} + +static uint64_t isom_update_ilst_size( isom_ilst_t *ilst ) +{ + if( !ilst ) + return 0; + ilst->size = ISOM_BASEBOX_COMMON_SIZE; + for( lsmash_entry_t *entry = ilst->item_list->head; entry; entry = entry->next ) + ilst->size += isom_update_metaitem_size( (isom_metaitem_t *)entry->data ); + CHECK_LARGESIZE( ilst->size ); + return ilst->size; +} + +static uint64_t isom_update_meta_size( isom_meta_t *meta ) +{ + if( !meta ) + return 0; + meta->size = ISOM_FULLBOX_COMMON_SIZE + + isom_update_hdlr_size( meta->hdlr ) + + isom_update_dinf_size( meta->dinf ) + + isom_update_ilst_size( meta->ilst ); + CHECK_LARGESIZE( meta->size ); + return meta->size; +} + +static uint64_t isom_update_cprt_size( isom_cprt_t *cprt ) +{ + if( !cprt ) + return 0; + cprt->size = ISOM_FULLBOX_COMMON_SIZE + 2 + cprt->notice_length; + CHECK_LARGESIZE( cprt->size ); + return cprt->size; +} + +static uint64_t isom_update_udta_size( isom_udta_t *udta_moov, isom_udta_t *udta_trak ) +{ + isom_udta_t *udta = udta_trak ? udta_trak : udta_moov ? udta_moov : NULL; + if( !udta ) + return 0; + udta->size = ISOM_BASEBOX_COMMON_SIZE + + (udta_moov ? isom_update_chpl_size( udta->chpl ) : 0) + + isom_update_meta_size( udta->meta ); + if( udta->cprt_list ) + for( lsmash_entry_t *entry = udta->cprt_list->head; entry; entry = entry->next ) + udta->size += isom_update_cprt_size( (isom_cprt_t *)entry->data ); + CHECK_LARGESIZE( udta->size ); + return udta->size; +} + +static uint64_t isom_update_trak_entry_size( isom_trak_entry_t *trak ) +{ + if( !trak ) + return 0; + trak->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_tkhd_size( trak->tkhd ) + + isom_update_tapt_size( trak->tapt ) + + isom_update_edts_size( trak->edts ) + + isom_update_tref_size( trak->tref ) + + isom_update_mdia_size( trak->mdia ) + + isom_update_udta_size( NULL, trak->udta ) + + isom_update_meta_size( trak->meta ); + CHECK_LARGESIZE( trak->size ); + return trak->size; +} + +static uint64_t isom_update_mehd_size( isom_mehd_t *mehd ) +{ + if( !mehd ) + return 0; + if( mehd->fragment_duration > UINT32_MAX ) + mehd->version = 1; + mehd->size = ISOM_FULLBOX_COMMON_SIZE + 4 * (1 + (mehd->version == 1)); + CHECK_LARGESIZE( mehd->size ); + return mehd->size; +} + +static uint64_t isom_update_trex_entry_size( isom_trex_entry_t *trex ) +{ + if( !trex ) + return 0; + trex->size = ISOM_FULLBOX_COMMON_SIZE + 20; + CHECK_LARGESIZE( trex->size ); + return trex->size; +} + +static uint64_t isom_update_mvex_size( isom_mvex_t *mvex ) +{ + if( !mvex ) + return 0; + mvex->size = ISOM_BASEBOX_COMMON_SIZE; + if( mvex->trex_list ) + for( lsmash_entry_t *entry = mvex->trex_list->head; entry; entry = entry->next ) + { + isom_trex_entry_t *trex = (isom_trex_entry_t *)entry->data; + mvex->size += isom_update_trex_entry_size( trex ); + } + if( mvex->root->bs->stream != stdout ) + mvex->size += mvex->mehd ? isom_update_mehd_size( mvex->mehd ) : 20; /* 20 bytes is of placeholder. */ + CHECK_LARGESIZE( mvex->size ); + return mvex->size; +} + +static int isom_update_moov_size( isom_moov_t *moov ) +{ + if( !moov ) + return -1; + moov->size = ISOM_BASEBOX_COMMON_SIZE + + isom_update_mvhd_size( moov->mvhd ) + + isom_update_iods_size( moov->iods ) + + isom_update_udta_size( moov->udta, NULL ) + + isom_update_meta_size( moov->meta ) + + isom_update_mvex_size( moov->mvex ); + if( moov->trak_list ) + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + moov->size += isom_update_trak_entry_size( trak ); + } + CHECK_LARGESIZE( moov->size ); + return 0; +} + +static uint64_t isom_update_mfhd_size( isom_mfhd_t *mfhd ) +{ + if( !mfhd ) + return 0; + mfhd->size = ISOM_FULLBOX_COMMON_SIZE + 4; + CHECK_LARGESIZE( mfhd->size ); + return mfhd->size; +} + +static uint64_t isom_update_tfhd_size( isom_tfhd_t *tfhd ) +{ + if( !tfhd ) + return 0; + tfhd->size = ISOM_FULLBOX_COMMON_SIZE + + 4 + + 8 * !!( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) + + 4 * !!( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) + + 4 * !!( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) + + 4 * !!( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) + + 4 * !!( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ); + CHECK_LARGESIZE( tfhd->size ); + return tfhd->size; +} + +static uint64_t isom_update_trun_entry_size( isom_trun_entry_t *trun ) +{ + if( !trun ) + return 0; + trun->size = ISOM_FULLBOX_COMMON_SIZE + + 4 + + 4 * !!( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) + + 4 * !!( trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ); + uint64_t row_size = 4 * !!( trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) + + 4 * !!( trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) + + 4 * !!( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) + + 4 * !!( trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ); + trun->size += row_size * trun->sample_count; + CHECK_LARGESIZE( trun->size ); + return trun->size; +} + +static uint64_t isom_update_traf_entry_size( isom_traf_entry_t *traf ) +{ + if( !traf ) + return 0; + traf->size = ISOM_BASEBOX_COMMON_SIZE + isom_update_tfhd_size( traf->tfhd ); + if( traf->trun_list ) + for( lsmash_entry_t *entry = traf->trun_list->head; entry; entry = entry->next ) + { + isom_trun_entry_t *trun = (isom_trun_entry_t *)entry->data; + traf->size += isom_update_trun_entry_size( trun ); + } + CHECK_LARGESIZE( traf->size ); + return traf->size; +} + +static int isom_update_moof_entry_size( isom_moof_entry_t *moof ) +{ + if( !moof ) + return -1; + moof->size = ISOM_BASEBOX_COMMON_SIZE + isom_update_mfhd_size( moof->mfhd ); + if( moof->traf_list ) + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + { + isom_traf_entry_t *traf = (isom_traf_entry_t *)entry->data; + moof->size += isom_update_traf_entry_size( traf ); + } + CHECK_LARGESIZE( moof->size ); + return 0; +} + +static uint64_t isom_update_tfra_entry_size( isom_tfra_entry_t *tfra ) +{ + if( !tfra ) + return 0; + tfra->size = ISOM_FULLBOX_COMMON_SIZE + 12; + uint32_t entry_size = 8 * (1 + (tfra->version == 1)) + + tfra->length_size_of_traf_num + 1 + + tfra->length_size_of_trun_num + 1 + + tfra->length_size_of_sample_num + 1; + tfra->size += entry_size * tfra->number_of_entry; + CHECK_LARGESIZE( tfra->size ); + return tfra->size; +} + +static uint64_t isom_update_mfro_size( isom_mfro_t *mfro ) +{ + if( !mfro ) + return 0; + mfro->size = ISOM_FULLBOX_COMMON_SIZE + 4; + CHECK_LARGESIZE( mfro->size ); + return mfro->size; +} + +static int isom_update_mfra_size( isom_mfra_t *mfra ) +{ + if( !mfra ) + return -1; + mfra->size = ISOM_BASEBOX_COMMON_SIZE; + if( mfra->tfra_list ) + for( lsmash_entry_t *entry = mfra->tfra_list->head; entry; entry = entry->next ) + { + isom_tfra_entry_t *tfra = (isom_tfra_entry_t *)entry->data; + mfra->size += isom_update_tfra_entry_size( tfra ); + } + CHECK_LARGESIZE( mfra->size ); + if( mfra->mfro ) + { + mfra->size += isom_update_mfro_size( mfra->mfro ); + mfra->mfro->length = mfra->size; + } + return 0; +} + +/******************************* + public interfaces +*******************************/ + +/*---- track manipulators ----*/ + +void lsmash_delete_track( lsmash_root_t *root, uint32_t track_ID ) +{ + if( !root || !root->moov || !root->moov->trak_list ) + return; + for( lsmash_entry_t *entry = root->moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak || !trak->tkhd ) + return; + if( trak->tkhd->track_ID == track_ID ) + { + lsmash_entry_t *next = entry->next; + lsmash_entry_t *prev = entry->prev; + isom_remove_trak( trak ); + free( entry ); + entry = next; + if( entry ) + { + if( prev ) + prev->next = entry; + entry->prev = prev; + } + return; + } + } +} + +uint32_t lsmash_create_track( lsmash_root_t *root, lsmash_media_type media_type ) +{ + isom_trak_entry_t *trak = isom_add_trak( root ); + if( !trak ) + return 0; + if( isom_add_tkhd( trak, media_type ) + || isom_add_mdia( trak ) + || isom_add_mdhd( trak->mdia, root->qt_compatible ? 0 : ISOM_LANGUAGE_CODE_UNDEFINED ) + || isom_add_minf( trak->mdia ) + || isom_add_stbl( trak->mdia->minf ) + || isom_add_dinf( trak->mdia->minf ) + || isom_add_dref( trak->mdia->minf->dinf ) + || isom_add_stsd( trak->mdia->minf->stbl ) + || isom_add_stts( trak->mdia->minf->stbl ) + || isom_add_stsc( trak->mdia->minf->stbl ) + || isom_add_stco( trak->mdia->minf->stbl ) + || isom_add_stsz( trak->mdia->minf->stbl ) ) + return 0; + if( isom_add_hdlr( trak->mdia, NULL, NULL, media_type ) ) + return 0; + if( root->qt_compatible && isom_add_hdlr( NULL, NULL, trak->mdia->minf, QT_REFERENCE_HANDLER_TYPE_URL ) ) + return 0; + switch( media_type ) + { + case ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK : + if( isom_add_vmhd( trak->mdia->minf ) ) + return 0; + break; + case ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK : + if( isom_add_smhd( trak->mdia->minf ) ) + return 0; + break; + case ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK : + if( isom_add_hmhd( trak->mdia->minf ) ) + return 0; + break; + case ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK : + if( root->qt_compatible || root->itunes_movie ) + { + if( isom_add_gmhd( trak->mdia->minf ) + || isom_add_gmin( trak->mdia->minf->gmhd ) + || isom_add_text( trak->mdia->minf->gmhd ) ) + return 0; + } + else + return 0; /* We support only reference text media track for chapter yet. */ + break; + default : + if( isom_add_nmhd( trak->mdia->minf ) ) + return 0; + break; + } + return trak->tkhd->track_ID; +} + +uint32_t lsmash_get_track_ID( lsmash_root_t *root, uint32_t track_number ) +{ + if( !root || !root->moov ) + return 0; + isom_trak_entry_t *trak = (isom_trak_entry_t *)lsmash_get_entry_data( root->moov->trak_list, track_number ); + if( !trak || !trak->tkhd ) + return 0; + return trak->tkhd->track_ID; +} + +void lsmash_initialize_track_parameters( lsmash_track_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_track_parameters_t) ); + param->audio_volume = 0x0100; + param->matrix[0] = 0x00010000; + param->matrix[4] = 0x00010000; + param->matrix[8] = 0x40000000; +} + +int lsmash_set_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->hdlr || !root->moov->mvhd ) + return -1; + /* Prepare Track Aperture Modes if required. */ + if( root->qt_compatible && param->aperture_modes ) + { + if( !trak->tapt && isom_add_tapt( trak ) ) + return -1; + isom_tapt_t *tapt = trak->tapt; + if( (!tapt->clef && isom_add_clef( tapt )) + || (!tapt->prof && isom_add_prof( tapt )) + || (!tapt->enof && isom_add_enof( tapt )) ) + return -1; + } + else + isom_remove_tapt( trak->tapt ); + /* Set up Track Header. */ + uint32_t media_type = trak->mdia->hdlr->componentSubtype; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->flags = param->mode; + tkhd->track_ID = param->track_ID ? param->track_ID : tkhd->track_ID; + tkhd->duration = !trak->edts || !trak->edts->elst ? param->duration : tkhd->duration; + /* Template fields + * According to 14496-14, these value are all set to defaut values in 14496-12. + * And when a file is read as an MPEG-4 file, these values shall be ignored. + * If a file complies with other specifications, then those fields may have non-default values + * as required by those other specifications. */ + tkhd->alternate_group = root->qt_compatible || root->itunes_movie || root->max_3gpp_version >= 4 ? param->alternate_group : 0; + if( root->qt_compatible || root->itunes_movie ) + { + tkhd->layer = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->video_layer : 0; + tkhd->volume = media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ? param->audio_volume : 0; + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + for( int i = 0; i < 9; i++ ) + tkhd->matrix[i] = param->matrix[i]; + else + for( int i = 0; i < 9; i++ ) + tkhd->matrix[i] = 0; + } + else + { + tkhd->layer = 0; + tkhd->volume = media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ? 0x0100 : 0; + tkhd->matrix[0] = 0x00010000; + tkhd->matrix[1] = tkhd->matrix[2] = tkhd->matrix[3] = 0; + tkhd->matrix[4] = 0x00010000; + tkhd->matrix[5] = tkhd->matrix[6] = tkhd->matrix[7] = 0; + tkhd->matrix[8] = 0x40000000; + } + /* visual presentation size */ + tkhd->width = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->display_width : 0; + tkhd->height = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->display_height : 0; + /* Update next_track_ID if needed. */ + if( root->moov->mvhd->next_track_ID <= tkhd->track_ID ) + root->moov->mvhd->next_track_ID = tkhd->track_ID + 1; + return 0; +} + +int lsmash_get_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak ) + return -1; + isom_tkhd_t *tkhd = trak->tkhd; + param->mode = tkhd->flags; + param->track_ID = tkhd->track_ID; + param->duration = tkhd->duration; + param->video_layer = tkhd->layer; + param->alternate_group = tkhd->alternate_group; + param->audio_volume = tkhd->volume; + for( int i = 0; i < 9; i++ ) + param->matrix[i] = tkhd->matrix[i]; + param->display_width = tkhd->width; + param->display_height = tkhd->height; + param->aperture_modes = !!trak->tapt; + return 0; +} + +static int isom_set_media_handler_name( lsmash_root_t *root, uint32_t track_ID, char *handler_name ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->hdlr ) + return -1; + isom_hdlr_t *hdlr = trak->mdia->hdlr; + uint8_t *name = NULL; + uint32_t name_length = strlen( handler_name ) + root->isom_compatible + root->qt_compatible; + if( root->qt_compatible ) + name_length = LSMASH_MIN( name_length, 255 ); + if( name_length > hdlr->componentName_length && hdlr->componentName ) + name = realloc( hdlr->componentName, name_length ); + else if( !hdlr->componentName ) + name = malloc( name_length ); + else + name = hdlr->componentName; + if( !name ) + return -1; + if( root->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + root->qt_compatible, handler_name, strlen( handler_name ) ); + if( root->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + return 0; +} + +static int isom_set_data_handler_name( lsmash_root_t *root, uint32_t track_ID, char *handler_name ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->hdlr ) + return -1; + isom_hdlr_t *hdlr = trak->mdia->minf->hdlr; + uint8_t *name = NULL; + uint32_t name_length = strlen( handler_name ) + root->isom_compatible + root->qt_compatible; + if( root->qt_compatible ) + name_length = LSMASH_MIN( name_length, 255 ); + if( name_length > hdlr->componentName_length && hdlr->componentName ) + name = realloc( hdlr->componentName, name_length ); + else if( !hdlr->componentName ) + name = malloc( name_length ); + else + name = hdlr->componentName; + if( !name ) + return -1; + if( root->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + root->qt_compatible, handler_name, strlen( handler_name ) ); + if( root->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + return 0; +} + +uint32_t lsmash_get_media_timescale( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd ) + return 0; + return trak->mdia->mdhd->timescale; +} + +uint64_t lsmash_get_media_duration( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd ) + return 0; + return trak->mdia->mdhd->duration; +} + +uint64_t lsmash_get_track_duration( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->tkhd ) + return 0; + return trak->tkhd->duration; +} + +uint32_t lsmash_get_last_sample_delta( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stts || !trak->mdia->minf->stbl->stts->list + || !trak->mdia->minf->stbl->stts->list->tail || !trak->mdia->minf->stbl->stts->list->tail->data ) + return 0; + return ((isom_stts_entry_t *)trak->mdia->minf->stbl->stts->list->tail->data)->sample_delta; +} + +uint32_t lsmash_get_start_time_offset( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->ctts || !trak->mdia->minf->stbl->ctts->list + || !trak->mdia->minf->stbl->ctts->list->head || !trak->mdia->minf->stbl->ctts->list->head->data ) + return 0; + return ((isom_ctts_entry_t *)trak->mdia->minf->stbl->ctts->list->head->data)->sample_offset; +} + +uint32_t lsmash_get_composition_to_decode_shift( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return 0; + uint32_t sample_count = isom_get_sample_count( trak ); + if( sample_count == 0 ) + return 0; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stts || !stbl->stts->list || !stbl->ctts || !stbl->ctts->list ) + return 0; + if( !(root->max_isom_version >= 4 && stbl->ctts->version == 1) && !root->qt_compatible ) + return 0; /* This movie shall not have composition to decode timeline shift. */ + lsmash_entry_t *stts_entry = stbl->stts->list->head; + lsmash_entry_t *ctts_entry = stbl->ctts->list->head; + if( !stts_entry || !ctts_entry ) + return 0; + uint64_t dts = 0; + uint64_t cts = 0; + uint32_t ctd_shift = 0; + uint32_t i = 0; + uint32_t j = 0; + for( uint32_t k = 0; k < sample_count; i++ ) + { + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)stts_entry->data; + isom_ctts_entry_t *ctts_data = (isom_ctts_entry_t *)ctts_entry->data; + if( !stts_data || !ctts_data ) + return 0; + cts = dts + (int32_t)ctts_data->sample_offset; + if( dts > cts + ctd_shift ) + ctd_shift = dts - cts; + dts += stts_data->sample_delta; + if( ++i == stts_data->sample_count ) + { + stts_entry = stts_entry->next; + if( !stts_entry ) + return 0; + i = 0; + } + if( ++j == ctts_data->sample_count ) + { + ctts_entry = ctts_entry->next; + if( !ctts_entry ) + return 0; + j = 0; + } + } + return ctd_shift; +} + +uint16_t lsmash_pack_iso_language( char *iso_language ) +{ + if( !iso_language || strlen( iso_language ) != 3 ) + return 0; + return (uint16_t)LSMASH_PACK_ISO_LANGUAGE( iso_language[0], iso_language[1], iso_language[2] ); +} + +static int isom_iso2mac_language( uint16_t ISO_language, uint16_t *MAC_language ) +{ + if( !MAC_language ) + return -1; + int i = 0; + for( ; isom_languages[i].iso_name; i++ ) + if( ISO_language == isom_languages[i].iso_name ) + break; + if( !isom_languages[i].iso_name ) + return -1; + *MAC_language = isom_languages[i].mac_value; + return 0; +} + +static int isom_mac2iso_language( uint16_t MAC_language, uint16_t *ISO_language ) +{ + if( !ISO_language ) + return -1; + int i = 0; + for( ; isom_languages[i].iso_name; i++ ) + if( MAC_language == isom_languages[i].mac_value ) + break; + *ISO_language = isom_languages[i].iso_name ? isom_languages[i].iso_name : ISOM_LANGUAGE_CODE_UNDEFINED; + return 0; +} + +static int isom_set_media_language( lsmash_root_t *root, uint32_t track_ID, uint16_t ISO_language, uint16_t MAC_language ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd ) + return -1; + uint16_t language = 0; + if( root->isom_compatible ) + { + if( ISO_language ) + language = ISO_language; + else if( MAC_language ) + { + if( isom_mac2iso_language( MAC_language, &language ) ) + return -1; + } + else + language = ISOM_LANGUAGE_CODE_UNDEFINED; + } + else if( root->qt_compatible ) + { + if( ISO_language ) + { + if( isom_iso2mac_language( ISO_language, &language ) ) + return -1; + } + else + language = MAC_language; + } + else + return -1; + trak->mdia->mdhd->language = language; + return 0; +} + +static int isom_create_grouping( isom_trak_entry_t *trak, isom_grouping_type grouping_type ) +{ + lsmash_root_t *root = trak->root; + switch( grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + assert( root->max_isom_version >= 6 ); + break; + case ISOM_GROUP_TYPE_ROLL : + assert( root->avc_extensions || root->qt_compatible ); + break; + default : + assert( 0 ); + break; + } + if( !isom_add_sgpd( trak->mdia->minf->stbl, grouping_type ) + || !isom_add_sbgp( trak->mdia->minf->stbl, grouping_type ) ) + return -1; + return 0; +} + +void lsmash_initialize_media_parameters( lsmash_media_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_media_parameters_t) ); + param->timescale = 1; +} + +int lsmash_set_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + trak->mdia->mdhd->timescale = param->timescale; + if( isom_set_media_language( root, track_ID, param->ISO_language, param->MAC_language ) ) + return -1; + if( param->media_handler_name + && isom_set_media_handler_name( root, track_ID, param->media_handler_name ) ) + return -1; + if( root->qt_compatible && param->data_handler_name + && isom_set_data_handler_name( root, track_ID, param->data_handler_name ) ) + return -1; + if( (root->avc_extensions || root->qt_compatible) && param->roll_grouping + && isom_create_grouping( trak, ISOM_GROUP_TYPE_ROLL ) ) + return -1; + if( (root->max_isom_version >= 6) && param->rap_grouping + && isom_create_grouping( trak, ISOM_GROUP_TYPE_RAP ) ) + return -1; + return 0; +} + +int lsmash_get_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->hdlr + || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_sbgp_entry_t *sbgp; + isom_sgpd_entry_t *sgpd; + param->timescale = mdhd->timescale; + param->handler_type = trak->mdia->hdlr->componentSubtype; + param->duration = mdhd->duration; + /* Whether sample grouping present. */ + sbgp = isom_get_sample_to_group( stbl, ISOM_GROUP_TYPE_ROLL ); + sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_ROLL ); + param->roll_grouping = sbgp && sgpd; + sbgp = isom_get_sample_to_group( stbl, ISOM_GROUP_TYPE_RAP ); + sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + param->rap_grouping = sbgp && sgpd; + /* Get media language. */ + if( mdhd->language >= 0x800 ) + { + param->MAC_language = 0; + param->ISO_language = mdhd->language; + } + else + { + param->MAC_language = mdhd->language; + param->ISO_language = 0; + } + /* Get handler name(s). */ + isom_hdlr_t *hdlr = trak->mdia->hdlr; + int length = LSMASH_MIN( 255, hdlr->componentName_length ); + if( length ) + { + memcpy( param->media_handler_name_shadow, hdlr->componentName + root->qt_compatible, length ); + param->media_handler_name_shadow[length - 2 + root->isom_compatible + root->qt_compatible] = '\0'; + param->media_handler_name = param->media_handler_name_shadow; + } + else + { + param->media_handler_name = NULL; + memset( param->media_handler_name_shadow, 0, sizeof(param->media_handler_name_shadow) ); + } + if( trak->mdia->minf->hdlr ) + { + hdlr = trak->mdia->minf->hdlr; + length = LSMASH_MIN( 255, hdlr->componentName_length ); + if( length ) + { + memcpy( param->data_handler_name_shadow, hdlr->componentName + root->qt_compatible, length ); + param->data_handler_name_shadow[length - 2 + root->isom_compatible + root->qt_compatible] = '\0'; + param->data_handler_name = param->data_handler_name_shadow; + } + else + { + param->data_handler_name = NULL; + memset( param->data_handler_name_shadow, 0, sizeof(param->data_handler_name_shadow) ); + } + } + else + { + param->data_handler_name = NULL; + memset( param->data_handler_name_shadow, 0, sizeof(param->data_handler_name_shadow) ); + } + return 0; +} + +/*---- movie manipulators ----*/ + +lsmash_root_t *lsmash_open_movie( const char *filename, lsmash_file_mode mode ) +{ + if( !filename ) + return NULL; + char open_mode[4] = { 0 }; + if( mode & LSMASH_FILE_MODE_WRITE ) + memcpy( open_mode, "w+b", 4 ); +#ifdef LSMASH_DEMUXER_ENABLED + else if( mode & LSMASH_FILE_MODE_READ ) + memcpy( open_mode, "rb", 3 ); +#endif + if( !open_mode[0] ) + return NULL; + lsmash_root_t *root = lsmash_malloc_zero( sizeof(lsmash_root_t) ); + if( !root ) + return NULL; + root->root = root; + root->bs = lsmash_malloc_zero( sizeof(lsmash_bs_t) ); + if( !root->bs ) + goto fail; + if( !strcmp( filename, "-" ) ) + { + if( mode & LSMASH_FILE_MODE_READ ) + root->bs->stream = stdin; + else if( (mode & LSMASH_FILE_MODE_WRITE) && (mode & LSMASH_FILE_MODE_FRAGMENTED) ) + root->bs->stream = stdout; + } + else + root->bs->stream = fopen( filename, open_mode ); + if( !root->bs->stream ) + goto fail; + root->flags = mode; + if( mode & LSMASH_FILE_MODE_WRITE ) + { + if( isom_add_moov( root ) || isom_add_mvhd( root->moov ) ) + goto fail; + root->qt_compatible = 1; /* QTFF is default file format. */ + } +#ifdef LSMASH_DEMUXER_ENABLED + if( (mode & (LSMASH_FILE_MODE_READ | LSMASH_FILE_MODE_DUMP)) ) + { + if( isom_read_root( root ) ) + goto fail; + root->max_read_size = 4 * 1024 * 1024; + } +#endif + if( mode & LSMASH_FILE_MODE_FRAGMENTED ) + { + root->fragment = lsmash_malloc_zero( sizeof(isom_fragment_manager_t) ); + if( !root->fragment ) + goto fail; + root->fragment->pool = lsmash_create_entry_list(); + if( !root->fragment->pool ) + goto fail; + } + return root; +fail: + lsmash_destroy_root( root ); + return NULL; +} + +static int isom_finish_fragment_movie( lsmash_root_t *root ); + +/* A movie fragment cannot switch a sample description to another. + * So you must call this function before switching sample descriptions. */ +int lsmash_create_fragment_movie( lsmash_root_t *root ) +{ + if( !root || !root->bs || !root->fragment || !root->moov || !root->moov->trak_list ) + return -1; + /* Finish the previous movie fragment before starting a new one. */ + if( isom_finish_fragment_movie( root ) ) + return -1; + /* We always hold only one movie fragment except for the initial movie (a pair of moov and mdat). */ + if( root->fragment->movie && root->moof_list->entry_count != 1 ) + return -1; + isom_moof_entry_t *moof = isom_add_moof( root ); + if( isom_add_mfhd( moof ) ) + return -1; + root->fragment->movie = moof; + moof->mfhd->sequence_number = ++ root->fragment->fragment_count; + if( root->moof_list->entry_count == 1 ) + return 0; + /* Remove the previous movie fragment. */ + return lsmash_remove_entry( root->moof_list, 1, isom_remove_moof ); +} + +static int isom_set_brands( lsmash_root_t *root, lsmash_brand_type major_brand, uint32_t minor_version, lsmash_brand_type *brands, uint32_t brand_count ) +{ + if( brand_count > 50 ) + return -1; /* We support setting brands up to 50. */ + if( !brand_count ) + { + /* Absence of File Type Box means this file is a QuickTime or MP4 version 1 format file. */ + if( root->ftyp ) + { + if( root->ftyp->compatible_brands ) + free( root->ftyp->compatible_brands ); + free( root->ftyp ); + root->ftyp = NULL; + } + return 0; + } + if( !root->ftyp && isom_add_ftyp( root ) ) + return -1; + isom_ftyp_t *ftyp = root->ftyp; + ftyp->major_brand = major_brand; + ftyp->minor_version = minor_version; + lsmash_brand_type *compatible_brands; + if( !ftyp->compatible_brands ) + compatible_brands = malloc( brand_count * sizeof(uint32_t) ); + else + compatible_brands = realloc( ftyp->compatible_brands, brand_count * sizeof(uint32_t) ); + if( !compatible_brands ) + return -1; + ftyp->compatible_brands = compatible_brands; + for( uint32_t i = 0; i < brand_count; i++ ) + { + ftyp->compatible_brands[i] = brands[i]; + ftyp->size += 4; + } + ftyp->brand_count = brand_count; + return isom_check_compatibility( root ); +} + +void lsmash_initialize_movie_parameters( lsmash_movie_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_movie_parameters_t) ); + param->max_chunk_duration = 0.5; + param->max_async_tolerance = 2.0; + param->max_chunk_size = 4 * 1024 * 1024; + param->max_read_size = 4 * 1024 * 1024; + param->timescale = 600; + param->playback_rate = 0x00010000; + param->playback_volume = 0x0100; +} + +int lsmash_set_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ) +{ + if( !root || !root->moov || !root->moov->mvhd + || isom_set_brands( root, param->major_brand, param->minor_version, param->brands, param->number_of_brands ) ) + return -1; + isom_mvhd_t *mvhd = root->moov->mvhd; + root->max_chunk_duration = param->max_chunk_duration; + root->max_async_tolerance = LSMASH_MAX( param->max_async_tolerance, 2 * param->max_chunk_duration ); + root->max_chunk_size = param->max_chunk_size; + root->max_read_size = param->max_read_size; + mvhd->timescale = param->timescale; + if( root->qt_compatible || root->itunes_movie ) + { + mvhd->rate = param->playback_rate; + mvhd->volume = param->playback_volume; + mvhd->previewTime = param->preview_time; + mvhd->previewDuration = param->preview_duration; + mvhd->posterTime = param->poster_time; + } + else + { + mvhd->rate = 0x00010000; + mvhd->volume = 0x0100; + mvhd->previewTime = 0; + mvhd->previewDuration = 0; + mvhd->posterTime = 0; + } + return 0; +} + +int lsmash_get_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ) +{ + if( !root || !root->moov || !root->moov->mvhd ) + return -1; + isom_mvhd_t *mvhd = root->moov->mvhd; + if( root->ftyp ) + { + isom_ftyp_t *ftyp = root->ftyp; + uint32_t brand_count = LSMASH_MIN( ftyp->brand_count, 50 ); /* brands up to 50 */ + for( uint32_t i = 0; i < brand_count; i++ ) + param->brands_shadow[i] = ftyp->compatible_brands[i]; + param->major_brand = ftyp->major_brand; + param->brands = param->brands_shadow; + param->number_of_brands = brand_count; + param->minor_version = ftyp->minor_version; + } + param->max_chunk_duration = root->max_chunk_duration; + param->max_async_tolerance = root->max_async_tolerance; + param->max_chunk_size = root->max_chunk_size; + param->max_read_size = root->max_read_size; + param->timescale = mvhd->timescale; + param->duration = mvhd->duration; + param->playback_rate = mvhd->rate; + param->playback_volume = mvhd->volume; + param->preview_time = mvhd->previewTime; + param->preview_duration = mvhd->previewDuration; + param->poster_time = mvhd->posterTime; + param->number_of_tracks = root->moov->trak_list ? root->moov->trak_list->entry_count : 0; + return 0; +} + +uint32_t lsmash_get_movie_timescale( lsmash_root_t *root ) +{ + if( !root || !root->moov || !root->moov->mvhd ) + return 0; + return root->moov->mvhd->timescale; +} + +int lsmash_set_free( lsmash_root_t *root, uint8_t *data, uint64_t data_length ) +{ + if( !root || !root->free || !data || !data_length ) + return -1; + isom_free_t *skip = root->free; + uint8_t *tmp = NULL; + if( !skip->data ) + tmp = malloc( data_length ); + else if( skip->length < data_length ) + tmp = realloc( skip->data, data_length ); + if( !tmp ) + return -1; + memcpy( tmp, data, data_length ); + skip->data = tmp; + skip->length = data_length; + return 0; +} + +int lsmash_add_free( lsmash_root_t *root, uint8_t *data, uint64_t data_length ) +{ + if( !root ) + return -1; + if( !root->free ) + { + isom_create_box( skip, root, ISOM_BOX_TYPE_FREE ); + root->free = skip; + } + if( data && data_length ) + return lsmash_set_free( root, data, data_length ); + return 0; +} + +int lsmash_create_object_descriptor( lsmash_root_t *root ) +{ + if( !root ) + return -1; + /* Return error if this file is not compatible with MP4 file format. */ + if( !root->mp4_version1 && !root->mp4_version2 ) + return -1; + return isom_add_iods( root->moov ); +} + +/*---- finishing functions ----*/ + +static int isom_set_fragment_overall_duration( lsmash_root_t *root ) +{ + if( root->bs->stream == stdout ) + return 0; + isom_mvex_t *mvex = root->moov->mvex; + if( isom_add_mehd( mvex ) ) + return -1; + /* Get the longest duration of the tracks. */ + uint64_t longest_duration = 0; + for( lsmash_entry_t *entry = root->moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak || !trak->cache || !trak->cache->fragment || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->mdhd->timescale ) + return -1; + uint64_t duration; + if( !trak->edts || !trak->edts->elst || !trak->edts->elst->list ) + { + duration = trak->cache->fragment->largest_cts + trak->cache->fragment->last_duration; + duration = (uint64_t)(((double)duration / trak->mdia->mdhd->timescale) * root->moov->mvhd->timescale); + } + else + { + duration = 0; + for( lsmash_entry_t *elst_entry = trak->edts->elst->list->head; elst_entry; elst_entry = elst_entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)elst_entry->data; + if( !data ) + return -1; + duration += data->segment_duration; + } + } + longest_duration = LSMASH_MAX( duration, longest_duration ); + } + mvex->mehd->fragment_duration = longest_duration; + mvex->mehd->version = 1; + isom_update_mehd_size( mvex->mehd ); + /* Write Movie Extends Header Box here. */ + lsmash_bs_t *bs = root->bs; + FILE *stream = bs->stream; + uint64_t current_pos = lsmash_ftell( stream ); + lsmash_fseek( stream, mvex->placeholder_pos, SEEK_SET ); + int ret = isom_write_mehd( bs, mvex->mehd ); + if( !ret ) + ret = lsmash_bs_write_data( bs ); + lsmash_fseek( stream, current_pos, SEEK_SET ); + return ret; +} + +static int isom_write_fragment_random_access_info( lsmash_root_t *root ) +{ + if( root->bs->stream == stdout ) + return 0; + if( isom_update_mfra_size( root->mfra ) ) + return -1; + return isom_write_mfra( root->bs, root->mfra ); +} + +int lsmash_finish_movie( lsmash_root_t *root, lsmash_adhoc_remux_t* remux ) +{ + if( !root || !root->bs || !root->moov || !root->moov->trak_list ) + return -1; + if( root->fragment ) + { + /* Output the final movie fragment. */ + if( isom_finish_fragment_movie( root ) ) + return -1; + /* Write the overall random access information at the tail of the movie. */ + if( isom_write_fragment_random_access_info( root ) ) + return -1; + /* Set overall duration of the movie. */ + return isom_set_fragment_overall_duration( root ); + } + isom_moov_t *moov = root->moov; + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak || !trak->cache || !trak->tkhd || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + uint32_t track_ID = trak->tkhd->track_ID; + uint32_t related_track_ID = trak->related_track_ID; + /* Disable the track if the track is a track reference chapter. */ + if( trak->is_chapter ) + trak->tkhd->flags &= ~ISOM_TRACK_ENABLED; + if( trak->is_chapter && related_track_ID ) + { + /* In order that the track duration of the chapter track doesn't exceed that of the related track. */ + uint64_t track_duration = LSMASH_MIN( trak->tkhd->duration, lsmash_get_track_duration( root, related_track_ID ) ); + if( lsmash_create_explicit_timeline_map( root, track_ID, track_duration, 0, ISOM_EDIT_MODE_NORMAL ) ) + return -1; + } + /* Add stss box if any samples aren't sync sample. */ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !trak->cache->all_sync && !stbl->stss && isom_add_stss( stbl ) ) + return -1; + if( isom_update_bitrate_description( trak->mdia ) ) + return -1; + } + if( root->mp4_version1 == 1 && isom_add_iods( moov ) ) + return -1; + if( isom_check_mandatory_boxes( root ) + || isom_set_movie_creation_time( root ) + || isom_update_moov_size( moov ) + || isom_write_mdat_size( root ) ) + return -1; + + lsmash_bs_t *bs = root->bs; + uint64_t meta_size = root->meta ? root->meta->size : 0; + if( !remux ) + { + if( isom_write_moov( root ) + || isom_write_meta( bs, root->meta ) ) + return -1; + root->size += moov->size + meta_size; + return 0; + } + + /* stco->co64 conversion, depending on last chunk's offset */ + for( lsmash_entry_t* entry = moov->trak_list->head; entry; ) + { + isom_trak_entry_t* trak = (isom_trak_entry_t*)entry->data; + isom_stco_t* stco = trak->mdia->minf->stbl->stco; + if( !stco->list->tail ) + return -1; + if( stco->large_presentation + || (((isom_stco_entry_t*)stco->list->tail->data)->chunk_offset + moov->size + meta_size) <= UINT32_MAX ) + { + entry = entry->next; + continue; /* no need to convert stco into co64 */ + } + /* stco->co64 conversion */ + if( isom_convert_stco_to_co64( trak->mdia->minf->stbl ) + || isom_update_moov_size( moov ) ) + return -1; + entry = moov->trak_list->head; /* whenever any conversion, re-check all traks */ + } + + /* now the amount of offset is fixed. */ + uint64_t mtf_size = moov->size + meta_size; /* sum of size of boxes moved to front */ + + /* buffer size must be at least mtf_size * 2 */ + remux->buffer_size = LSMASH_MAX( remux->buffer_size, mtf_size * 2 ); + + uint8_t* buf[2]; + if( (buf[0] = (uint8_t*)malloc( remux->buffer_size )) == NULL ) + return -1; /* NOTE: i think we still can fallback to "return isom_write_moov( root );" here. */ + uint64_t size = remux->buffer_size / 2; + buf[1] = buf[0] + size; /* split to 2 buffers */ + + /* now the amount of offset is fixed. apply that to stco/co64 */ + for( lsmash_entry_t* entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_stco_t* stco = ((isom_trak_entry_t*)entry->data)->mdia->minf->stbl->stco; + if( stco->large_presentation ) + for( lsmash_entry_t* co64_entry = stco->list->head ; co64_entry ; co64_entry = co64_entry->next ) + ((isom_co64_entry_t*)co64_entry->data)->chunk_offset += mtf_size; + else + for( lsmash_entry_t* stco_entry = stco->list->head ; stco_entry ; stco_entry = stco_entry->next ) + ((isom_stco_entry_t*)stco_entry->data)->chunk_offset += mtf_size; + } + + FILE *stream = bs->stream; + isom_mdat_t *mdat = root->mdat; + uint64_t total = root->size + mtf_size; + uint64_t readnum; + /* backup starting area of mdat and write moov + meta there instead */ + if( lsmash_fseek( stream, mdat->placeholder_pos, SEEK_SET ) ) + goto fail; + readnum = fread( buf[0], 1, size, stream ); + uint64_t read_pos = lsmash_ftell( stream ); + + /* write moov + meta there instead */ + if( lsmash_fseek( stream, mdat->placeholder_pos, SEEK_SET ) + || isom_write_moov( root ) + || isom_write_meta( bs, root->meta ) ) + goto fail; + uint64_t write_pos = lsmash_ftell( stream ); + + mdat->placeholder_pos += mtf_size; /* update placeholder */ + + /* copy-pastan */ + int buf_switch = 1; + while( readnum == size ) + { + if( lsmash_fseek( stream, read_pos, SEEK_SET ) ) + goto fail; + readnum = fread( buf[buf_switch], 1, size, stream ); + read_pos = lsmash_ftell( stream ); + + buf_switch ^= 0x1; + + if( lsmash_fseek( stream, write_pos, SEEK_SET ) + || fwrite( buf[buf_switch], 1, size, stream ) != size ) + goto fail; + write_pos = lsmash_ftell( stream ); + if( remux->func ) remux->func( remux->param, write_pos, total ); // FIXME: + } + if( fwrite( buf[buf_switch^0x1], 1, readnum, stream ) != readnum ) + goto fail; + if( remux->func ) remux->func( remux->param, total, total ); // FIXME: + + root->size += mtf_size; + free( buf[0] ); + return 0; + +fail: + free( buf[0] ); + return -1; +} + +#define GET_MOST_USED( box_name, index, flag_name ) \ + if( most_used[index] < stats.flag_name[i] ) \ + { \ + most_used[index] = stats.flag_name[i]; \ + box_name->default_sample_flags.flag_name = i; \ + } + +static int isom_create_fragment_overall_default_settings( lsmash_root_t *root ) +{ + if( isom_add_mvex( root->moov ) ) + return -1; + for( lsmash_entry_t *trak_entry = root->moov->trak_list->head; trak_entry; trak_entry = trak_entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)trak_entry->data; + if( !trak || !trak->cache || !trak->tkhd || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stts || !stbl->stts->list || !stbl->stsz + || (stbl->stts->list->tail && !stbl->stts->list->tail->data) + || (stbl->stsz->list && stbl->stsz->list->head && !stbl->stsz->list->head->data) ) + return -1; + isom_trex_entry_t *trex = isom_add_trex( root->moov->mvex ); + if( !trex ) + return -1; + trex->track_ID = trak->tkhd->track_ID; + /* Set up defaults. */ + trex->default_sample_description_index = trak->cache->chunk.sample_description_index ? trak->cache->chunk.sample_description_index : 1; + trex->default_sample_duration = stbl->stts->list->tail ? ((isom_stts_entry_t *)stbl->stts->list->tail->data)->sample_delta : 1; + trex->default_sample_size = !stbl->stsz->list + ? stbl->stsz->sample_size : stbl->stsz->list->head + ? ((isom_stsz_entry_t *)stbl->stsz->list->head->data)->entry_size : 0; + if( stbl->sdtp && stbl->sdtp->list ) + { + struct sample_flags_stats_t + { + uint32_t is_leading [4]; + uint32_t sample_depends_on [4]; + uint32_t sample_is_depended_on[4]; + uint32_t sample_has_redundancy[4]; + } stats = { { 0 }, { 0 }, { 0 }, { 0 } }; + for( lsmash_entry_t *sdtp_entry = stbl->sdtp->list->head; sdtp_entry; sdtp_entry = sdtp_entry->next ) + { + isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)sdtp_entry->data; + if( !data ) + return -1; + ++ stats.is_leading [ data->is_leading ]; + ++ stats.sample_depends_on [ data->sample_depends_on ]; + ++ stats.sample_is_depended_on[ data->sample_is_depended_on ]; + ++ stats.sample_has_redundancy[ data->sample_has_redundancy ]; + } + uint32_t most_used[4] = { 0, 0, 0, 0 }; + for( int i = 0; i < 4; i++ ) + { + GET_MOST_USED( trex, 0, is_leading ); + GET_MOST_USED( trex, 1, sample_depends_on ); + GET_MOST_USED( trex, 2, sample_is_depended_on ); + GET_MOST_USED( trex, 3, sample_has_redundancy ); + } + } + trex->default_sample_flags.sample_is_non_sync_sample = !trak->cache->all_sync; + } + return 0; +} + +static int isom_prepare_random_access_info( lsmash_root_t *root ) +{ + if( root->bs->stream == stdout ) + return 0; + if( isom_add_mfra( root ) + || isom_add_mfro( root->mfra ) ) + return -1; + return 0; +} + +static int isom_output_fragment_media_data( lsmash_root_t *root ) +{ + isom_fragment_manager_t *fragment = root->fragment; + if( !fragment->pool->entry_count ) + { + /* no need to write media data */ + lsmash_remove_entries( fragment->pool, lsmash_delete_sample ); + fragment->pool_size = 0; + return 0; + } + /* If there is no available Media Data Box to write samples, add and write a new one. */ + if( isom_new_mdat( root, fragment->pool_size ) ) + return -1; + /* Write samples in the current movie fragment. */ + for( lsmash_entry_t* entry = fragment->pool->head; entry; entry = entry->next ) + { + isom_sample_pool_t *pool = (isom_sample_pool_t *)entry->data; + if( !pool ) + return -1; + lsmash_bs_put_bytes( root->bs, pool->data, pool->size ); + } + if( lsmash_bs_write_data( root->bs ) ) + return -1; + root->size += root->mdat->size; + lsmash_remove_entries( fragment->pool, isom_remove_sample_pool ); + fragment->pool_size = 0; + return 0; +} + +static int isom_finish_fragment_initial_movie( lsmash_root_t *root ) +{ + if( !root->moov || !root->moov->trak_list ) + return -1; + isom_moov_t *moov = root->moov; + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *trak = (isom_trak_entry_t *)entry->data; + if( !trak || !trak->cache || !trak->tkhd || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->minf || !trak->mdia->minf->stbl ) + return -1; + if( isom_get_sample_count( trak ) ) + { + /* Add stss box if any samples aren't sync sample. */ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !trak->cache->all_sync && !stbl->stss && isom_add_stss( stbl ) ) + return -1; + } + else + trak->tkhd->duration = 0; + if( isom_update_bitrate_description( trak->mdia ) ) + return -1; + } + if( root->mp4_version1 == 1 && isom_add_iods( moov ) ) + return -1; + if( isom_create_fragment_overall_default_settings( root ) + || isom_prepare_random_access_info( root ) + || isom_check_mandatory_boxes( root ) + || isom_set_movie_creation_time( root ) + || isom_update_moov_size( moov ) ) + return -1; + /* stco->co64 conversion, depending on last chunk's offset */ + uint64_t meta_size = root->meta ? root->meta->size : 0; + for( lsmash_entry_t* entry = moov->trak_list->head; entry; ) + { + isom_trak_entry_t* trak = (isom_trak_entry_t*)entry->data; + isom_stco_t* stco = trak->mdia->minf->stbl->stco; + if( !stco->list->tail /* no samples */ + || stco->large_presentation + || (((isom_stco_entry_t*)stco->list->tail->data)->chunk_offset + moov->size + meta_size) <= UINT32_MAX ) + { + entry = entry->next; + continue; /* no need to convert stco into co64 */ + } + /* stco->co64 conversion */ + if( isom_convert_stco_to_co64( trak->mdia->minf->stbl ) + || isom_update_moov_size( moov ) ) + return -1; + entry = moov->trak_list->head; /* whenever any conversion, re-check all traks */ + } + /* Now, the amount of offset is fixed. Apply that to stco/co64. */ + uint64_t preceding_size = moov->size + meta_size; + for( lsmash_entry_t* entry = moov->trak_list->head; entry; entry = entry->next ) + { + isom_stco_t* stco = ((isom_trak_entry_t*)entry->data)->mdia->minf->stbl->stco; + if( stco->large_presentation ) + for( lsmash_entry_t* co64_entry = stco->list->head ; co64_entry ; co64_entry = co64_entry->next ) + ((isom_co64_entry_t*)co64_entry->data)->chunk_offset += preceding_size; + else + for( lsmash_entry_t* stco_entry = stco->list->head ; stco_entry ; stco_entry = stco_entry->next ) + ((isom_stco_entry_t*)stco_entry->data)->chunk_offset += preceding_size; + } + /* Write File Type Box here if it was not written yet. */ + if( !root->file_type_written && isom_write_ftyp( root ) ) + return -1; + /* Write Movie Box. */ + if( isom_write_moov( root ) + || isom_write_meta( root->bs, root->meta ) ) + return -1; + root->size += preceding_size; + /* Output samples. */ + return isom_output_fragment_media_data( root ); +} + +/* Return 1 if there is diffrence, otherwise return 0. */ +static int isom_compare_sample_flags( isom_sample_flags_t *a, isom_sample_flags_t *b ) +{ + return (a->reserved != b->reserved) + || (a->is_leading != b->is_leading) + || (a->sample_depends_on != b->sample_depends_on) + || (a->sample_is_depended_on != b->sample_is_depended_on) + || (a->sample_has_redundancy != b->sample_has_redundancy) + || (a->sample_padding_value != b->sample_padding_value) + || (a->sample_is_non_sync_sample != b->sample_is_non_sync_sample) + || (a->sample_degradation_priority != b->sample_degradation_priority); +} + +static int isom_finish_fragment_movie( lsmash_root_t *root ) +{ + if( !root->moov || !root->moov->trak_list || !root->fragment || !root->fragment->pool ) + return -1; + isom_moof_entry_t *moof = root->fragment->movie; + if( !moof ) + return isom_finish_fragment_initial_movie( root ); + /* Calculate appropriate default_sample_flags of each Track Fragment Header Box. + * And check whether that default_sample_flags is useful or not. */ + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + { + isom_traf_entry_t *traf = (isom_traf_entry_t *)entry->data; + if( !traf || !traf->tfhd || !traf->root || !traf->root->moov || !traf->root->moov->mvex ) + return -1; + isom_tfhd_t *tfhd = traf->tfhd; + isom_trex_entry_t *trex = isom_get_trex( root->moov->mvex, tfhd->track_ID ); + if( !trex ) + return -1; + struct sample_flags_stats_t + { + uint32_t is_leading [4]; + uint32_t sample_depends_on [4]; + uint32_t sample_is_depended_on [4]; + uint32_t sample_has_redundancy [4]; + uint32_t sample_is_non_sync_sample[2]; + } stats = { { 0 }, { 0 }, { 0 }, { 0 }, { 0 } }; + for( lsmash_entry_t *trun_entry = traf->trun_list->head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_entry_t *trun = (isom_trun_entry_t *)trun_entry->data; + if( !trun || !trun->sample_count ) + return -1; + isom_sample_flags_t *sample_flags; + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) + { + if( !trun->optional ) + return -1; + for( lsmash_entry_t *optional_entry = trun->optional->head; optional_entry; optional_entry = optional_entry->next ) + { + isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data; + if( !row ) + return -1; + sample_flags = &row->sample_flags; + ++ stats.is_leading [ sample_flags->is_leading ]; + ++ stats.sample_depends_on [ sample_flags->sample_depends_on ]; + ++ stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ]; + ++ stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ]; + ++ stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ]; + } + } + else + { + sample_flags = &tfhd->default_sample_flags; + stats.is_leading [ sample_flags->is_leading ] += trun->sample_count; + stats.sample_depends_on [ sample_flags->sample_depends_on ] += trun->sample_count; + stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ] += trun->sample_count; + stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ] += trun->sample_count; + stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ] += trun->sample_count; + } + } + uint32_t most_used[5] = { 0, 0, 0, 0, 0 }; + for( int i = 0; i < 4; i++ ) + { + GET_MOST_USED( tfhd, 0, is_leading ); + GET_MOST_USED( tfhd, 1, sample_depends_on ); + GET_MOST_USED( tfhd, 2, sample_is_depended_on ); + GET_MOST_USED( tfhd, 3, sample_has_redundancy ); + if( i < 2 ) + GET_MOST_USED( tfhd, 4, sample_is_non_sync_sample ); + } + int useful_default_sample_duration = 0; + int useful_default_sample_size = 0; + for( lsmash_entry_t *trun_entry = traf->trun_list->head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_entry_t *trun = (isom_trun_entry_t *)trun_entry->data; + if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT) ) + useful_default_sample_duration = 1; + if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT) ) + useful_default_sample_size = 1; + int useful_first_sample_flags = 1; + int useful_default_sample_flags = 1; + if( trun->sample_count == 1 ) + { + /* It is enough to check only if first_sample_flags equals default_sample_flags or not. + * If it is equal, just use default_sample_flags. + * If not, just use first_sample_flags of this run. */ + if( !isom_compare_sample_flags( &trun->first_sample_flags, &tfhd->default_sample_flags ) ) + useful_first_sample_flags = 0; + } + else if( trun->optional && trun->optional->head ) + { + lsmash_entry_t *optional_entry = trun->optional->head->next; + isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data; + isom_sample_flags_t representative_sample_flags = row->sample_flags; + if( isom_compare_sample_flags( &tfhd->default_sample_flags, &representative_sample_flags ) ) + useful_default_sample_flags = 0; + if( !isom_compare_sample_flags( &trun->first_sample_flags, &representative_sample_flags ) ) + useful_first_sample_flags = 0; + if( useful_default_sample_flags ) + for( optional_entry = optional_entry->next; optional_entry; optional_entry = optional_entry->next ) + { + row = (isom_trun_optional_row_t *)optional_entry->data; + if( isom_compare_sample_flags( &representative_sample_flags, &row->sample_flags ) ) + { + useful_default_sample_flags = 0; + break; + } + } + } + if( useful_default_sample_flags ) + { + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT; + trun->flags &= ~ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + } + else + { + useful_first_sample_flags = 0; + trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + } + if( useful_first_sample_flags ) + trun->flags |= ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT; + } + if( useful_default_sample_duration && tfhd->default_sample_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + else + tfhd->default_sample_duration = trex->default_sample_duration; /* This might be redundant, but is to be more natural. */ + if( useful_default_sample_size && tfhd->default_sample_size != trex->default_sample_size ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT; + else + tfhd->default_sample_size = trex->default_sample_size; /* This might be redundant, but is to be more natural. */ + if( !(tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT) ) + tfhd->default_sample_flags = trex->default_sample_flags; /* This might be redundant, but is to be more natural. */ + else if( !isom_compare_sample_flags( &tfhd->default_sample_flags, &trex->default_sample_flags ) ) + tfhd->flags &= ~ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT; + } + /* When using for live streaming, setting explicit base_data_offset is not preferable. + * However, it's OK because we haven't supported this yet. + * Implicit base_data_offsets that originate in the first byte of each Movie Fragment Box will be implemented + * by the feature of ISO Base Media File Format version 5 or later. + * Media Data Box starts immediately after Movie Fragment Box. */ + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + { + isom_traf_entry_t *traf = (isom_traf_entry_t *)entry->data; + traf->tfhd->flags |= ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT; + } + /* Consider the update of tf_flags here. */ + if( isom_update_moof_entry_size( moof ) ) + return -1; + /* Now, we can calculate offsets in the current movie fragment, so do it. */ + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + { + isom_traf_entry_t *traf = (isom_traf_entry_t *)entry->data; + traf->tfhd->base_data_offset = root->size + moof->size + ISOM_BASEBOX_COMMON_SIZE; + } + if( isom_write_moof( root->bs, moof ) ) + return -1; + root->size += moof->size; + /* Output samples. */ + return isom_output_fragment_media_data( root ); +} + +#undef GET_MOST_USED + +static isom_trun_optional_row_t *isom_request_trun_optional_row( isom_trun_entry_t *trun, isom_tfhd_t *tfhd, uint32_t sample_number ) +{ + isom_trun_optional_row_t *row = NULL; + if( !trun->optional ) + { + trun->optional = lsmash_create_entry_list(); + if( !trun->optional ) + return NULL; + } + if( trun->optional->entry_count < sample_number ) + { + while( trun->optional->entry_count < sample_number ) + { + row = malloc( sizeof(isom_trun_optional_row_t) ); + if( !row ) + return NULL; + /* Copy from default. */ + row->sample_duration = tfhd->default_sample_duration; + row->sample_size = tfhd->default_sample_size; + row->sample_flags = tfhd->default_sample_flags; + row->sample_composition_time_offset = 0; + if( lsmash_add_entry( trun->optional, row ) ) + { + free( row ); + return NULL; + } + } + return row; + } + uint32_t i = 0; + for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next ) + { + row = (isom_trun_optional_row_t *)entry->data; + if( !row ) + return NULL; + if( ++i == sample_number ) + return row; + } + return NULL; +} + +int lsmash_create_fragment_empty_duration( lsmash_root_t *root, uint32_t track_ID, uint32_t duration ) +{ + if( !root || !root->fragment || !root->fragment->movie || !root->moov ) + return -1; + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->tkhd ) + return -1; + isom_trex_entry_t *trex = isom_get_trex( root->moov->mvex, track_ID ); + if( !trex ) + return -1; + isom_moof_entry_t *moof = root->fragment->movie; + isom_traf_entry_t *traf = isom_get_traf( moof, track_ID ); + if( traf ) + return -1; + traf = isom_add_traf( root, moof ); + if( isom_add_tfhd( traf ) ) + return -1; + isom_tfhd_t *tfhd = traf->tfhd; + tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */ + tfhd->track_ID = trak->tkhd->track_ID; + tfhd->default_sample_duration = duration; + if( duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + traf->cache = trak->cache; + traf->cache->fragment->traf_number = moof->traf_list->entry_count; + traf->cache->fragment->last_duration += duration; /* The duration of the last sample includes this empty-duration. */ + return 0; +} + +static int isom_set_fragment_last_duration( isom_traf_entry_t *traf, uint32_t last_duration ) +{ + isom_tfhd_t *tfhd = traf->tfhd; + if( !traf->trun_list || !traf->trun_list->tail || !traf->trun_list->tail->data ) + { + /* There are no track runs in this track fragment, so it is a empty-duration. */ + isom_trex_entry_t *trex = isom_get_trex( traf->root->moov->mvex, tfhd->track_ID ); + if( !trex ) + return -1; + tfhd->flags |= ISOM_TF_FLAGS_DURATION_IS_EMPTY; + if( last_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = last_duration; + traf->cache->fragment->last_duration = last_duration; + return 0; + } + /* Update the last sample_duration if needed. */ + isom_trun_entry_t *trun = (isom_trun_entry_t *)traf->trun_list->tail->data; + if( trun->sample_count == 1 && traf->trun_list->entry_count == 1 ) + { + isom_trex_entry_t *trex = isom_get_trex( traf->root->moov->mvex, tfhd->track_ID ); + if( !trex ) + return -1; + if( last_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = last_duration; + } + else if( last_duration != tfhd->default_sample_duration ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT; + if( trun->flags ) + { + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count ); + if( !row ) + return -1; + row->sample_duration = last_duration; + } + traf->cache->fragment->last_duration = last_duration; + return 0; +} + +int lsmash_set_last_sample_delta( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_delta ) +{ + if( !root || !track_ID ) + return -1; + if( root->fragment && root->fragment->movie ) + { + isom_traf_entry_t *traf = isom_get_traf( root->fragment->movie, track_ID ); + if( !traf || !traf->cache || !traf->tfhd || !traf->trun_list ) + return -1; + return isom_set_fragment_last_duration( traf, sample_delta ); + } + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stsz || !trak->mdia->minf->stbl->stts || !trak->mdia->minf->stbl->stts->list ) + return -1; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_stts_t *stts = stbl->stts; + uint32_t sample_count = isom_get_sample_count( trak ); + if( !stts->list->tail ) + { + if( !sample_count ) + return 0; /* no samples */ + if( sample_count > 1 ) + return -1; /* irregular sample_count */ + if( isom_add_stts_entry( stbl, sample_delta ) ) + return -1; + return lsmash_update_track_duration( root, track_ID, 0 ); + } + uint32_t i = 0; + for( lsmash_entry_t *entry = stts->list->head; entry; entry = entry->next ) + i += ((isom_stts_entry_t *)entry->data)->sample_count; + if( sample_count < i ) + return -1; + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)stts->list->tail->data; + if( !last_stts_data ) + return -1; + if( sample_count > i ) + { + if( sample_count - i > 1 ) + return -1; + /* Add a sample_delta. */ + if( sample_delta == last_stts_data->sample_delta ) + ++ last_stts_data->sample_count; + else if( isom_add_stts_entry( stbl, sample_delta ) ) + return -1; + } + else if( sample_count == i && isom_replace_last_sample_delta( stbl, sample_delta ) ) + return -1; + return lsmash_update_track_duration( root, track_ID, sample_delta ); +} + +void lsmash_discard_boxes( lsmash_root_t *root ) +{ + if( !root ) + return; + isom_remove_ftyp( root->ftyp ); + isom_remove_moov( root ); + lsmash_remove_list( root->moof_list, isom_remove_moof ); + isom_remove_mdat( root->mdat ); + isom_remove_free( root->free ); + isom_remove_meta( root->meta ); + isom_remove_mfra( root->mfra ); + root->ftyp = NULL; + root->moov = NULL; + root->moof_list = NULL; + root->mdat = NULL; + root->free = NULL; + root->mfra = NULL; +} + +void lsmash_destroy_root( lsmash_root_t *root ) +{ + if( !root ) + return; +#ifdef LSMASH_DEMUXER_ENABLED + isom_remove_print_funcs( root ); + isom_remove_timelines( root ); +#endif + lsmash_discard_boxes( root ); + if( root->bs ) + { + if( root->bs->stream ) + fclose( root->bs->stream ); + if( root->bs->data ) + free( root->bs->data ); + free( root->bs ); + } + if( root->fragment ) + { + lsmash_remove_list( root->fragment->pool, lsmash_delete_sample ); + free( root->fragment ); + } + free( root ); +} + +/*---- timeline manipulator ----*/ + +int lsmash_modify_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint64_t segment_duration, int64_t media_time, int32_t media_rate ) +{ + if( !segment_duration || media_time < -1 ) + return -1; + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->edts || !trak->edts->elst || !trak->edts->elst->list ) + return -1; + isom_elst_t *elst = trak->edts->elst; + isom_elst_entry_t *data = (isom_elst_entry_t *)lsmash_get_entry_data( elst->list, entry_number ); + if( !data ) + return -1; + data->segment_duration = segment_duration; + data->media_time = media_time; + data->media_rate = media_rate; + if( !elst->pos || !root->fragment || root->bs->stream == stdout ) + return isom_update_tkhd_duration( trak ); + /* Rewrite the specified entry. + * Note: we don't update the version of the Edit List Box. */ + lsmash_bs_t *bs = root->bs; + FILE *stream = bs->stream; + uint64_t current_pos = lsmash_ftell( stream ); + uint64_t entry_pos = elst->pos + ISOM_LIST_FULLBOX_COMMON_SIZE + ((uint64_t)entry_number - 1) * (elst->version == 1 ? 20 : 12); + lsmash_fseek( stream, entry_pos, SEEK_SET ); + if( elst->version ) + { + lsmash_bs_put_be64( bs, data->segment_duration ); + lsmash_bs_put_be64( bs, data->media_time ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)data->segment_duration ); + lsmash_bs_put_be32( bs, (uint32_t)data->media_time ); + } + lsmash_bs_put_be32( bs, data->media_rate ); + int ret = lsmash_bs_write_data( bs ); + lsmash_fseek( stream, current_pos, SEEK_SET ); + return ret; +} + +int lsmash_create_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint64_t segment_duration, int64_t media_time, int32_t media_rate ) +{ + if( media_time < -1 ) + return -1; + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->tkhd ) + return -1; + segment_duration = (segment_duration || root->fragment) ? segment_duration + : trak->tkhd->duration ? trak->tkhd->duration + : isom_update_tkhd_duration( trak ) ? 0 + : trak->tkhd->duration; + if( isom_add_edts( trak ) + || isom_add_elst( trak->edts ) + || isom_add_elst_entry( trak->edts->elst, segment_duration, media_time, media_rate ) ) + return -1; + return isom_update_tkhd_duration( trak ); +} + +/*---- create / modification time fields manipulators ----*/ + +int lsmash_update_media_modification_time( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->mdia || !trak->mdia->mdhd ) + return -1; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + mdhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( mdhd->creation_time > mdhd->modification_time ) + mdhd->creation_time = mdhd->modification_time; + return 0; +} + +int lsmash_update_track_modification_time( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->tkhd ) + return -1; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( tkhd->creation_time > tkhd->modification_time ) + tkhd->creation_time = tkhd->modification_time; + return 0; +} + +int lsmash_update_movie_modification_time( lsmash_root_t *root ) +{ + if( !root || !root->moov || !root->moov->mvhd ) + return -1; + isom_mvhd_t *mvhd = root->moov->mvhd; + mvhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( mvhd->creation_time > mvhd->modification_time ) + mvhd->creation_time = mvhd->modification_time; + return 0; +} + +/*---- sample manipulators ----*/ +lsmash_sample_t *lsmash_create_sample( uint32_t size ) +{ + lsmash_sample_t *sample = lsmash_malloc_zero( sizeof(lsmash_sample_t) ); + if( !sample ) + return NULL; + if( !size ) + return sample; + sample->data = malloc( size ); + if( !sample->data ) + { + free( sample ); + return NULL; + } + sample->length = size; + return sample; +} + +int lsmash_sample_alloc( lsmash_sample_t *sample, uint32_t size ) +{ + if( !sample ) + return -1; + if( !size ) + { + if( sample->data ) + free( sample->data ); + sample->data = NULL; + sample->length = 0; + return 0; + } + if( size == sample->length ) + return 0; + uint8_t *data; + if( !sample->data ) + data = malloc( size ); + else + data = realloc( sample->data, size ); + if( !data ) + return -1; + sample->data = data; + sample->length = size; + return 0; +} + +void lsmash_delete_sample( lsmash_sample_t *sample ) +{ + if( !sample ) + return; + if( sample->data ) + free( sample->data ); + free( sample ); +} + +isom_sample_pool_t *isom_create_sample_pool( uint64_t size ) +{ + isom_sample_pool_t *pool = lsmash_malloc_zero( sizeof(isom_sample_pool_t) ); + if( !pool ) + return NULL; + if( !size ) + return pool; + pool->data = malloc( size ); + if( !pool->data ) + { + free( pool ); + return NULL; + } + pool->alloc = size; + return pool; +} + +static void isom_remove_sample_pool( isom_sample_pool_t *pool ) +{ + if( !pool ) + return; + if( pool->data ) + free( pool->data ); + free( pool ); +} + +static uint32_t isom_add_size( isom_trak_entry_t *trak, uint32_t sample_size ) +{ + if( isom_add_stsz_entry( trak->mdia->minf->stbl, sample_size ) ) + return 0; + return isom_get_sample_count( trak ); +} + +static uint32_t isom_add_dts( isom_stbl_t *stbl, isom_timestamp_t *cache, uint64_t dts ) +{ + isom_stts_t *stts = stbl->stts; + if( !stts->list->entry_count ) + { + if( isom_add_stts_entry( stbl, dts ) ) + return 0; + cache->dts = dts; + return dts; + } + if( dts <= cache->dts ) + return 0; + uint32_t sample_delta = dts - cache->dts; + isom_stts_entry_t *data = (isom_stts_entry_t *)stts->list->tail->data; + if( data->sample_delta == sample_delta ) + ++ data->sample_count; + else if( isom_add_stts_entry( stbl, sample_delta ) ) + return 0; + cache->dts = dts; + return sample_delta; +} + +static int isom_add_cts( isom_stbl_t *stbl, isom_timestamp_t *cache, uint64_t cts ) +{ + isom_ctts_t *ctts = stbl->ctts; + if( !ctts ) + { + if( cts == cache->dts ) + { + cache->cts = cts; + return 0; + } + /* Add ctts box and the first ctts entry. */ + if( isom_add_ctts( stbl ) || isom_add_ctts_entry( stbl, 0 ) ) + return -1; + ctts = stbl->ctts; + isom_ctts_entry_t *data = (isom_ctts_entry_t *)ctts->list->head->data; + uint32_t sample_count = stbl->stsz->sample_count; + if( sample_count != 1 ) + { + data->sample_count = sample_count - 1; + if( isom_add_ctts_entry( stbl, cts - cache->dts ) ) + return -1; + } + else + data->sample_offset = cts; + cache->cts = cts; + return 0; + } + if( !ctts->list ) + return -1; + isom_ctts_entry_t *data = (isom_ctts_entry_t *)ctts->list->tail->data; + uint32_t sample_offset = cts - cache->dts; + if( data->sample_offset == sample_offset ) + ++ data->sample_count; + else if( isom_add_ctts_entry( stbl, sample_offset ) ) + return -1; + cache->cts = cts; + return 0; +} + +static int isom_add_timestamp( isom_trak_entry_t *trak, uint64_t dts, uint64_t cts ) +{ + if( !trak->cache || !trak->mdia->minf->stbl->stts || !trak->mdia->minf->stbl->stts->list ) + return -1; + lsmash_root_t *root = trak->root; + if( root->isom_compatible && root->qt_compatible && (cts - dts) > INT32_MAX ) + return -1; /* sample_offset is not compatible. */ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_timestamp_t *ts_cache = &trak->cache->timestamp; + uint32_t sample_count = isom_get_sample_count( trak ); + uint32_t sample_delta = sample_count > 1 ? isom_add_dts( stbl, ts_cache, dts ) : 0; + if( sample_count > 1 && !sample_delta ) + return -1; + if( isom_add_cts( stbl, ts_cache, cts ) ) + return -1; + if( (cts + ts_cache->ctd_shift) < dts ) + { + if( (root->max_isom_version < 4 && !root->qt_compatible) /* Negative sample offset is not supported. */ + || (root->max_isom_version >= 4 && trak->root->qt_compatible) /* ctts version 1 is not defined in QTFF. */ + || root->fragment /* Composition time offset is positive. */ + || ((dts - cts) > INT32_MAX) ) /* Overflow */ + return -1; + ts_cache->ctd_shift = dts - cts; + if( !stbl->ctts->version && !trak->root->qt_compatible ) + stbl->ctts->version = 1; + } + if( trak->cache->fragment ) + { + isom_fragment_t *fragment_cache = trak->cache->fragment; + fragment_cache->last_duration = sample_delta; + fragment_cache->largest_cts = LSMASH_MAX( ts_cache->cts, fragment_cache->largest_cts ); + } + return 0; +} + +static int isom_add_sync_point( isom_trak_entry_t *trak, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_cache_t *cache = trak->cache; + if( prop->random_access_type != ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC ) /* no null check for prop */ + { + if( !cache->all_sync ) + return 0; + if( !stbl->stss && isom_add_stss( stbl ) ) + return -1; + if( isom_add_stss_entry( stbl, 1 ) ) /* Declare here the first sample is a sync sample. */ + return -1; + cache->all_sync = 0; + return 0; + } + if( cache->all_sync ) /* We don't need stss box if all samples are sync sample. */ + return 0; + if( !stbl->stss ) + { + if( isom_get_sample_count( trak ) == 1 ) + { + cache->all_sync = 1; /* Also the first sample is a sync sample. */ + return 0; + } + if( isom_add_stss( stbl ) ) + return -1; + } + return isom_add_stss_entry( stbl, sample_number ); +} + +static int isom_add_partial_sync( isom_trak_entry_t *trak, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + if( !trak->root->qt_compatible ) + return 0; + if( prop->random_access_type != QT_SAMPLE_RANDOM_ACCESS_TYPE_PARTIAL_SYNC + && !(prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_RECOVERY && prop->post_roll.identifier == prop->post_roll.complete) ) + return 0; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stps && isom_add_stps( stbl ) ) + return -1; + return isom_add_stps_entry( stbl, sample_number ); +} + +static int isom_add_dependency_type( isom_trak_entry_t *trak, lsmash_sample_property_t *prop ) +{ + if( !trak->root->qt_compatible && !trak->root->avc_extensions ) + return 0; + uint8_t avc_extensions = trak->root->avc_extensions; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( stbl->sdtp ) + return isom_add_sdtp_entry( stbl, prop, avc_extensions ); + if( !prop->allow_earlier && !prop->leading && !prop->independent && !prop->disposable && !prop->redundant ) /* no null check for prop */ + return 0; + if( isom_add_sdtp( stbl ) ) + return -1; + uint32_t count = isom_get_sample_count( trak ); + /* fill past samples with ISOM_SAMPLE_*_UNKNOWN */ + lsmash_sample_property_t null_prop = { 0 }; + for( uint32_t i = 1; i < count; i++ ) + if( isom_add_sdtp_entry( stbl, &null_prop, avc_extensions ) ) + return -1; + return isom_add_sdtp_entry( stbl, prop, avc_extensions ); +} + +static int isom_rap_grouping_established( isom_rap_group_t *group, int num_leading_samples_known, isom_sgpd_entry_t *sgpd ) +{ + isom_rap_entry_t *rap = group->random_access; + if( !rap ) + return 0; + assert( rap == (isom_rap_entry_t *)sgpd->list->tail->data ); + rap->num_leading_samples_known = num_leading_samples_known; + /* Avoid duplication of sample group descriptions. */ + uint32_t group_description_index = 1; + for( lsmash_entry_t *entry = sgpd->list->head; entry != sgpd->list->tail; entry = entry->next ) + { + isom_rap_entry_t *data = (isom_rap_entry_t *)entry->data; + if( !data ) + return -1; + if( rap->num_leading_samples_known == data->num_leading_samples_known + && rap->num_leading_samples == data->num_leading_samples ) + { + /* The same description already exists. + * Remove the latest random access entry. */ + lsmash_remove_entry_direct( sgpd->list, sgpd->list->tail, NULL ); + /* Replace assigned group_description_index with the one corresponding the same description. */ + if( group->assignment->group_description_index == 0 ) + { + if( group->prev_assignment ) + group->prev_assignment->group_description_index = group_description_index; + } + else + group->assignment->group_description_index = group_description_index; + break; + } + ++group_description_index; + } + group->random_access = NULL; + return 0; +} + +static int isom_group_random_access( isom_trak_entry_t *trak, lsmash_sample_property_t *prop ) +{ + if( trak->root->max_isom_version < 6 ) + return 0; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_sbgp_entry_t *sbgp = isom_get_sample_to_group( stbl, ISOM_GROUP_TYPE_RAP ); + isom_sgpd_entry_t *sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + if( !sbgp || !sgpd ) + return 0; + uint8_t is_rap = prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP + || prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_OPEN_RAP + || prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_UNKNOWN_RAP + || (prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_RECOVERY && prop->post_roll.identifier == prop->post_roll.complete); + isom_rap_group_t *group = trak->cache->rap; + if( !group ) + { + /* This sample is the first sample, create a grouping cache. */ + assert( isom_get_sample_count( trak ) == 1 ); + group = malloc( sizeof(isom_rap_group_t) ); + if( !group ) + return -1; + if( is_rap ) + { + group->random_access = isom_add_rap_group_entry( sgpd ); + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count ); + } + else + { + /* The first sample is not always random access point. */ + group->random_access = NULL; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + } + if( !group->assignment ) + { + free( group ); + return -1; + } + group->prev_assignment = NULL; + group->is_prev_rap = is_rap; + trak->cache->rap = group; + return 0; + } + if( group->is_prev_rap ) + { + /* OK. here, the previous sample is a menber of 'rap '. */ + if( !is_rap ) + { + /* This sample isn't a member of 'rap ' and the previous sample is. + * So we create a new group and set 0 on its group_description_index. */ + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + if( !group->assignment ) + { + free( group ); + return -1; + } + } + else if( prop->random_access_type != ISOM_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP ) + { + /* Create a new group since there is the possibility the next sample is a leading sample. + * This sample is a member of 'rap ', so we set appropriate value on its group_description_index. */ + if( isom_rap_grouping_established( group, 1, sgpd ) ) + return -1; + group->random_access = isom_add_rap_group_entry( sgpd ); + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count ); + if( !group->assignment ) + { + free( group ); + return -1; + } + } + else /* The previous and current sample are a member of 'rap ', and the next sample must not be a leading sample. */ + ++ group->assignment->sample_count; + } + else if( is_rap ) + { + /* This sample is a member of 'rap ' and the previous sample isn't. + * So we create a new group and set appropriate value on its group_description_index. */ + if( isom_rap_grouping_established( group, 1, sgpd ) ) + return -1; + group->random_access = isom_add_rap_group_entry( sgpd ); + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count ); + if( !group->assignment ) + { + free( group ); + return -1; + } + } + else /* The previous and current sample aren't a member of 'rap '. */ + ++ group->assignment->sample_count; + /* Obtain the property of the latest random access point group. */ + if( !is_rap && group->random_access ) + { + if( prop->leading == ISOM_SAMPLE_LEADING_UNKNOWN ) + { + /* We can no longer know num_leading_samples in this group. */ + if( isom_rap_grouping_established( group, 0, sgpd ) ) + return -1; + } + else + { + if( prop->leading == ISOM_SAMPLE_IS_UNDECODABLE_LEADING || prop->leading == ISOM_SAMPLE_IS_DECODABLE_LEADING ) + ++ group->random_access->num_leading_samples; + /* no more consecutive leading samples in this group */ + else if( isom_rap_grouping_established( group, 1, sgpd ) ) + return -1; + } + } + group->is_prev_rap = is_rap; + return 0; +} + +static int isom_roll_grouping_established( isom_roll_group_t *group, int16_t roll_distance, isom_sgpd_entry_t *sgpd ) +{ + /* Avoid duplication of sample group descriptions. */ + uint32_t group_description_index = 1; + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + isom_roll_entry_t *data = (isom_roll_entry_t *)entry->data; + if( !data ) + return -1; + if( roll_distance == data->roll_distance ) + { + /* The same description already exists. + * Set the group_description_index corresponding the same description. */ + group->assignment->group_description_index = group_description_index; + return 0; + } + ++group_description_index; + } + /* Add a new roll recovery description. */ + if( !isom_add_roll_group_entry( sgpd, roll_distance ) ) + return -1; + group->assignment->group_description_index = sgpd->list->entry_count; + return 0; +} + +/* Remove pooled caches that has become unnecessary. */ +static int isom_clean_roll_pool( lsmash_entry_list_t *pool ) +{ + for( lsmash_entry_t *entry = pool->head; entry; entry = pool->head ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return -1; + if( !group->delimited || !group->described ) + break; + if( lsmash_remove_entry_direct( pool, entry, NULL ) ) + return -1; + } + return 0; +} + +static int isom_all_recovery_described( lsmash_entry_list_t *pool ) +{ + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return -1; + group->described = 1; + } + return isom_clean_roll_pool( pool ); +} + +static int isom_group_roll_recovery( isom_trak_entry_t *trak, lsmash_sample_property_t *prop ) +{ + if( !trak->root->avc_extensions && !trak->root->qt_compatible ) + return 0; + if( prop->pre_roll.distance > -INT16_MIN ) + return -1; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_sbgp_entry_t *sbgp = isom_get_sample_to_group( stbl, ISOM_GROUP_TYPE_ROLL ); + isom_sgpd_entry_t *sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_ROLL ); + if( !sbgp || !sgpd ) + return 0; + lsmash_entry_list_t *pool = trak->cache->roll.pool; + if( !pool ) + { + pool = lsmash_create_entry_list(); + if( !pool ) + return -1; + trak->cache->roll.pool = pool; + } + isom_roll_group_t *group = (isom_roll_group_t *)lsmash_get_entry_data( pool, pool->entry_count ); + uint32_t sample_count = isom_get_sample_count( trak ); + if( !group || (prop->pre_roll.distance == 0 && prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_RECOVERY) ) + { + if( group ) + group->delimited = 1; + else + assert( sample_count == 1 ); + /* Create a new group. This group is not 'roll' yet, so we set 0 on its group_description_index. */ + group = lsmash_malloc_zero( sizeof(isom_roll_group_t) ); + if( !group ) + return -1; + if( prop->pre_roll.distance ) + group->described = 1; + else + { + group->first_sample = sample_count; + group->recovery_point = prop->post_roll.complete; + } + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + if( !group->assignment || lsmash_add_entry( pool, group ) ) + { + free( group ); + return -1; + } + if( prop->pre_roll.distance ) + return isom_roll_grouping_established( group, -prop->pre_roll.distance, sgpd ); + } + else + { + /* Check pre-roll distance. */ + isom_roll_entry_t *roll = (isom_roll_entry_t *)lsmash_get_entry_data( sgpd->list, sgpd->list->entry_count ); + int new_group = 0; + if( prop->pre_roll.distance ) + { + if( !roll ) + new_group = 1; /* Create the first pre-roll group. */ + else if( prop->pre_roll.distance != -roll->roll_distance ) + { + /* Pre-roll distance is different from the previous. */ + group->delimited = 1; + group->described = 1; + new_group = 1; + } + } + if( new_group ) + { + /* Create a new pre-roll group. */ + group = lsmash_malloc_zero( sizeof(isom_roll_group_t) ); + if( !group ) + return -1; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + if( !group->assignment || lsmash_add_entry( pool, group ) ) + { + free( group ); + return -1; + } + if( prop->pre_roll.distance && isom_roll_grouping_established( group, -prop->pre_roll.distance, sgpd ) ) + return -1; + return isom_all_recovery_described( pool ); + } + ++ group->assignment->sample_count; + } + /* If encountered a sync sample, all recovery is completed here. */ + if( prop->random_access_type == ISOM_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP ) + return isom_all_recovery_described( pool ); + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + group = (isom_roll_group_t *)entry->data; + if( !group ) + return -1; + if( group->described ) + continue; + if( prop->post_roll.identifier == group->recovery_point ) + { + group->described = 1; + int16_t distance = sample_count - group->first_sample; + /* Add a roll recovery entry only when roll_distance isn't zero since roll_distance = 0 must not be used. */ + if( distance ) + { + /* Now, this group is a 'roll'. */ + if( isom_roll_grouping_established( group, distance, sgpd ) ) + return -1; + /* All groups before the current group are described. */ + lsmash_entry_t *current = entry; + for( entry = pool->head; entry != current; entry = entry->next ) + { + group = (isom_roll_group_t *)entry->data; + if( !group ) + return -1; + group->described = 1; + } + } + break; /* Avoid evaluating groups, in the pool, having the same identifier for recovery point again. */ + } + } + return isom_clean_roll_pool( pool ); +} + +/* returns 1 if pooled samples must be flushed. */ +/* FIXME: I wonder if this function should have a extra argument which indicates force_to_flush_cached_chunk. + see lsmash_append_sample for detail. */ +static int isom_add_chunk( isom_trak_entry_t *trak, lsmash_sample_t *sample ) +{ + if( !trak->root || !trak->cache || !trak->mdia->mdhd || !trak->mdia->mdhd->timescale + || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list ) + return -1; + lsmash_root_t *root = trak->root; + isom_chunk_t *current = &trak->cache->chunk; + if( !current->pool ) + { + /* Very initial settings, just once per track */ + current->pool = isom_create_sample_pool( 0 ); + if( !current->pool ) + return -1; + } + if( !current->pool->sample_count ) + { + /* Cannot decide whether we should flush the current sample or not here yet. */ + ++ current->chunk_number; + current->sample_description_index = sample->index; + current->first_dts = sample->dts; + return 0; + } + if( sample->dts < current->first_dts ) + return -1; /* easy error check. */ + if( (root->max_chunk_duration >= ((double)(sample->dts - current->first_dts) / trak->mdia->mdhd->timescale)) + && (root->max_chunk_size >= current->pool->size + sample->length) + && (current->sample_description_index == sample->index) ) + return 0; /* No need to flush current cached chunk, the current sample must be put into that. */ + /* NOTE: chunk relative stuff must be pushed into root after a chunk is fully determined with its contents. */ + /* now current cached chunk is fixed, actually add chunk relative properties to root accordingly. */ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + lsmash_entry_t *last_stsc_entry = stbl->stsc->list->tail; + /* Create a new chunk sequence in this track if needed. */ + if( (!last_stsc_entry || current->pool->sample_count != ((isom_stsc_entry_t *)last_stsc_entry->data)->samples_per_chunk) + && isom_add_stsc_entry( stbl, current->chunk_number, current->pool->sample_count, current->sample_description_index ) ) + return -1; + /* Add a new chunk offset in this track. */ + uint64_t offset = root->size; + if( root->fragment ) + offset += ISOM_BASEBOX_COMMON_SIZE + root->fragment->pool_size; + if( isom_add_stco_entry( stbl, offset ) ) + return -1; + /* update cache information */ + ++ current->chunk_number; + /* re-initialize cache, using the current sample */ + current->sample_description_index = sample->index; + current->first_dts = sample->dts; + /* current->pool must be flushed in isom_append_sample_internal() */ + return 1; +} + +static int isom_write_pooled_samples( lsmash_root_t *root, isom_sample_pool_t *pool ) +{ + if( !root || !root->mdat || !root->bs || !root->bs->stream ) + return -1; + lsmash_bs_put_bytes( root->bs, pool->data, pool->size ); + if( lsmash_bs_write_data( root->bs ) ) + return -1; + root->mdat->size += pool->size; + root->size += pool->size; + pool->sample_count = 0; + pool->size = 0; + return 0; +} + +static int isom_update_sample_tables( isom_trak_entry_t *trak, lsmash_sample_t *sample ) +{ + /* Add a sample_size and increment sample_count. */ + uint32_t sample_count = isom_add_size( trak, sample->length ); + if( !sample_count ) + return -1; + /* Add a decoding timestamp and a composition timestamp. */ + if( isom_add_timestamp( trak, sample->dts, sample->cts ) ) + return -1; + /* Add a sync point if needed. */ + if( isom_add_sync_point( trak, sample_count, &sample->prop ) ) + return -1; + /* Add a partial sync point if needed. */ + if( isom_add_partial_sync( trak, sample_count, &sample->prop ) ) + return -1; + /* Add leading, independent, disposable and redundant information if needed. */ + if( isom_add_dependency_type( trak, &sample->prop ) ) + return -1; + /* Group samples into random access point type if needed. */ + if( isom_group_random_access( trak, &sample->prop ) ) + return -1; + /* Group samples into random access recovery point type if needed. */ + if( isom_group_roll_recovery( trak, &sample->prop ) ) + return -1; + /* Add a chunk if needed. */ + return isom_add_chunk( trak, sample ); +} + +static int isom_append_fragment_track_run( lsmash_root_t *root, isom_chunk_t *chunk ) +{ + if( !chunk->pool || !chunk->pool->size ) + return 0; + isom_fragment_manager_t *fragment = root->fragment; + /* Move data in the pool of the current track fragment to the pool of the current movie fragment. + * Empty the pool of current track. We don't delete data of samples here. */ + if( lsmash_add_entry( fragment->pool, chunk->pool ) ) + return -1; + fragment->pool->entry_count += chunk->pool->sample_count; + fragment->pool_size += chunk->pool->size; + chunk->pool = isom_create_sample_pool( chunk->pool->size ); + return chunk->pool ? 0 : -1; +} + +static int isom_output_cached_chunk( isom_trak_entry_t *trak ) +{ + lsmash_root_t *root = trak->root; + isom_chunk_t *chunk = &trak->cache->chunk; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + lsmash_entry_t *last_stsc_entry = stbl->stsc->list->tail; + /* Create a new chunk sequence in this track if needed. */ + if( (!last_stsc_entry || chunk->pool->sample_count != ((isom_stsc_entry_t *)last_stsc_entry->data)->samples_per_chunk) + && isom_add_stsc_entry( stbl, chunk->chunk_number, chunk->pool->sample_count, chunk->sample_description_index ) ) + return -1; + if( root->fragment ) + { + /* Add a new chunk offset in this track. */ + if( isom_add_stco_entry( stbl, root->size + ISOM_BASEBOX_COMMON_SIZE + root->fragment->pool_size ) ) + return -1; + return isom_append_fragment_track_run( root, chunk ); + } + /* Add a new chunk offset in this track. */ + if( isom_add_stco_entry( stbl, root->size ) ) + return -1; + /* Output pooled samples in this track. */ + return isom_write_pooled_samples( root, chunk->pool ); +} + +static int isom_pool_sample( isom_sample_pool_t *pool, lsmash_sample_t *sample ) +{ + uint64_t pool_size = pool->size + sample->length; + if( pool->alloc < pool_size ) + { + uint8_t *data; + uint64_t alloc = pool_size + (1<<16); + if( !pool->data ) + data = malloc( alloc ); + else + data = realloc( pool->data, alloc ); + if( !data ) + return -1; + pool->data = data; + pool->alloc = alloc; + } + memcpy( pool->data + pool->size, sample->data, sample->length ); + pool->size = pool_size; + pool->sample_count += 1; + lsmash_delete_sample( sample ); + return 0; +} + +static int isom_append_sample_internal( isom_trak_entry_t *trak, lsmash_sample_t *sample ) +{ + int flush = isom_update_sample_tables( trak, sample ); + if( flush < 0 ) + return -1; + /* flush == 1 means pooled samples must be flushed. */ + lsmash_root_t *root = trak->root; + isom_sample_pool_t *current_pool = trak->cache->chunk.pool; + if( flush == 1 && isom_write_pooled_samples( root, current_pool ) ) + return -1; + /* Arbitration system between tracks with extremely scattering dts. + * Here, we check whether asynchronization between the tracks exceeds the tolerance. + * If a track has too old "first DTS" in its cached chunk than current sample's DTS, then its pooled samples must be flushed. + * We don't consider presentation of media since any edit can pick an arbitrary portion of media in track. + * Note: you needn't read this loop until you grasp the basic handling of chunks. */ + double tolerance = root->max_async_tolerance; + for( lsmash_entry_t *entry = root->moov->trak_list->head; entry; entry = entry->next ) + { + isom_trak_entry_t *other = (isom_trak_entry_t *)entry->data; + if( trak == other ) + continue; + if( !other || !other->cache || !other->mdia || !other->mdia->mdhd || !other->mdia->mdhd->timescale + || !other->mdia->minf || !other->mdia->minf->stbl || !other->mdia->minf->stbl->stsc || !other->mdia->minf->stbl->stsc->list ) + return -1; + isom_chunk_t *chunk = &other->cache->chunk; + if( !chunk->pool || !chunk->pool->sample_count ) + continue; + double diff = ((double)sample->dts / trak->mdia->mdhd->timescale) + - ((double)chunk->first_dts / other->mdia->mdhd->timescale); + if( diff > tolerance && isom_output_cached_chunk( other ) ) + return -1; + /* Note: we don't flush the cached chunk in the current track and the current sample here + * even if the conditional expression of '-diff > tolerance' meets. + * That's useless because appending a sample to another track would be a good equivalent. + * It's even harmful because it causes excess chunk division by calling + * isom_output_cached_chunk() which always generates a new chunk. + * Anyway some excess chunk division will be there, but rather less without it. + * To completely avoid this, we need to observe at least whether the current sample will be placed + * right next to the previous chunk of the same track or not. */ + } + /* anyway the current sample must be pooled. */ + return isom_pool_sample( current_pool, sample ); +} + +static int isom_append_sample( lsmash_root_t *root, uint32_t track_ID, lsmash_sample_t *sample ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->root || !trak->cache || !trak->mdia + || !trak->mdia->mdhd || !trak->mdia->mdhd->timescale + || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list + || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list ) + return -1; + /* If there is no available Media Data Box to write samples, add and write a new one before any chunk offset is decided. */ + if( !root->mdat ) + { + if( isom_new_mdat( root, 0 ) ) + return -1; + /* Add the size of the Media Data Box and the placeholder. */ + root->size += 2 * ISOM_BASEBOX_COMMON_SIZE; + } + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, sample->index ); + if( !sample_entry ) + return -1; + if( isom_is_lpcm_audio( sample_entry->type ) ) + { + uint32_t frame_size = ((isom_audio_entry_t *)sample_entry)->constBytesPerAudioPacket; + if( sample->length == frame_size ) + return isom_append_sample_internal( trak, sample ); + else if( sample->length < frame_size ) + return -1; + /* Append samples splitted into each LPCMFrame. */ + uint64_t dts = sample->dts; + uint64_t cts = sample->cts; + for( uint32_t offset = 0; offset < sample->length; offset += frame_size ) + { + lsmash_sample_t *lpcm_sample = lsmash_create_sample( frame_size ); + if( !lpcm_sample ) + return -1; + memcpy( lpcm_sample->data, sample->data + offset, frame_size ); + lpcm_sample->dts = dts++; + lpcm_sample->cts = cts++; + lpcm_sample->prop = sample->prop; + lpcm_sample->index = sample->index; + if( isom_append_sample_internal( trak, lpcm_sample ) ) + { + lsmash_delete_sample( lpcm_sample ); + return -1; + } + } + lsmash_delete_sample( sample ); + return 0; + } + return isom_append_sample_internal( trak, sample ); +} + +static int isom_output_cache( isom_trak_entry_t *trak ) +{ + if( trak->cache->chunk.pool && trak->cache->chunk.pool->sample_count + && isom_output_cached_chunk( trak ) ) + return -1; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->sgpd_list ) + return 0; + for( lsmash_entry_t *entry = stbl->sgpd_list->head; entry; entry = entry->next ) + { + isom_sgpd_entry_t *sgpd = (isom_sgpd_entry_t *)entry->data; + if( !sgpd ) + return -1; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + isom_rap_group_t *group = trak->cache->rap; + if( !group ) + { + if( trak->root->fragment ) + continue; + else + return -1; + } + if( !group->random_access ) + continue; + group->random_access->num_leading_samples_known = 1; + break; + } + case ISOM_GROUP_TYPE_ROLL : + if( !trak->cache->roll.pool ) + { + if( trak->root->fragment ) + continue; + else + return -1; + } + for( lsmash_entry_t *roll_entry = trak->cache->roll.pool->head; roll_entry; roll_entry = roll_entry->next ) + { + isom_roll_group_t *group = (isom_roll_group_t *)roll_entry->data; + if( !group ) + return -1; + group->described = 1; + } + break; + default : + break; + } + } + return 0; +} + +static int isom_flush_fragment_pooled_samples( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_duration ) +{ + isom_traf_entry_t *traf = isom_get_traf( root->fragment->movie, track_ID ); + if( !traf ) + return 0; /* no samples */ + if( !traf->cache || !traf->cache->fragment ) + return -1; + if( traf->trun_list && traf->trun_list->entry_count && traf->trun_list->tail && traf->trun_list->tail->data ) + { + /* Media Data Box preceded by Movie Fragment Box could change base_data_offsets in each track fragments later. + * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */ + isom_trun_entry_t *trun = (isom_trun_entry_t *)traf->trun_list->tail->data; + if( root->fragment->pool_size ) + trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT; + trun->data_offset = root->fragment->pool_size; + } + if( isom_append_fragment_track_run( root, &traf->cache->chunk ) ) + return -1; + return isom_set_fragment_last_duration( traf, last_sample_duration ); +} + +int lsmash_flush_pooled_samples( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ) +{ + if( !root ) + return -1; + if( root->fragment && root->fragment->movie ) + return isom_flush_fragment_pooled_samples( root, track_ID, last_sample_delta ); + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->cache || !trak->mdia || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list ) + return -1; + if( isom_output_cache( trak ) ) + return -1; + return lsmash_set_last_sample_delta( root, track_ID, last_sample_delta ); +} + +/* This function doesn't update sample_duration of the last sample in the previous movie fragment. + * Instead of this, isom_finish_movie_fragment undertakes this task. */ +static int isom_update_fragment_previous_sample_duration( isom_traf_entry_t *traf, isom_trex_entry_t *trex, uint32_t duration ) +{ + isom_tfhd_t *tfhd = traf->tfhd; + isom_trun_entry_t *trun = (isom_trun_entry_t *)traf->trun_list->tail->data; + int previous_run_has_previous_sample = 0; + if( trun->sample_count == 1 ) + { + if( traf->trun_list->entry_count == 1 ) + return 0; /* The previous track run belongs to the previous movie fragment if it exists. */ + if( !traf->trun_list->tail->prev || !traf->trun_list->tail->prev->data ) + return -1; + /* OK. The previous sample exists in the previous track run in the same track fragment. */ + trun = (isom_trun_entry_t *)traf->trun_list->tail->prev->data; + previous_run_has_previous_sample = 1; + } + /* Update default_sample_duration of the Track Fragment Header Box + * if this duration is what the first sample in the current track fragment owns. */ + if( (trun->sample_count == 2 && traf->trun_list->entry_count == 1) + || (trun->sample_count == 1 && traf->trun_list->entry_count == 2) ) + { + if( duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = duration; + } + /* Update the previous sample_duration if needed. */ + if( duration != tfhd->default_sample_duration ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT; + if( trun->flags ) + { + uint32_t sample_number = trun->sample_count - !previous_run_has_previous_sample; + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, sample_number ); + if( !row ) + return -1; + row->sample_duration = duration; + } + traf->cache->fragment->last_duration = duration; + return 0; +} + +static isom_sample_flags_t isom_generate_fragment_sample_flags( lsmash_sample_t *sample ) +{ + isom_sample_flags_t flags; + flags.reserved = 0; + flags.is_leading = sample->prop.leading & 0x3; + flags.sample_depends_on = sample->prop.independent & 0x3; + flags.sample_is_depended_on = sample->prop.disposable & 0x3; + flags.sample_has_redundancy = sample->prop.redundant & 0x3; + flags.sample_padding_value = 0; + flags.sample_is_non_sync_sample = sample->prop.random_access_type != ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC; + flags.sample_degradation_priority = 0; + return flags; +} + +static int isom_update_fragment_sample_tables( isom_traf_entry_t *traf, lsmash_sample_t *sample ) +{ + isom_tfhd_t *tfhd = traf->tfhd; + isom_trex_entry_t *trex = isom_get_trex( traf->root->moov->mvex, tfhd->track_ID ); + if( !trex ) + return -1; + lsmash_root_t *root = traf->root; + isom_cache_t *cache = traf->cache; + isom_chunk_t *current = &cache->chunk; + /* Create a new track run if the duration exceeds max_chunk_duration. + * Old one will be appended to the pool of this movie fragment. */ + int delimit = (root->max_chunk_duration < ((double)(sample->dts - current->first_dts) / lsmash_get_media_timescale( root, tfhd->track_ID ))) + || (root->max_chunk_size < (current->pool->size + sample->length)); + isom_trun_entry_t *trun = NULL; + if( !traf->trun_list || !traf->trun_list->entry_count || delimit ) + { + if( delimit && traf->trun_list && traf->trun_list->entry_count && traf->trun_list->tail && traf->trun_list->tail->data ) + { + /* Media Data Box preceded by Movie Fragment Box could change base data offsets in each track fragments later. + * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */ + trun = (isom_trun_entry_t *)traf->trun_list->tail->data; + if( root->fragment->pool_size ) + trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT; + trun->data_offset = root->fragment->pool_size; + } + trun = isom_add_trun( traf ); + if( !trun ) + return -1; + if( !current->pool ) + { + /* Very initial settings, just once per track */ + current->pool = isom_create_sample_pool( 0 ); + if( !current->pool ) + return -1; + } + } + else + { + if( !traf->trun_list->tail || !traf->trun_list->tail->data ) + return -1; + trun = (isom_trun_entry_t *)traf->trun_list->tail->data; + } + uint32_t sample_composition_time_offset = sample->cts - sample->dts; + isom_sample_flags_t sample_flags = isom_generate_fragment_sample_flags( sample ); + if( ++trun->sample_count == 1 ) + { + if( traf->trun_list->entry_count == 1 ) + { + /* This track fragment isn't empty-duration-fragment any more. */ + tfhd->flags &= ~ISOM_TF_FLAGS_DURATION_IS_EMPTY; + /* Set up sample_description_index in this track fragment. */ + if( sample->index != trex->default_sample_description_index ) + tfhd->flags |= ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT; + tfhd->sample_description_index = current->sample_description_index = sample->index; + /* Set up default_sample_size used in this track fragment. */ + tfhd->default_sample_size = sample->length; + /* Set up default_sample_flags used in this track fragment. + * Note: we decide an appropriate default value at the end of this movie fragment. */ + tfhd->default_sample_flags = sample_flags; + /* Set up random access information if this sample is random accessible sample. + * We inform only the first sample in each movie fragment. */ + if( root->bs->stream != stdout && sample->prop.random_access_type ) + { + isom_tfra_entry_t *tfra = isom_get_tfra( root->mfra, tfhd->track_ID ); + if( !tfra ) + { + tfra = isom_add_tfra( root->mfra ); + if( !tfra ) + return -1; + tfra->track_ID = tfhd->track_ID; + } + if( !tfra->list ) + { + tfra->list = lsmash_create_entry_list(); + if( !tfra->list ) + return -1; + } + isom_tfra_location_time_entry_t *rap = malloc( sizeof(isom_tfra_location_time_entry_t) ); + if( !rap ) + return -1; + rap->time = sample->cts; /* If this is wrong, blame vague descriptions of 'presentation time' in the spec. */ + rap->moof_offset = root->size; /* We place Movie Fragment Box in the head of each movie fragment. */ + rap->traf_number = cache->fragment->traf_number; + rap->trun_number = traf->trun_list->entry_count; + rap->sample_number = trun->sample_count; + if( lsmash_add_entry( tfra->list, rap ) ) + return -1; + tfra->number_of_entry = tfra->list->entry_count; + int length; + for( length = 1; rap->traf_number >> (length * 8); length++ ); + tfra->length_size_of_traf_num = LSMASH_MAX( length - 1, tfra->length_size_of_traf_num ); + for( length = 1; rap->traf_number >> (length * 8); length++ ); + tfra->length_size_of_trun_num = LSMASH_MAX( length - 1, tfra->length_size_of_trun_num ); + for( length = 1; rap->sample_number >> (length * 8); length++ ); + tfra->length_size_of_sample_num = LSMASH_MAX( length - 1, tfra->length_size_of_sample_num ); + } + } + trun->first_sample_flags = sample_flags; + current->first_dts = sample->dts; + } + /* Update the optional rows in the current track run except for sample_duration if needed. */ + if( sample->length != tfhd->default_sample_size ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT; + if( isom_compare_sample_flags( &sample_flags, &tfhd->default_sample_flags ) ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + if( sample_composition_time_offset ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT; + if( trun->flags ) + { + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count ); + if( !row ) + return -1; + row->sample_size = sample->length; + row->sample_flags = sample_flags; + row->sample_composition_time_offset = sample_composition_time_offset; + } + /* Set up the previous sample_duration if this sample is not the first sample in the overall movie. */ + if( cache->fragment->has_samples ) + { + /* Note: when using for live streaming, it is not good idea to return error (-1) by sample->dts < prev_dts + * since that's trivial for such semi-permanent presentation. */ + uint64_t prev_dts = cache->timestamp.dts; + if( sample->dts <= prev_dts || sample->dts > prev_dts + UINT32_MAX ) + return -1; + uint32_t sample_duration = sample->dts - prev_dts; + if( isom_update_fragment_previous_sample_duration( traf, trex, sample_duration ) ) + return -1; + } + cache->timestamp.dts = sample->dts; + cache->fragment->largest_cts = LSMASH_MAX( sample->cts, cache->fragment->largest_cts ); + return delimit; +} + +static int isom_append_fragment_sample_internal_initial( isom_trak_entry_t *trak, lsmash_sample_t *sample ) +{ + int delimit = 0; + /* Update the sample tables of this track fragment. + * If a new chunk was created, append the previous one to the pool of this movie fragment. */ + delimit = isom_update_sample_tables( trak, sample ); + if( delimit < 0 ) + return -1; + else if( delimit == 1 ) + isom_append_fragment_track_run( trak->root, &trak->cache->chunk ); + /* Add a new sample into the pool of this track fragment. */ + if( isom_pool_sample( trak->cache->chunk.pool, sample ) ) + return -1; + trak->cache->fragment->has_samples = 1; + return 0; +} + +static int isom_append_fragment_sample_internal( isom_traf_entry_t *traf, lsmash_sample_t *sample ) +{ + int delimit = 0; + /* Update the sample tables of this track fragment. + * If a new track run was created, append the previous one to the pool of this movie fragment. */ + delimit = isom_update_fragment_sample_tables( traf, sample ); + if( delimit < 0 ) + return -1; + else if( delimit == 1 ) + isom_append_fragment_track_run( traf->root, &traf->cache->chunk ); + /* Add a new sample into the pool of this track fragment. */ + if( isom_pool_sample( traf->cache->chunk.pool, sample ) ) + return -1; + traf->cache->fragment->has_samples = 1; + return 0; +} + +static int isom_append_fragment_sample( lsmash_root_t *root, uint32_t track_ID, lsmash_sample_t *sample ) +{ + isom_fragment_manager_t *fragment = root->fragment; + if( !fragment || !fragment->pool ) + return -1; + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || !trak->root || !trak->cache || !trak->cache->fragment || !trak->tkhd || !trak->mdia + || !trak->mdia->mdhd || !trak->mdia->mdhd->timescale + || !trak->mdia->minf || !trak->mdia->minf->stbl + || !trak->mdia->minf->stbl->stsd || !trak->mdia->minf->stbl->stsd->list + || !trak->mdia->minf->stbl->stsc || !trak->mdia->minf->stbl->stsc->list ) + return -1; + int (*append_sample_func)( void *, lsmash_sample_t * ) = NULL; + void *track_fragment = NULL; + if( !fragment->movie ) + { + append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal_initial; + track_fragment = trak; + } + else + { + isom_traf_entry_t *traf = isom_get_traf( fragment->movie, track_ID ); + if( !traf ) + { + traf = isom_add_traf( root, fragment->movie ); + if( isom_add_tfhd( traf ) ) + return -1; + traf->tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */ + traf->tfhd->track_ID = trak->tkhd->track_ID; + traf->cache = trak->cache; + traf->cache->fragment->traf_number = fragment->movie->traf_list->entry_count; + } + else if( !traf->root || !traf->root->moov || !traf->root->moov->mvex || !traf->cache || !traf->tfhd ) + return -1; + append_sample_func = (int (*)( void *, lsmash_sample_t * ))isom_append_fragment_sample_internal; + track_fragment = traf; + } + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->stsd->list, sample->index ); + if( !sample_entry ) + return -1; + if( isom_is_lpcm_audio( sample_entry->type ) ) + { + uint32_t frame_size = ((isom_audio_entry_t *)sample_entry)->constBytesPerAudioPacket; + if( sample->length == frame_size ) + return append_sample_func( track_fragment, sample ); + else if( sample->length < frame_size ) + return -1; + /* Append samples splitted into each LPCMFrame. */ + uint64_t dts = sample->dts; + uint64_t cts = sample->cts; + for( uint32_t offset = 0; offset < sample->length; offset += frame_size ) + { + lsmash_sample_t *lpcm_sample = lsmash_create_sample( frame_size ); + if( !lpcm_sample ) + return -1; + memcpy( lpcm_sample->data, sample->data + offset, frame_size ); + lpcm_sample->dts = dts++; + lpcm_sample->cts = cts++; + lpcm_sample->prop = sample->prop; + lpcm_sample->index = sample->index; + if( append_sample_func( track_fragment, lpcm_sample ) ) + { + lsmash_delete_sample( lpcm_sample ); + return -1; + } + } + lsmash_delete_sample( sample ); + return 0; + } + return append_sample_func( track_fragment, sample ); +} + +int lsmash_append_sample( lsmash_root_t *root, uint32_t track_ID, lsmash_sample_t *sample ) +{ + /* We think max_chunk_duration == 0, which means all samples will be cached on memory, should be prevented. + * This means removal of a feature that we used to have, but anyway very alone chunk does not make sense. */ + if( !root || !root->bs || !sample || !sample->data || !track_ID + || root->max_chunk_duration == 0 || root->max_async_tolerance == 0 ) + return -1; + /* Write File Type Box here if it was not written yet. */ + if( !root->file_type_written && isom_write_ftyp( root ) ) + return -1; + if( root->fragment && root->fragment->pool ) + return isom_append_fragment_sample( root, track_ID, sample ); + return isom_append_sample( root, track_ID, sample ); +} + +/*---- misc functions ----*/ + +int lsmash_delete_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak ) + return -1; + isom_remove_edts( trak->edts ); + trak->edts = NULL; + return isom_update_tkhd_duration( trak ); +} + +void lsmash_delete_tyrant_chapter( lsmash_root_t *root ) +{ + if( !root || !root->moov || !root->moov->udta ) + return; + isom_remove_chpl( root->moov->udta->chpl ); + root->moov->udta->chpl = NULL; +} + +int lsmash_set_copyright( lsmash_root_t *root, uint32_t track_ID, uint16_t ISO_language, char *notice ) +{ + if( !root || !root->moov || !root->isom_compatible || (ISO_language && ISO_language < 0x800) || !notice ) + return -1; + isom_udta_t *udta; + if( track_ID ) + { + isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); + if( !trak || (!trak->udta && isom_add_udta( root, track_ID )) ) + return -1; + udta = trak->udta; + } + else + { + if( !root->moov->udta && isom_add_udta( root, 0 ) ) + return -1; + udta = root->moov->udta; + } + assert( udta ); + if( udta->cprt_list ) + for( lsmash_entry_t *entry = udta->cprt_list->head; entry; entry = entry->next ) + { + isom_cprt_t *cprt = (isom_cprt_t *)entry->data; + if( !cprt || cprt->language == ISO_language ) + return -1; + } + if( isom_add_cprt( udta ) ) + return -1; + isom_cprt_t *cprt = (isom_cprt_t *)udta->cprt_list->tail->data; + cprt->language = ISO_language; + cprt->notice_length = strlen( notice ) + 1; + cprt->notice = lsmash_memdup( notice, cprt->notice_length ); + return 0; +} + +static isom_data_t *isom_add_metadata( lsmash_root_t *root, uint32_t type, char *meaning_string, char *name_string ) +{ + assert( root && root->moov ); + if( ((type == ITUNES_METADATA_TYPE_CUSTOM) && (!meaning_string || !meaning_string[0]) ) + || (!root->moov->udta && isom_add_udta( root, 0 )) + || (!root->moov->udta->meta && isom_add_meta( (isom_box_t *)root->moov->udta )) + || (!root->moov->udta->meta->hdlr && isom_add_hdlr( NULL, root->moov->udta->meta, NULL, ISOM_META_HANDLER_TYPE_ITUNES_METADATA )) + || (!root->moov->udta->meta->ilst && isom_add_ilst( root->moov )) ) + return NULL; + isom_ilst_t *ilst = root->moov->udta->meta->ilst; + if( isom_add_metaitem( ilst, type ) ) + return NULL; + isom_metaitem_t *metaitem = (isom_metaitem_t *)ilst->item_list->tail->data; + if( type == ITUNES_METADATA_TYPE_CUSTOM ) + { + if( isom_add_mean( metaitem ) ) + goto fail; + isom_mean_t *mean = metaitem->mean; + mean->meaning_string_length = strlen( meaning_string ); /* No null terminator */ + mean->meaning_string = lsmash_memdup( meaning_string, mean->meaning_string_length ); + if( !mean->meaning_string ) + goto fail; + if( name_string && name_string[0] ) + { + if( isom_add_name( metaitem ) ) + goto fail; + isom_name_t *name = metaitem->name; + name->name_length = strlen( name_string ); /* No null terminator */ + name->name = lsmash_memdup( name_string, name->name_length ); + if( !name->name ) + goto fail; + } + } + if( isom_add_data( metaitem ) ) + goto fail; + return metaitem->data; +fail: + lsmash_remove_entry_direct( ilst->item_list, ilst->item_list->tail, isom_remove_metaitem ); + return NULL; +} + +int lsmash_set_itunes_metadata_string( lsmash_root_t *root, lsmash_itunes_metadata_type type, char *value, char *meaning, char *name ) +{ + static const lsmash_itunes_metadata_type metadata_type_table[] = + { + ITUNES_METADATA_TYPE_ALBUM_NAME, + ITUNES_METADATA_TYPE_ARTIST, + ITUNES_METADATA_TYPE_USER_COMMENT, + ITUNES_METADATA_TYPE_RELEASE_DATE, + ITUNES_METADATA_TYPE_ENCODED_BY, + ITUNES_METADATA_TYPE_USER_GENRE, + ITUNES_METADATA_TYPE_0XA9_GROUPING, + ITUNES_METADATA_TYPE_LYRICS, + ITUNES_METADATA_TYPE_TITLE, + ITUNES_METADATA_TYPE_TRACK_SUBTITLE, + ITUNES_METADATA_TYPE_ENCODING_TOOL, + ITUNES_METADATA_TYPE_COMPOSER, + ITUNES_METADATA_TYPE_ALBUM_ARTIST, + ITUNES_METADATA_TYPE_PODCAST_CATEGORY, + ITUNES_METADATA_TYPE_COPYRIGHT, + ITUNES_METADATA_TYPE_DESCRIPTION, + ITUNES_METADATA_TYPE_GROUPING, + ITUNES_METADATA_TYPE_PODCAST_KEYWORD, + ITUNES_METADATA_TYPE_LONG_DESCRIPTION, + ITUNES_METADATA_TYPE_PURCHASE_DATE, + ITUNES_METADATA_TYPE_TV_EPISODE_ID, + ITUNES_METADATA_TYPE_TV_NETWORK, + ITUNES_METADATA_TYPE_TV_SHOW_NAME, + ITUNES_METADATA_TYPE_ITUNES_PURCHASE_ACCOUNT_ID, + ITUNES_METADATA_TYPE_CUSTOM, + 0 + }; + int i; + for( i = 0; metadata_type_table[i]; i++ ) + if( type == metadata_type_table[i] ) + break; + if( !metadata_type_table[i] ) + return -1; + uint32_t value_length = strlen( value ); + if( type == ITUNES_METADATA_TYPE_DESCRIPTION && value_length > 255 ) + type = ITUNES_METADATA_TYPE_LONG_DESCRIPTION; + isom_data_t *data = isom_add_metadata( root, type, meaning, name ); + if( !data ) + return -1; + data->type_code = 1; + data->value_length = value_length; /* No null terminator */ + data->value = lsmash_memdup( value, data->value_length ); + if( !data->value ) + { + isom_ilst_t *ilst = root->moov->udta->meta->ilst; + lsmash_remove_entry_direct( ilst->item_list, ilst->item_list->tail, isom_remove_metaitem ); + return -1; + } + return 0; +} + +int lsmash_set_itunes_metadata_integer( lsmash_root_t *root, lsmash_itunes_metadata_type type, uint64_t value, char *meaning, char *name ) +{ + static const struct + { + lsmash_itunes_metadata_type type; + int length; + } metadata_code_type_table[] = + { + { ITUNES_METADATA_TYPE_EPISODE_GLOBAL_ID, 1 }, + { ITUNES_METADATA_TYPE_PREDEFINED_GENRE, 4 }, + { ITUNES_METADATA_TYPE_CONTENT_RATING, 1 }, + { ITUNES_METADATA_TYPE_MEDIA_TYPE, 1 }, + { ITUNES_METADATA_TYPE_BEATS_PER_MINUTE, 2 }, + { ITUNES_METADATA_TYPE_TV_EPISODE, 4 }, + { ITUNES_METADATA_TYPE_TV_SEASON, 4 }, + { ITUNES_METADATA_TYPE_ITUNES_ACCOUNT_TYPE, 1 }, + { ITUNES_METADATA_TYPE_ITUNES_ARTIST_ID, 4 }, + { ITUNES_METADATA_TYPE_ITUNES_COMPOSER_ID, 4 }, + { ITUNES_METADATA_TYPE_ITUNES_CATALOG_ID, 4 }, + { ITUNES_METADATA_TYPE_ITUNES_TV_GENRE_ID, 4 }, + { ITUNES_METADATA_TYPE_ITUNES_PLAYLIST_ID, 8 }, + { ITUNES_METADATA_TYPE_ITUNES_COUNTRY_CODE, 4 }, + { ITUNES_METADATA_TYPE_CUSTOM, 8 }, + { 0 } + }; + int i; + for( i = 0; metadata_code_type_table[i].type; i++ ) + if( type == metadata_code_type_table[i].type ) + break; + if( !metadata_code_type_table[i].type ) + return -1; + isom_data_t *data = isom_add_metadata( root, type, meaning, name ); + if( !data ) + return -1; + data->type_code = 21; + data->value_length = metadata_code_type_table[i].length; + uint8_t temp[8]; + for( i = 0; i < data->value_length; i++ ) + { + int shift = (data->value_length - i - 1) * 8; + temp[i] = (value >> shift) & 0xff; + } + data->value = lsmash_memdup( temp, data->value_length ); + if( !data->value ) + { + isom_ilst_t *ilst = root->moov->udta->meta->ilst; + lsmash_remove_entry_direct( ilst->item_list, ilst->item_list->tail, isom_remove_metaitem ); + return -1; + } + return 0; +} + +int lsmash_set_itunes_metadata_boolean( lsmash_root_t *root, lsmash_itunes_metadata_type type, lsmash_boolean_t value, char *meaning, char *name ) +{ + static const lsmash_itunes_metadata_type metadata_type_table[] = + { + ITUNES_METADATA_TYPE_DISC_COMPILATION, + ITUNES_METADATA_TYPE_HIGH_DEFINITION_VIDEO, + ITUNES_METADATA_TYPE_PODCAST, + ITUNES_METADATA_TYPE_GAPLESS_PLAYBACK, + ITUNES_METADATA_TYPE_CUSTOM, + 0 + }; + int i; + for( i = 0; metadata_type_table[i]; i++ ) + if( type == metadata_type_table[i] ) + break; + if( !metadata_type_table[i] ) + return -1; + isom_data_t *data = isom_add_metadata( root, type, meaning, name ); + if( !data ) + return -1; + data->type_code = 21; + data->value_length = 1; + uint8_t temp = (uint8_t)value; + data->value = lsmash_memdup( &temp, 1 ); + if( !data->value ) + { + isom_ilst_t *ilst = root->moov->udta->meta->ilst; + lsmash_remove_entry_direct( ilst->item_list, ilst->item_list->tail, isom_remove_metaitem ); + return -1; + } + return 0; +} + +#ifdef LSMASH_DEMUXER_ENABLED +/*---- remuxer functions ----*/ + +static int isom_copy_mean( isom_metaitem_t *dst, isom_metaitem_t *src ) +{ + if( !dst ) + return 0; + isom_remove_mean( dst->mean ); + if( !src || !src->mean ) + return 0; + if( isom_add_mean( dst ) ) + return -1; + if( src->mean->meaning_string ) + { + dst->mean->meaning_string = lsmash_memdup( src->mean->meaning_string, src->mean->meaning_string_length ); + if( !dst->mean->meaning_string ) + return -1; + dst->mean->meaning_string_length = src->mean->meaning_string_length; + } + return 0; +} + +static int isom_copy_name( isom_metaitem_t *dst, isom_metaitem_t *src ) +{ + if( !dst ) + return 0; + isom_remove_name( dst->name ); + if( !src || !src->name ) + return 0; + if( isom_add_name( dst ) ) + return -1; + if( src->name->name ) + { + dst->name->name = lsmash_memdup( src->name->name, src->name->name_length ); + if( !dst->name->name ) + return -1; + dst->name->name_length = src->name->name_length; + } + return 0; +} + +static int isom_copy_data( isom_metaitem_t *dst, isom_metaitem_t *src ) +{ + if( !dst ) + return 0; + isom_remove_data( dst->data ); + if( !src || !src->data ) + return 0; + if( isom_add_data( dst ) ) + return -1; + isom_copy_fields( dst, src, data ); + if( src->data->value ) + { + dst->data->value = lsmash_memdup( src->data->value, src->data->value_length ); + if( !dst->data->value ) + return -1; + dst->data->value_length = src->data->value_length; + } + return 0; +} + +static isom_metaitem_t *isom_duplicate_metaitem( isom_metaitem_t *src ) +{ + isom_metaitem_t *dst = lsmash_memdup( src, sizeof(isom_metaitem_t) ); + if( !dst ) + return NULL; + dst->mean = NULL; + dst->name = NULL; + dst->data = NULL; + /* Copy children. */ + if( isom_copy_mean( dst, src ) + || isom_copy_name( dst, src ) + || isom_copy_data( dst, src ) ) + { + isom_remove_metaitem( dst ); + return NULL; + } + return dst; +} + +lsmash_itunes_metadata_list_t *lsmash_export_itunes_metadata( lsmash_root_t *root ) +{ + if( !root || !root->moov ) + return NULL; + if( !root->moov->udta || !root->moov->udta->meta || !root->moov->udta->meta->ilst ) + { + isom_ilst_t *dst = lsmash_malloc_zero( sizeof(isom_ilst_t) ); + if( !dst ) + return NULL; + return (lsmash_itunes_metadata_list_t *)dst; + } + isom_ilst_t *src = root->moov->udta->meta->ilst; + isom_ilst_t *dst = lsmash_memdup( src, sizeof(isom_ilst_t) ); + if( !dst ) + return NULL; + dst->root = NULL; + dst->parent = NULL; + dst->item_list = NULL; + if( src->item_list ) + { + dst->item_list = lsmash_create_entry_list(); + if( !dst->item_list ) + { + free( dst ); + return NULL; + } + for( lsmash_entry_t *entry = src->item_list->head; entry; entry = entry->next ) + { + isom_metaitem_t *dst_metaitem = isom_duplicate_metaitem( (isom_metaitem_t *)entry->data ); + if( !dst_metaitem ) + { + isom_remove_ilst( dst ); + return NULL; + } + if( lsmash_add_entry( dst->item_list, dst_metaitem ) ) + { + isom_remove_metaitem( dst_metaitem ); + isom_remove_ilst( dst ); + return NULL; + } + } + } + return (lsmash_itunes_metadata_list_t *)dst; +} + +int lsmash_import_itunes_metadata( lsmash_root_t *root, lsmash_itunes_metadata_list_t *list ) +{ + if( !root || !list ) + return -1; + if( !root->itunes_movie ) + return 0; + isom_ilst_t *src = (isom_ilst_t *)list; + if( !src->item_list || !src->item_list->entry_count ) + return 0; + if( (!root->moov->udta && isom_add_udta( root, 0 )) + || (!root->moov->udta->meta && isom_add_meta( (isom_box_t *)root->moov->udta )) + || (!root->moov->udta->meta->hdlr && isom_add_hdlr( NULL, root->moov->udta->meta, NULL, ISOM_META_HANDLER_TYPE_ITUNES_METADATA )) + || (!root->moov->udta->meta->ilst && isom_add_ilst( root->moov )) ) + return -1; + isom_ilst_t *dst = root->moov->udta->meta->ilst; + for( lsmash_entry_t *entry = src->item_list->head; entry; entry = entry->next ) + { + isom_metaitem_t *dst_metaitem = isom_duplicate_metaitem( (isom_metaitem_t *)entry->data ); + if( !dst_metaitem ) + return -1; + if( lsmash_add_entry( dst->item_list, dst_metaitem ) ) + { + isom_remove_metaitem( dst_metaitem ); + return -1; + } + } + return 0; +} + +void lsmash_destroy_itunes_metadata( lsmash_itunes_metadata_list_t *list ) +{ + isom_remove_ilst( (isom_ilst_t *)list ); +} +#endif /* LSMASH_DEMUXER_ENABLED */ diff --git a/output/mp4/isom.h b/output/mp4/isom.h new file mode 100644 index 0000000..83d622c --- /dev/null +++ b/output/mp4/isom.h @@ -0,0 +1,35 @@ +/***************************************************************************** + * isom.h: + ***************************************************************************** + * Copyright (C) 2011 L-SMASH project + * + * Authors: Hiroki Taniura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_ISOM_H +#define LSMASH_ISOM_H + +isom_tref_type_t *isom_add_track_reference_type( isom_tref_t *tref, isom_track_reference_type type, uint32_t ref_count, uint32_t *track_ID ); +int isom_add_chpl_entry( isom_chpl_t *chpl, isom_chapter_entry_t *chap_data ); +int isom_add_tref( isom_trak_entry_t *trak ); +int isom_add_chpl( isom_moov_t *moov ); +int isom_add_udta( lsmash_root_t *root, uint32_t track_ID ); +void isom_remove_track_reference_type( isom_tref_type_t *ref ); +void isom_remove_tref( isom_tref_t *tref ); +void isom_remove_trak( isom_trak_entry_t *trak ); + +#endif diff --git a/output/mp4/lsmash.h b/output/mp4/lsmash.h new file mode 100644 index 0000000..b48f161 --- /dev/null +++ b/output/mp4/lsmash.h @@ -0,0 +1,1328 @@ +/***************************************************************************** + * lsmash.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_H +#define LSMASH_H + +#include + +#define PRIVATE /* If this declaration is placed at a variable, any user shouldn't use it. */ + +#define LSMASH_4CC( a, b, c, d ) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)) +#define LSMASH_PACK_ISO_LANGUAGE( a, b, c ) ((((a-0x60)&0x1f)<<10) | (((b-0x60)&0x1f)<<5) | ((c-0x60)&0x1f)) + +/* public constants */ +typedef enum +{ + LSMASH_FILE_MODE_WRITE = 1, + LSMASH_FILE_MODE_READ = 1<<1, + LSMASH_FILE_MODE_DUMP = 1<<2, + LSMASH_FILE_MODE_FRAGMENTED = 1<<16, + LSMASH_FILE_MODE_WRITE_FRAGMENTED = LSMASH_FILE_MODE_WRITE | LSMASH_FILE_MODE_FRAGMENTED, + LSMASH_FILE_MODE_READ_FRAGMENTED = LSMASH_FILE_MODE_READ | LSMASH_FILE_MODE_FRAGMENTED, +} lsmash_file_mode; + +typedef enum +{ + ISOM_MEDIA_HANDLER_TYPE_3GPP_SCENE_DESCRIPTION = LSMASH_4CC( '3', 'g', 's', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_ID3_VERSION2_METADATA = LSMASH_4CC( 'I', 'D', '3', '2' ), + ISOM_MEDIA_HANDLER_TYPE_AUXILIARY_VIDEO_TRACK = LSMASH_4CC( 'a', 'u', 'x', 'v' ), + ISOM_MEDIA_HANDLER_TYPE_CPCM_AUXILIARY_METADATA = LSMASH_4CC( 'c', 'p', 'a', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_CLOCK_REFERENCE_STREAM = LSMASH_4CC( 'c', 'r', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_DVB_MANDATORY_BASIC_DESCRIPTION = LSMASH_4CC( 'd', 'm', 'b', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_TV_ANYTIME = LSMASH_4CC( 'd', 't', 'v', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_BROADBAND_CONTENT_GUIDE = LSMASH_4CC( 'd', 't', 'v', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_FONT_DATA_STREAM = LSMASH_4CC( 'f', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_GENERAL_MPEG4_SYSTEM_STREAM = LSMASH_4CC( 'g', 'e', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK = LSMASH_4CC( 'h', 'i', 'n', 't' ), + ISOM_MEDIA_HANDLER_TYPE_IPDC_ELECTRONIC_SERVICE_GUIDE = LSMASH_4CC( 'i', 'p', 'd', 'c' ), + ISOM_MEDIA_HANDLER_TYPE_IPMP_STREAM = LSMASH_4CC( 'i', 'p', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_MPEG7_STREAM = LSMASH_4CC( 'm', '7', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_TIMED_METADATA_TRACK = LSMASH_4CC( 'm', 'e', 't', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_MPEGJ_STREAM = LSMASH_4CC( 'm', 'j', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_MPEG21_DIGITAL_ITEM = LSMASH_4CC( 'm', 'p', '2', '1' ), + ISOM_MEDIA_HANDLER_TYPE_OBJECT_CONTENT_INFO_STREAM = LSMASH_4CC( 'o', 'c', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_OBJECT_DESCRIPTOR_STREAM = LSMASH_4CC( 'o', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_SCENE_DESCRIPTION_STREAM = LSMASH_4CC( 's', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_KEY_MANAGEMENT_MESSAGES = LSMASH_4CC( 's', 'k', 'm', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK = LSMASH_4CC( 's', 'o', 'u', 'n' ), + ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK = LSMASH_4CC( 't', 'e', 'x', 't' ), + ISOM_MEDIA_HANDLER_TYPE_PROPRIETARY_DESCRIPTIVE_METADATA = LSMASH_4CC( 'u', 'r', 'i', ' ' ), + ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK = LSMASH_4CC( 'v', 'i', 'd', 'e' ), +} lsmash_media_type; + +typedef enum +{ + ISOM_BRAND_TYPE_3G2A = LSMASH_4CC( '3', 'g', '2', 'a' ), + ISOM_BRAND_TYPE_3GE6 = LSMASH_4CC( '3', 'g', 'e', '6' ), + ISOM_BRAND_TYPE_3GG6 = LSMASH_4CC( '3', 'g', 'g', '6' ), + ISOM_BRAND_TYPE_3GP4 = LSMASH_4CC( '3', 'g', 'p', '4' ), + ISOM_BRAND_TYPE_3GP5 = LSMASH_4CC( '3', 'g', 'p', '5' ), + ISOM_BRAND_TYPE_3GP6 = LSMASH_4CC( '3', 'g', 'p', '6' ), + ISOM_BRAND_TYPE_3GR6 = LSMASH_4CC( '3', 'g', 'r', '6' ), + ISOM_BRAND_TYPE_3GS6 = LSMASH_4CC( '3', 'g', 's', '6' ), + ISOM_BRAND_TYPE_CAEP = LSMASH_4CC( 'C', 'A', 'E', 'P' ), + ISOM_BRAND_TYPE_CDES = LSMASH_4CC( 'C', 'D', 'e', 's' ), + ISOM_BRAND_TYPE_M4A = LSMASH_4CC( 'M', '4', 'A', ' ' ), + ISOM_BRAND_TYPE_M4B = LSMASH_4CC( 'M', '4', 'B', ' ' ), + ISOM_BRAND_TYPE_M4P = LSMASH_4CC( 'M', '4', 'P', ' ' ), + ISOM_BRAND_TYPE_M4V = LSMASH_4CC( 'M', '4', 'V', ' ' ), + ISOM_BRAND_TYPE_MPPI = LSMASH_4CC( 'M', 'P', 'P', 'I' ), + ISOM_BRAND_TYPE_ROSS = LSMASH_4CC( 'R', 'O', 'S', 'S' ), + ISOM_BRAND_TYPE_AVC1 = LSMASH_4CC( 'a', 'v', 'c', '1' ), + ISOM_BRAND_TYPE_CAQV = LSMASH_4CC( 'c', 'a', 'q', 'v' ), + ISOM_BRAND_TYPE_DA0A = LSMASH_4CC( 'd', 'a', '0', 'a' ), + ISOM_BRAND_TYPE_DA0B = LSMASH_4CC( 'd', 'a', '0', 'b' ), + ISOM_BRAND_TYPE_DA1A = LSMASH_4CC( 'd', 'a', '1', 'a' ), + ISOM_BRAND_TYPE_DA1B = LSMASH_4CC( 'd', 'a', '1', 'b' ), + ISOM_BRAND_TYPE_DA2A = LSMASH_4CC( 'd', 'a', '2', 'a' ), + ISOM_BRAND_TYPE_DA2B = LSMASH_4CC( 'd', 'a', '2', 'b' ), + ISOM_BRAND_TYPE_DA3A = LSMASH_4CC( 'd', 'a', '3', 'a' ), + ISOM_BRAND_TYPE_DA3B = LSMASH_4CC( 'd', 'a', '3', 'b' ), + ISOM_BRAND_TYPE_DMB1 = LSMASH_4CC( 'd', 'm', 'b', '1' ), + ISOM_BRAND_TYPE_DV1A = LSMASH_4CC( 'd', 'v', '1', 'a' ), + ISOM_BRAND_TYPE_DV1B = LSMASH_4CC( 'd', 'v', '1', 'b' ), + ISOM_BRAND_TYPE_DV2A = LSMASH_4CC( 'd', 'v', '2', 'a' ), + ISOM_BRAND_TYPE_DV2B = LSMASH_4CC( 'd', 'v', '2', 'b' ), + ISOM_BRAND_TYPE_DV3A = LSMASH_4CC( 'd', 'v', '3', 'a' ), + ISOM_BRAND_TYPE_DV3B = LSMASH_4CC( 'd', 'v', '3', 'b' ), + ISOM_BRAND_TYPE_DVR1 = LSMASH_4CC( 'd', 'v', 'r', '1' ), + ISOM_BRAND_TYPE_DVT1 = LSMASH_4CC( 'd', 'v', 't', '1' ), + ISOM_BRAND_TYPE_ISC2 = LSMASH_4CC( 'i', 's', 'c', '2' ), + ISOM_BRAND_TYPE_ISO2 = LSMASH_4CC( 'i', 's', 'o', '2' ), + ISOM_BRAND_TYPE_ISO3 = LSMASH_4CC( 'i', 's', 'o', '3' ), + ISOM_BRAND_TYPE_ISO4 = LSMASH_4CC( 'i', 's', 'o', '4' ), + ISOM_BRAND_TYPE_ISO5 = LSMASH_4CC( 'i', 's', 'o', '5' ), + ISOM_BRAND_TYPE_ISO6 = LSMASH_4CC( 'i', 's', 'o', '6' ), + ISOM_BRAND_TYPE_ISOM = LSMASH_4CC( 'i', 's', 'o', 'm' ), + ISOM_BRAND_TYPE_JPSI = LSMASH_4CC( 'j', 'p', 's', 'i' ), + ISOM_BRAND_TYPE_MJ2S = LSMASH_4CC( 'm', 'j', '2', 'j' ), + ISOM_BRAND_TYPE_MJP2 = LSMASH_4CC( 'm', 'j', 'p', '2' ), + ISOM_BRAND_TYPE_MP21 = LSMASH_4CC( 'm', 'p', '2', '1' ), + ISOM_BRAND_TYPE_MP41 = LSMASH_4CC( 'm', 'p', '4', '1' ), + ISOM_BRAND_TYPE_MP42 = LSMASH_4CC( 'm', 'p', '4', '2' ), + ISOM_BRAND_TYPE_MP71 = LSMASH_4CC( 'm', 'p', '7', '1' ), + ISOM_BRAND_TYPE_NIKO = LSMASH_4CC( 'n', 'i', 'k', 'o' ), + ISOM_BRAND_TYPE_ODCF = LSMASH_4CC( 'o', 'd', 'c', 'f' ), + ISOM_BRAND_TYPE_OPF2 = LSMASH_4CC( 'o', 'p', 'f', '2' ), + ISOM_BRAND_TYPE_OPX2 = LSMASH_4CC( 'o', 'p', 'x', '2' ), + ISOM_BRAND_TYPE_PANA = LSMASH_4CC( 'p', 'a', 'n', 'a' ), + ISOM_BRAND_TYPE_QT = LSMASH_4CC( 'q', 't', ' ', ' ' ), + ISOM_BRAND_TYPE_SDV = LSMASH_4CC( 's', 'd', 'v', ' ' ), +} lsmash_brand_type; + +typedef enum +{ + /* Audio Type */ + ISOM_CODEC_TYPE_AC_3_AUDIO = LSMASH_4CC( 'a', 'c', '-', '3' ), /* AC-3 audio */ + ISOM_CODEC_TYPE_ALAC_AUDIO = LSMASH_4CC( 'a', 'l', 'a', 'c' ), /* Apple lossless audio codec */ + ISOM_CODEC_TYPE_DRA1_AUDIO = LSMASH_4CC( 'd', 'r', 'a', '1' ), /* DRA Audio */ + ISOM_CODEC_TYPE_DTSC_AUDIO = LSMASH_4CC( 'd', 't', 's', 'c' ), /* DTS Coherent Acoustics audio */ + ISOM_CODEC_TYPE_DTSH_AUDIO = LSMASH_4CC( 'd', 't', 's', 'h' ), /* DTS-HD High Resolution Audio */ + ISOM_CODEC_TYPE_DTSL_AUDIO = LSMASH_4CC( 'd', 't', 's', 'l' ), /* DTS-HD Master Audio */ + ISOM_CODEC_TYPE_DTSE_AUDIO = LSMASH_4CC( 'd', 't', 's', 'e' ), /* DTS Express low bit rate audio, also known as DTS LBR */ + ISOM_CODEC_TYPE_EC_3_AUDIO = LSMASH_4CC( 'e', 'c', '-', '3' ), /* Enhanced AC-3 audio */ + ISOM_CODEC_TYPE_ENCA_AUDIO = LSMASH_4CC( 'e', 'n', 'c', 'a' ), /* Encrypted/Protected audio */ + ISOM_CODEC_TYPE_G719_AUDIO = LSMASH_4CC( 'g', '7', '1', '9' ), /* ITU-T Recommendation G.719 (2008) */ + ISOM_CODEC_TYPE_G726_AUDIO = LSMASH_4CC( 'g', '7', '2', '6' ), /* ITU-T Recommendation G.726 (1990) */ + ISOM_CODEC_TYPE_M4AE_AUDIO = LSMASH_4CC( 'm', '4', 'a', 'e' ), /* MPEG-4 Audio Enhancement */ + ISOM_CODEC_TYPE_MLPA_AUDIO = LSMASH_4CC( 'm', 'l', 'p', 'a' ), /* MLP Audio */ + ISOM_CODEC_TYPE_MP4A_AUDIO = LSMASH_4CC( 'm', 'p', '4', 'a' ), /* MPEG-4 Audio */ + ISOM_CODEC_TYPE_RAW_AUDIO = LSMASH_4CC( 'r', 'a', 'w', ' ' ), /* Uncompressed audio */ + ISOM_CODEC_TYPE_SAMR_AUDIO = LSMASH_4CC( 's', 'a', 'm', 'r' ), /* Narrowband AMR voice */ + ISOM_CODEC_TYPE_SAWB_AUDIO = LSMASH_4CC( 's', 'a', 'w', 'b' ), /* Wideband AMR voice */ + ISOM_CODEC_TYPE_SAWP_AUDIO = LSMASH_4CC( 's', 'a', 'w', 'p' ), /* Extended AMR-WB (AMR-WB+) */ + ISOM_CODEC_TYPE_SEVC_AUDIO = LSMASH_4CC( 's', 'e', 'v', 'c' ), /* EVRC Voice */ + ISOM_CODEC_TYPE_SQCP_AUDIO = LSMASH_4CC( 's', 'q', 'c', 'p' ), /* 13K Voice */ + ISOM_CODEC_TYPE_SSMV_AUDIO = LSMASH_4CC( 's', 's', 'm', 'v' ), /* SMV Voice */ + ISOM_CODEC_TYPE_TWOS_AUDIO = LSMASH_4CC( 't', 'w', 'o', 's' ), /* Uncompressed 16-bit audio */ + + QT_CODEC_TYPE_23NI_AUDIO = LSMASH_4CC( '2', '3', 'n', 'i' ), /* 32-bit little endian integer uncompressed */ + QT_CODEC_TYPE_MAC3_AUDIO = LSMASH_4CC( 'M', 'A', 'C', '3' ), /* MACE 3:1 */ + QT_CODEC_TYPE_MAC6_AUDIO = LSMASH_4CC( 'M', 'A', 'C', '6' ), /* MACE 6:1 */ + QT_CODEC_TYPE_NONE_AUDIO = LSMASH_4CC( 'N', 'O', 'N', 'E' ), /* either 'raw ' or 'twos' */ + QT_CODEC_TYPE_QDM2_AUDIO = LSMASH_4CC( 'Q', 'D', 'M', '2' ), /* Qdesign music 2 */ + QT_CODEC_TYPE_QDMC_AUDIO = LSMASH_4CC( 'Q', 'D', 'M', 'C' ), /* Qdesign music 1 */ + QT_CODEC_TYPE_QCLP_AUDIO = LSMASH_4CC( 'Q', 'c', 'l', 'p' ), /* Qualcomm PureVoice */ + QT_CODEC_TYPE_AC_3_AUDIO = LSMASH_4CC( 'a', 'c', '-', '3' ), /* Digital Audio Compression Standard (AC-3, Enhanced AC-3) */ + QT_CODEC_TYPE_AGSM_AUDIO = LSMASH_4CC( 'a', 'g', 's', 'm' ), /* GSM */ + QT_CODEC_TYPE_ALAC_AUDIO = LSMASH_4CC( 'a', 'l', 'a', 'c' ), /* Apple lossless audio codec */ + QT_CODEC_TYPE_ALAW_AUDIO = LSMASH_4CC( 'a', 'l', 'a', 'w' ), /* a-Law 2:1 */ + QT_CODEC_TYPE_CDX2_AUDIO = LSMASH_4CC( 'c', 'd', 'x', '2' ), /* CD/XA 2:1 */ + QT_CODEC_TYPE_CDX4_AUDIO = LSMASH_4CC( 'c', 'd', 'x', '4' ), /* CD/XA 4:1 */ + QT_CODEC_TYPE_DVCA_AUDIO = LSMASH_4CC( 'd', 'v', 'c', 'a' ), /* DV Audio */ + QT_CODEC_TYPE_DVI_AUDIO = LSMASH_4CC( 'd', 'v', 'i', ' ' ), /* DVI (as used in RTP, 4:1 compression) */ + QT_CODEC_TYPE_FL32_AUDIO = LSMASH_4CC( 'f', 'l', '3', '2' ), /* 32-bit float */ + QT_CODEC_TYPE_FL64_AUDIO = LSMASH_4CC( 'f', 'l', '6', '4' ), /* 64-bit float */ + QT_CODEC_TYPE_IMA4_AUDIO = LSMASH_4CC( 'i', 'm', 'a', '4' ), /* IMA (International Multimedia Assocation, defunct, 4:1) */ + QT_CODEC_TYPE_IN24_AUDIO = LSMASH_4CC( 'i', 'n', '2', '4' ), /* 24-bit integer uncompressed */ + QT_CODEC_TYPE_IN32_AUDIO = LSMASH_4CC( 'i', 'n', '3', '2' ), /* 32-bit integer uncompressed */ + QT_CODEC_TYPE_LPCM_AUDIO = LSMASH_4CC( 'l', 'p', 'c', 'm' ), /* Uncompressed audio (various integer and float formats) */ + QT_CODEC_TYPE_MP4A_AUDIO = LSMASH_4CC( 'm', 'p', '4', 'a' ), /* MPEG-4 Audio */ + QT_CODEC_TYPE_RAW_AUDIO = LSMASH_4CC( 'r', 'a', 'w', ' ' ), /* 8-bit offset-binary uncompressed */ + QT_CODEC_TYPE_SOWT_AUDIO = LSMASH_4CC( 's', 'o', 'w', 't' ), /* 16-bit little endian uncompressed */ + QT_CODEC_TYPE_TWOS_AUDIO = LSMASH_4CC( 't', 'w', 'o', 's' ), /* 8-bit or 16-bit big endian uncompressed */ + QT_CODEC_TYPE_ULAW_AUDIO = LSMASH_4CC( 'u', 'l', 'a', 'w' ), /* uLaw 2:1 */ + QT_CODEC_TYPE_VDVA_AUDIO = LSMASH_4CC( 'v', 'd', 'v', 'a' ), /* DV audio (variable duration per video frame) */ + QT_CODEC_TYPE_FULLMP3_AUDIO = LSMASH_4CC( '.', 'm', 'p', '3' ), /* MPEG-1 layer 3, CBR & VBR (QT4.1 and later) */ + QT_CODEC_TYPE_MP3_AUDIO = 0x6D730055, /* MPEG-1 layer 3, CBR only (pre-QT4.1) */ + QT_CODEC_TYPE_ADPCM2_AUDIO = 0x6D730002, /* Microsoft ADPCM - ACM code 2 */ + QT_CODEC_TYPE_ADPCM17_AUDIO = 0x6D730011, /* DVI/Intel IMA ADPCM - ACM code 17 */ + QT_CODEC_TYPE_GSM49_AUDIO = 0x6D730031, /* Microsoft GSM 6.10 - ACM code 49 */ + QT_CODEC_TYPE_NOT_SPECIFIED = 0x00000000, /* either 'raw ' or 'twos' */ + + /* Video Type */ + ISOM_CODEC_TYPE_AVC1_VIDEO = LSMASH_4CC( 'a', 'v', 'c', '1' ), /* Advanced Video Coding */ + ISOM_CODEC_TYPE_AVC2_VIDEO = LSMASH_4CC( 'a', 'v', 'c', '2' ), /* Advanced Video Coding */ + ISOM_CODEC_TYPE_AVCP_VIDEO = LSMASH_4CC( 'a', 'v', 'c', 'p' ), /* Advanced Video Coding Parameters */ + ISOM_CODEC_TYPE_DRAC_VIDEO = LSMASH_4CC( 'd', 'r', 'a', 'c' ), /* Dirac Video Coder */ + ISOM_CODEC_TYPE_ENCV_VIDEO = LSMASH_4CC( 'e', 'n', 'c', 'v' ), /* Encrypted/protected video */ + ISOM_CODEC_TYPE_MJP2_VIDEO = LSMASH_4CC( 'm', 'j', 'p', '2' ), /* Motion JPEG 2000 */ + ISOM_CODEC_TYPE_MP4V_VIDEO = LSMASH_4CC( 'm', 'p', '4', 'v' ), /* MPEG-4 Visual */ + ISOM_CODEC_TYPE_MVC1_VIDEO = LSMASH_4CC( 'm', 'v', 'c', '1' ), /* Multiview coding */ + ISOM_CODEC_TYPE_MVC2_VIDEO = LSMASH_4CC( 'm', 'v', 'c', '2' ), /* Multiview coding */ + ISOM_CODEC_TYPE_S263_VIDEO = LSMASH_4CC( 's', '2', '6', '3' ), /* ITU H.263 video (3GPP format) */ + ISOM_CODEC_TYPE_SVC1_VIDEO = LSMASH_4CC( 's', 'v', 'c', '1' ), /* Scalable Video Coding */ + ISOM_CODEC_TYPE_VC_1_VIDEO = LSMASH_4CC( 'v', 'c', '-', '1' ), /* SMPTE VC-1 */ + + QT_CODEC_TYPE_CFHD_VIDEO = LSMASH_4CC( 'C', 'F', 'H', 'D' ), /* CineForm High-Definition (HD) wavelet codec */ + QT_CODEC_TYPE_DV10_VIDEO = LSMASH_4CC( 'D', 'V', '1', '0' ), /* Digital Voodoo 10 bit Uncompressed 4:2:2 codec */ + QT_CODEC_TYPE_DVOO_VIDEO = LSMASH_4CC( 'D', 'V', 'O', 'O' ), /* Digital Voodoo 8 bit Uncompressed 4:2:2 codec */ + QT_CODEC_TYPE_DVOR_VIDEO = LSMASH_4CC( 'D', 'V', 'O', 'R' ), /* Digital Voodoo intermediate raw */ + QT_CODEC_TYPE_DVTV_VIDEO = LSMASH_4CC( 'D', 'V', 'T', 'V' ), /* Digital Voodoo intermediate 2vuy */ + QT_CODEC_TYPE_DVVT_VIDEO = LSMASH_4CC( 'D', 'V', 'V', 'T' ), /* Digital Voodoo intermediate v210 */ + QT_CODEC_TYPE_HD10_VIDEO = LSMASH_4CC( 'H', 'D', '1', '0' ), /* Digital Voodoo 10 bit Uncompressed 4:2:2 HD codec */ + QT_CODEC_TYPE_M105_VIDEO = LSMASH_4CC( 'M', '1', '0', '5' ), /* Internal format of video data supported by Matrox hardware; pixel organization is proprietary*/ + QT_CODEC_TYPE_PNTG_VIDEO = LSMASH_4CC( 'P', 'N', 'T', 'G' ), /* Apple MacPaint image format */ + QT_CODEC_TYPE_SVQ1_VIDEO = LSMASH_4CC( 'S', 'V', 'Q', '1' ), /* Sorenson Video 1 video */ + QT_CODEC_TYPE_SVQ3_VIDEO = LSMASH_4CC( 'S', 'V', 'Q', '3' ), /* Sorenson Video 3 video */ + QT_CODEC_TYPE_SHR0_VIDEO = LSMASH_4CC( 'S', 'h', 'r', '0' ), /* Generic SheerVideo codec */ + QT_CODEC_TYPE_SHR1_VIDEO = LSMASH_4CC( 'S', 'h', 'r', '1' ), /* SheerVideo RGB[A] 8b - at 8 bits/channel */ + QT_CODEC_TYPE_SHR2_VIDEO = LSMASH_4CC( 'S', 'h', 'r', '2' ), /* SheerVideo Y'CbCr[A] 8bv 4:4:4[:4] - at 8 bits/channel, in ITU-R BT.601-4 video range */ + QT_CODEC_TYPE_SHR3_VIDEO = LSMASH_4CC( 'S', 'h', 'r', '3' ), /* SheerVideo Y'CbCr 8bv 4:2:2 - 2:1 chroma subsampling, at 8 bits/channel, in ITU-R BT.601-4 video range */ + QT_CODEC_TYPE_SHR4_VIDEO = LSMASH_4CC( 'S', 'h', 'r', '4' ), /* SheerVideo Y'CbCr 8bw 4:2:2 - 2:1 chroma subsampling, at 8 bits/channel, with full-range luma and wide-range two's-complement chroma */ + QT_CODEC_TYPE_WRLE_VIDEO = LSMASH_4CC( 'W', 'R', 'L', 'E' ), /* Windows BMP image format */ + QT_CODEC_TYPE_APCH_VIDEO = LSMASH_4CC( 'a', 'p', 'c', 'h' ), /* Apple ProRes 422 High Quality */ + QT_CODEC_TYPE_APCN_VIDEO = LSMASH_4CC( 'a', 'p', 'c', 'n' ), /* Apple ProRes 422 Standard Definition */ + QT_CODEC_TYPE_APCS_VIDEO = LSMASH_4CC( 'a', 'p', 'c', 's' ), /* Apple ProRes 422 LT */ + QT_CODEC_TYPE_APCO_VIDEO = LSMASH_4CC( 'a', 'p', 'c', 'o' ), /* Apple ProRes 422 Proxy */ + QT_CODEC_TYPE_AP4H_VIDEO = LSMASH_4CC( 'a', 'p', '4', 'h' ), /* Apple ProRes 4444 */ + QT_CODEC_TYPE_CIVD_VIDEO = LSMASH_4CC( 'c', 'i', 'v', 'd' ), /* Cinepak Video */ + QT_CODEC_TYPE_DRAC_VIDEO = LSMASH_4CC( 'd', 'r', 'a', 'c' ), /* Dirac Video Coder */ + QT_CODEC_TYPE_DVH5_VIDEO = LSMASH_4CC( 'd', 'v', 'h', '5' ), /* DVCPRO-HD 1080/50i */ + QT_CODEC_TYPE_DVH6_VIDEO = LSMASH_4CC( 'd', 'v', 'h', '6' ), /* DVCPRO-HD 1080/60i */ + QT_CODEC_TYPE_DVHP_VIDEO = LSMASH_4CC( 'd', 'v', 'h', 'p' ), /* DVCPRO-HD 720/60p */ + QT_CODEC_TYPE_FLIC_VIDEO = LSMASH_4CC( 'f', 'l', 'i', 'c' ), /* Autodesk FLIC animation format */ + QT_CODEC_TYPE_GIF_VIDEO = LSMASH_4CC( 'g', 'i', 'f', ' ' ), /* GIF image format */ + QT_CODEC_TYPE_H261_VIDEO = LSMASH_4CC( 'h', '2', '6', '1' ), /* ITU H.261 video */ + QT_CODEC_TYPE_H263_VIDEO = LSMASH_4CC( 'h', '2', '6', '3' ), /* ITU H.263 video */ + QT_CODEC_TYPE_JPEG_VIDEO = LSMASH_4CC( 'j', 'p', 'e', 'g' ), /* JPEG image format */ + QT_CODEC_TYPE_MJPA_VIDEO = LSMASH_4CC( 'm', 'j', 'p', 'a' ), /* Motion-JPEG (format A) */ + QT_CODEC_TYPE_MJPB_VIDEO = LSMASH_4CC( 'm', 'j', 'p', 'b' ), /* Motion-JPEG (format B) */ + QT_CODEC_TYPE_PNG_VIDEO = LSMASH_4CC( 'p', 'n', 'g', ' ' ), /* W3C Portable Network Graphics (PNG) */ + QT_CODEC_TYPE_RLE_VIDEO = LSMASH_4CC( 'r', 'l', 'e', ' ' ), /* Apple animation codec */ + QT_CODEC_TYPE_RPZA_VIDEO = LSMASH_4CC( 'r', 'p', 'z', 'a' ), /* Apple simple video 'road pizza' compression */ + QT_CODEC_TYPE_TGA_VIDEO = LSMASH_4CC( 't', 'g', 'a', ' ' ), /* Truvision Targa video format */ + QT_CODEC_TYPE_TIFF_VIDEO = LSMASH_4CC( 't', 'i', 'f', 'f' ), /* Tagged Image File Format (Adobe) */ + + /* Text Type */ + ISOM_CODEC_TYPE_ENCT_TEXT = LSMASH_4CC( 'e', 'n', 'c', 't' ), /* Encrypted Text */ + ISOM_CODEC_TYPE_TX3G_TEXT = LSMASH_4CC( 't', 'x', '3', 'g' ), /* Timed Text stream */ + + QT_CODEC_TYPE_TEXT_TEXT = LSMASH_4CC( 't', 'e', 'x', 't' ), /* QuickTime Text Media */ + + /* Hint Type */ + ISOM_CODEC_TYPE_FDP_HINT = LSMASH_4CC( 'f', 'd', 'p', ' ' ), /* File delivery hints */ + ISOM_CODEC_TYPE_M2TS_HINT = LSMASH_4CC( 'm', '2', 't', 's' ), /* MPEG-2 transport stream for DMB */ + ISOM_CODEC_TYPE_PM2T_HINT = LSMASH_4CC( 'p', 'm', '2', 't' ), /* Protected MPEG-2 Transport */ + ISOM_CODEC_TYPE_PRTP_HINT = LSMASH_4CC( 'p', 'r', 't', 'p' ), /* Protected RTP Reception */ + ISOM_CODEC_TYPE_RM2T_HINT = LSMASH_4CC( 'r', 'm', '2', 't' ), /* MPEG-2 Transport Reception */ + ISOM_CODEC_TYPE_RRTP_HINT = LSMASH_4CC( 'r', 'r', 't', 'p' ), /* RTP reception */ + ISOM_CODEC_TYPE_RSRP_HINT = LSMASH_4CC( 'r', 's', 'r', 'p' ), /* SRTP Reception */ + ISOM_CODEC_TYPE_RTP_HINT = LSMASH_4CC( 'r', 't', 'p', ' ' ), /* RTP Hints */ + ISOM_CODEC_TYPE_SM2T_HINT = LSMASH_4CC( 's', 'm', '2', 't' ), /* MPEG-2 Transport Server */ + ISOM_CODEC_TYPE_SRTP_HINT = LSMASH_4CC( 's', 'r', 't', 'p' ), /* SRTP Hints */ + + /* Metadata Type */ + ISOM_CODEC_TYPE_IXSE_META = LSMASH_4CC( 'i', 'x', 's', 'e' ), /* DVB Track Level Index Track */ + ISOM_CODEC_TYPE_METT_META = LSMASH_4CC( 'm', 'e', 't', 't' ), /* Text timed metadata */ + ISOM_CODEC_TYPE_METX_META = LSMASH_4CC( 'm', 'e', 't', 'x' ), /* XML timed metadata */ + ISOM_CODEC_TYPE_MLIX_META = LSMASH_4CC( 'm', 'l', 'i', 'x' ), /* DVB Movie level index track */ + ISOM_CODEC_TYPE_OKSD_META = LSMASH_4CC( 'o', 'k', 's', 'd' ), /* OMA Keys */ + ISOM_CODEC_TYPE_SVCM_META = LSMASH_4CC( 's', 'v', 'c', 'M' ), /* SVC metadata */ + ISOM_CODEC_TYPE_TEXT_META = LSMASH_4CC( 't', 'e', 'x', 't' ), /* Textual meta-data with MIME type */ + ISOM_CODEC_TYPE_URIM_META = LSMASH_4CC( 'u', 'r', 'i', 'm' ), /* URI identified timed metadata */ + ISOM_CODEC_TYPE_XML_META = LSMASH_4CC( 'x', 'm', 'l', ' ' ), /* XML-formatted meta-data */ + + /* Other Type */ + ISOM_CODEC_TYPE_ENCS_SYSTEM = LSMASH_4CC( 'e', 'n', 'c', 's' ), /* Encrypted Systems stream */ + ISOM_CODEC_TYPE_MP4S_SYSTEM = LSMASH_4CC( 'm', 'p', '4', 's' ), /* MPEG-4 Systems */ +} lsmash_codec_type; + +typedef enum +{ + ISOM_LANGUAGE_CODE_ENGLISH = LSMASH_PACK_ISO_LANGUAGE( 'e', 'n', 'g' ), + ISOM_LANGUAGE_CODE_FRENCH = LSMASH_PACK_ISO_LANGUAGE( 'f', 'r', 'a' ), + ISOM_LANGUAGE_CODE_GERMAN = LSMASH_PACK_ISO_LANGUAGE( 'd', 'e', 'u' ), + ISOM_LANGUAGE_CODE_ITALIAN = LSMASH_PACK_ISO_LANGUAGE( 'i', 't', 'a' ), + ISOM_LANGUAGE_CODE_DUTCH_M = LSMASH_PACK_ISO_LANGUAGE( 'd', 'u', 'm' ), + ISOM_LANGUAGE_CODE_SWEDISH = LSMASH_PACK_ISO_LANGUAGE( 's', 'w', 'e' ), + ISOM_LANGUAGE_CODE_SPANISH = LSMASH_PACK_ISO_LANGUAGE( 's', 'p', 'a' ), + ISOM_LANGUAGE_CODE_DANISH = LSMASH_PACK_ISO_LANGUAGE( 'd', 'a', 'n' ), + ISOM_LANGUAGE_CODE_PORTUGUESE = LSMASH_PACK_ISO_LANGUAGE( 'p', 'o', 'r' ), + ISOM_LANGUAGE_CODE_NORWEGIAN = LSMASH_PACK_ISO_LANGUAGE( 'n', 'o', 'r' ), + ISOM_LANGUAGE_CODE_HEBREW = LSMASH_PACK_ISO_LANGUAGE( 'h', 'e', 'b' ), + ISOM_LANGUAGE_CODE_JAPANESE = LSMASH_PACK_ISO_LANGUAGE( 'j', 'p', 'n' ), + ISOM_LANGUAGE_CODE_ARABIC = LSMASH_PACK_ISO_LANGUAGE( 'a', 'r', 'a' ), + ISOM_LANGUAGE_CODE_FINNISH = LSMASH_PACK_ISO_LANGUAGE( 'f', 'i', 'n' ), + ISOM_LANGUAGE_CODE_GREEK = LSMASH_PACK_ISO_LANGUAGE( 'e', 'l', 'l' ), + ISOM_LANGUAGE_CODE_ICELANDIC = LSMASH_PACK_ISO_LANGUAGE( 'i', 's', 'l' ), + ISOM_LANGUAGE_CODE_MALTESE = LSMASH_PACK_ISO_LANGUAGE( 'm', 'l', 't' ), + ISOM_LANGUAGE_CODE_TURKISH = LSMASH_PACK_ISO_LANGUAGE( 't', 'u', 'r' ), + ISOM_LANGUAGE_CODE_CROATIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'r', 'v' ), + ISOM_LANGUAGE_CODE_CHINESE = LSMASH_PACK_ISO_LANGUAGE( 'z', 'h', 'o' ), + ISOM_LANGUAGE_CODE_URDU = LSMASH_PACK_ISO_LANGUAGE( 'u', 'r', 'd' ), + ISOM_LANGUAGE_CODE_HINDI = LSMASH_PACK_ISO_LANGUAGE( 'h', 'i', 'n' ), + ISOM_LANGUAGE_CODE_THAI = LSMASH_PACK_ISO_LANGUAGE( 't', 'h', 'a' ), + ISOM_LANGUAGE_CODE_KOREAN = LSMASH_PACK_ISO_LANGUAGE( 'k', 'o', 'r' ), + ISOM_LANGUAGE_CODE_LITHUANIAN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'i', 't' ), + ISOM_LANGUAGE_CODE_POLISH = LSMASH_PACK_ISO_LANGUAGE( 'p', 'o', 'l' ), + ISOM_LANGUAGE_CODE_HUNGARIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'u', 'n' ), + ISOM_LANGUAGE_CODE_ESTONIAN = LSMASH_PACK_ISO_LANGUAGE( 'e', 's', 't' ), + ISOM_LANGUAGE_CODE_LATVIAN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 'v' ), + ISOM_LANGUAGE_CODE_SAMI = LSMASH_PACK_ISO_LANGUAGE( 's', 'm', 'i' ), + ISOM_LANGUAGE_CODE_FAROESE = LSMASH_PACK_ISO_LANGUAGE( 'f', 'a', 'o' ), + ISOM_LANGUAGE_CODE_RUSSIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'u', 's' ), + ISOM_LANGUAGE_CODE_DUTCH = LSMASH_PACK_ISO_LANGUAGE( 'n', 'l', 'd' ), + ISOM_LANGUAGE_CODE_IRISH = LSMASH_PACK_ISO_LANGUAGE( 'g', 'l', 'e' ), + ISOM_LANGUAGE_CODE_ALBANIAN = LSMASH_PACK_ISO_LANGUAGE( 's', 'q', 'i' ), + ISOM_LANGUAGE_CODE_ROMANIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'o', 'n' ), + ISOM_LANGUAGE_CODE_CZECH = LSMASH_PACK_ISO_LANGUAGE( 'c', 'e', 's' ), + ISOM_LANGUAGE_CODE_SLOVAK = LSMASH_PACK_ISO_LANGUAGE( 's', 'l', 'k' ), + ISOM_LANGUAGE_CODE_SLOVENIA = LSMASH_PACK_ISO_LANGUAGE( 's', 'l', 'v' ), + ISOM_LANGUAGE_CODE_YIDDISH = LSMASH_PACK_ISO_LANGUAGE( 'y', 'i', 'd' ), + ISOM_LANGUAGE_CODE_SERBIAN = LSMASH_PACK_ISO_LANGUAGE( 's', 'r', 'p' ), + ISOM_LANGUAGE_CODE_MACEDONIAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 'k', 'd' ), + ISOM_LANGUAGE_CODE_BULGARIAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'u', 'l' ), + ISOM_LANGUAGE_CODE_UKRAINIAN = LSMASH_PACK_ISO_LANGUAGE( 'u', 'k', 'r' ), + ISOM_LANGUAGE_CODE_BELARUSIAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'e', 'l' ), + ISOM_LANGUAGE_CODE_UZBEK = LSMASH_PACK_ISO_LANGUAGE( 'u', 'z', 'b' ), + ISOM_LANGUAGE_CODE_KAZAKH = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 'z' ), + ISOM_LANGUAGE_CODE_AZERBAIJANI = LSMASH_PACK_ISO_LANGUAGE( 'a', 'z', 'e' ), + ISOM_LANGUAGE_CODE_ARMENIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'y', 'e' ), + ISOM_LANGUAGE_CODE_GEORGIAN = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 't' ), + ISOM_LANGUAGE_CODE_MOLDAVIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'o', 'n' ), + ISOM_LANGUAGE_CODE_KIRGHIZ = LSMASH_PACK_ISO_LANGUAGE( 'k', 'i', 'r' ), + ISOM_LANGUAGE_CODE_TAJIK = LSMASH_PACK_ISO_LANGUAGE( 't', 'g', 'k' ), + ISOM_LANGUAGE_CODE_TURKMEN = LSMASH_PACK_ISO_LANGUAGE( 't', 'u', 'k' ), + ISOM_LANGUAGE_CODE_MONGOLIAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 'o', 'n' ), + ISOM_LANGUAGE_CODE_PASHTO = LSMASH_PACK_ISO_LANGUAGE( 'p', 'u', 's' ), + ISOM_LANGUAGE_CODE_KURDISH = LSMASH_PACK_ISO_LANGUAGE( 'k', 'u', 'r' ), + ISOM_LANGUAGE_CODE_KASHMIRI = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 's' ), + ISOM_LANGUAGE_CODE_SINDHI = LSMASH_PACK_ISO_LANGUAGE( 's', 'n', 'd' ), + ISOM_LANGUAGE_CODE_TIBETAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'o', 'd' ), + ISOM_LANGUAGE_CODE_NEPALI = LSMASH_PACK_ISO_LANGUAGE( 'n', 'e', 'p' ), + ISOM_LANGUAGE_CODE_SANSKRIT = LSMASH_PACK_ISO_LANGUAGE( 's', 'a', 'n' ), + ISOM_LANGUAGE_CODE_MARATHI = LSMASH_PACK_ISO_LANGUAGE( 'm', 'a', 'r' ), + ISOM_LANGUAGE_CODE_BENGALI = LSMASH_PACK_ISO_LANGUAGE( 'b', 'e', 'n' ), + ISOM_LANGUAGE_CODE_ASSAMESE = LSMASH_PACK_ISO_LANGUAGE( 'a', 's', 'm' ), + ISOM_LANGUAGE_CODE_GUJARATI = LSMASH_PACK_ISO_LANGUAGE( 'g', 'u', 'j' ), + ISOM_LANGUAGE_CODE_PUNJABI = LSMASH_PACK_ISO_LANGUAGE( 'p', 'a', 'n' ), + ISOM_LANGUAGE_CODE_ORIYA = LSMASH_PACK_ISO_LANGUAGE( 'o', 'r', 'i' ), + ISOM_LANGUAGE_CODE_MALAYALAM = LSMASH_PACK_ISO_LANGUAGE( 'm', 'a', 'l' ), + ISOM_LANGUAGE_CODE_KANNADA = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 'n' ), + ISOM_LANGUAGE_CODE_TAMIL = LSMASH_PACK_ISO_LANGUAGE( 't', 'a', 'm' ), + ISOM_LANGUAGE_CODE_TELUGU = LSMASH_PACK_ISO_LANGUAGE( 't', 'e', 'l' ), + ISOM_LANGUAGE_CODE_SINHALESE = LSMASH_PACK_ISO_LANGUAGE( 's', 'i', 'n' ), + ISOM_LANGUAGE_CODE_BURMESE = LSMASH_PACK_ISO_LANGUAGE( 'm', 'y', 'a' ), + ISOM_LANGUAGE_CODE_KHMER = LSMASH_PACK_ISO_LANGUAGE( 'k', 'h', 'm' ), + ISOM_LANGUAGE_CODE_LAO = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 'o' ), + ISOM_LANGUAGE_CODE_VIETNAMESE = LSMASH_PACK_ISO_LANGUAGE( 'v', 'i', 'e' ), + ISOM_LANGUAGE_CODE_INDONESIAN = LSMASH_PACK_ISO_LANGUAGE( 'i', 'n', 'd' ), + ISOM_LANGUAGE_CODE_TAGALOG = LSMASH_PACK_ISO_LANGUAGE( 't', 'g', 'l' ), + ISOM_LANGUAGE_CODE_MALAY_ROMAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 's', 'a' ), + ISOM_LANGUAGE_CODE_MAYAY_ARABIC = LSMASH_PACK_ISO_LANGUAGE( 'm', 's', 'a' ), + ISOM_LANGUAGE_CODE_AMHARIC = LSMASH_PACK_ISO_LANGUAGE( 'a', 'm', 'h' ), + ISOM_LANGUAGE_CODE_OROMO = LSMASH_PACK_ISO_LANGUAGE( 'o', 'r', 'm' ), + ISOM_LANGUAGE_CODE_SOMALI = LSMASH_PACK_ISO_LANGUAGE( 's', 'o', 'm' ), + ISOM_LANGUAGE_CODE_SWAHILI = LSMASH_PACK_ISO_LANGUAGE( 's', 'w', 'a' ), + ISOM_LANGUAGE_CODE_KINYARWANDA = LSMASH_PACK_ISO_LANGUAGE( 'k', 'i', 'n' ), + ISOM_LANGUAGE_CODE_RUNDI = LSMASH_PACK_ISO_LANGUAGE( 'r', 'u', 'n' ), + ISOM_LANGUAGE_CODE_CHEWA = LSMASH_PACK_ISO_LANGUAGE( 'n', 'y', 'a' ), + ISOM_LANGUAGE_CODE_MALAGASY = LSMASH_PACK_ISO_LANGUAGE( 'm', 'l', 'g' ), + ISOM_LANGUAGE_CODE_ESPERANTO = LSMASH_PACK_ISO_LANGUAGE( 'e', 'p', 'o' ), + ISOM_LANGUAGE_CODE_WELSH = LSMASH_PACK_ISO_LANGUAGE( 'c', 'y', 'm' ), + ISOM_LANGUAGE_CODE_BASQUE = LSMASH_PACK_ISO_LANGUAGE( 'e', 'u', 's' ), + ISOM_LANGUAGE_CODE_CATALAN = LSMASH_PACK_ISO_LANGUAGE( 'c', 'a', 't' ), + ISOM_LANGUAGE_CODE_LATIN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 't' ), + ISOM_LANGUAGE_CODE_QUECHUA = LSMASH_PACK_ISO_LANGUAGE( 'q', 'u', 'e' ), + ISOM_LANGUAGE_CODE_GUARANI = LSMASH_PACK_ISO_LANGUAGE( 'g', 'r', 'n' ), + ISOM_LANGUAGE_CODE_AYMARA = LSMASH_PACK_ISO_LANGUAGE( 'a', 'y', 'm' ), + ISOM_LANGUAGE_CODE_TATAR = LSMASH_PACK_ISO_LANGUAGE( 'c', 'r', 'h' ), + ISOM_LANGUAGE_CODE_UIGHUR = LSMASH_PACK_ISO_LANGUAGE( 'u', 'i', 'g' ), + ISOM_LANGUAGE_CODE_DZONGKHA = LSMASH_PACK_ISO_LANGUAGE( 'd', 'z', 'o' ), + ISOM_LANGUAGE_CODE_JAVANESE = LSMASH_PACK_ISO_LANGUAGE( 'j', 'a', 'v' ), + ISOM_LANGUAGE_CODE_UNDEFINED = LSMASH_PACK_ISO_LANGUAGE( 'u', 'n', 'd' ), +} lsmash_iso_language_code; + +typedef enum +{ +#define UINT16_MAX_PLUS_ONE 0x10000 + QT_COLOR_PARAMETER_NOT_SPECIFIED = UINT16_MAX_PLUS_ONE, + QT_COLOR_PARAMETER_ITU_R_BT470_M, + QT_COLOR_PARAMETER_ITU_R_BT470_BG, + QT_COLOR_PARAMETER_ITU_R_BT709, + QT_COLOR_PARAMETER_SMPTE_170M, + QT_COLOR_PARAMETER_SMPTE_240M, + QT_COLOR_PARAMETER_SMPTE_274M, + QT_COLOR_PARAMETER_SMPTE_293M, + QT_COLOR_PARAMETER_SMPTE_296M, + QT_COLOR_PARAMETER_END, +} lsmash_color_parameter; + +typedef enum +{ + QT_CHANNEL_LABEL_UNKNOWN = 0xffffffff, /* unknown or unspecified other use */ + QT_CHANNEL_LABEL_UNUSED = 0, /* channel is present, but has no intended use or destination */ + QT_CHANNEL_LABEL_USE_COORDINATES = 100, /* channel is described by the coordinates fields. */ + + QT_CHANNEL_LABEL_LEFT = 1, + QT_CHANNEL_LABEL_RIGHT = 2, + QT_CHANNEL_LABEL_CENTER = 3, + QT_CHANNEL_LABEL_LFE_SCREEN = 4, + QT_CHANNEL_LABEL_LEFT_SURROUND = 5, /* WAVE: "Back Left" */ + QT_CHANNEL_LABEL_RIGHT_SUROUND = 6, /* WAVE: "Back Right" */ + QT_CHANNEL_LABEL_LEFT_CENTER = 7, + QT_CHANNEL_LABEL_RIGHT_CENTER = 8, + QT_CHANNEL_LABEL_CENTER_SURROUND = 9, /* WAVE: "Back Center" or plain "Rear Surround" */ + QT_CHANNEL_LABEL_LEFT_SURROUND_DIRECT = 10, /* WAVE: "Side Left" */ + QT_CHANNEL_LABEL_RIGHT_SURROUND_DIRECT = 11, /* WAVE: "Side Right" */ + QT_CHANNEL_LABEL_TOP_CENTER_SURROUND = 12, + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_LEFT = 13, /* WAVE: "Top Front Left" */ + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_CENTER = 14, /* WAVE: "Top Front Center" */ + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_RIGHT = 15, /* WAVE: "Top Front Right" */ + + QT_CHANNEL_LABEL_TOP_BACK_LEFT = 16, + QT_CHANNEL_LABEL_TOP_BACK_CENTER = 17, + QT_CHANNEL_LABEL_TOP_BACK_RIGHT = 18, + + QT_CHANNEL_LABEL_REAR_SURROUND_LEFT = 33, + QT_CHANNEL_LABEL_REAR_SURROUND_RIGHT = 34, + QT_CHANNEL_LABEL_LEFT_WIDE = 35, + QT_CHANNEL_LABEL_RIGHT_WIDE = 36, + QT_CHANNEL_LABEL_LFE2 = 37, + QT_CHANNEL_LABEL_LEFT_TOTAL = 38, /* matrix encoded 4 channels */ + QT_CHANNEL_LABEL_RIGHT_TOTAL = 39, /* matrix encoded 4 channels */ + QT_CHANNEL_LABEL_HEARING_IMPAIRED = 40, + QT_CHANNEL_LABEL_NARRATION = 41, + QT_CHANNEL_LABEL_MONO = 42, + QT_CHANNEL_LABEL_DIALOG_CENTRIC_MIX = 43, + + QT_CHANNEL_LABEL_CENTER_SURROUND_DIRECT = 44, /* back center, non diffuse */ + + QT_CHANNEL_LABEL_HAPTIC = 45, + + /* first order ambisonic channels */ + QT_CHANNEL_LABEL_AMBISONIC_W = 200, + QT_CHANNEL_LABEL_AMBISONIC_X = 201, + QT_CHANNEL_LABEL_AMBISONIC_Y = 202, + QT_CHANNEL_LABEL_AMBISONIC_Z = 203, + + /* Mid/Side Recording */ + QT_CHANNEL_LABEL_MS_MID = 204, + QT_CHANNEL_LABEL_MS_SIDE = 205, + + /* X-Y Recording */ + QT_CHANNEL_LABEL_XY_X = 206, + QT_CHANNEL_LABEL_XY_Y = 207, + + /* other */ + QT_CHANNEL_LABEL_HEADPHONES_LEFT = 301, + QT_CHANNEL_LABEL_HEADPHONES_RIGHT = 302, + QT_CHANNEL_LABEL_CLICK_TRACK = 304, + QT_CHANNEL_LABEL_FOREIGN_LANGUAGE = 305, + + /* generic discrete channel */ + QT_CHANNEL_LABEL_DISCRETE = 400, + + /* numbered discrete channel */ + QT_CHANNEL_LABEL_DISCRETE_0 = (1<<16), + QT_CHANNEL_LABEL_DISCRETE_1 = (1<<16) | 1, + QT_CHANNEL_LABEL_DISCRETE_2 = (1<<16) | 2, + QT_CHANNEL_LABEL_DISCRETE_3 = (1<<16) | 3, + QT_CHANNEL_LABEL_DISCRETE_4 = (1<<16) | 4, + QT_CHANNEL_LABEL_DISCRETE_5 = (1<<16) | 5, + QT_CHANNEL_LABEL_DISCRETE_6 = (1<<16) | 6, + QT_CHANNEL_LABEL_DISCRETE_7 = (1<<16) | 7, + QT_CHANNEL_LABEL_DISCRETE_8 = (1<<16) | 8, + QT_CHANNEL_LABEL_DISCRETE_9 = (1<<16) | 9, + QT_CHANNEL_LABEL_DISCRETE_10 = (1<<16) | 10, + QT_CHANNEL_LABEL_DISCRETE_11 = (1<<16) | 11, + QT_CHANNEL_LABEL_DISCRETE_12 = (1<<16) | 12, + QT_CHANNEL_LABEL_DISCRETE_13 = (1<<16) | 13, + QT_CHANNEL_LABEL_DISCRETE_14 = (1<<16) | 14, + QT_CHANNEL_LABEL_DISCRETE_15 = (1<<16) | 15, + QT_CHANNEL_LABEL_DISCRETE_65535 = (1<<16) | 65535, +} lsmash_channel_label; + +typedef enum +{ + QT_CHANNEL_BIT_LEFT = 1, + QT_CHANNEL_BIT_RIGHT = 1<<1, + QT_CHANNEL_BIT_CENTER = 1<<2, + QT_CHANNEL_BIT_LFE_SCREEN = 1<<3, + QT_CHANNEL_BIT_LEFT_SURROUND = 1<<4, /* WAVE: "Back Left" */ + QT_CHANNEL_BIT_RIGHT_SURROUND = 1<<5, /* WAVE: "Back Right" */ + QT_CHANNEL_BIT_LEFT_CENTER = 1<<6, + QT_CHANNEL_BIT_RIGHT_CENTER = 1<<7, + QT_CHANNEL_BIT_CENTER_SURROUND = 1<<8, /* WAVE: "Back Center" */ + QT_CHANNEL_BIT_LEFT_SURROUND_DIRECT = 1<<9, /* WAVE: "Side Left" */ + QT_CHANNEL_BIT_RIGHT_SURROUND_DIRECT = 1<<10, /* WAVE: "Side Right" */ + QT_CHANNEL_BIT_TOP_CENTER_SURROUND = 1<<11, + QT_CHANNEL_BIT_VERTICAL_HEIGHT_LEFT = 1<<12, /* WAVE: "Top Front Left" */ + QT_CHANNEL_BIT_VERTICAL_HEIGHT_CENTER = 1<<13, /* WAVE: "Top Front Center" */ + QT_CHANNEL_BIT_VERTICAL_HEIGHT_RIGHT = 1<<14, /* WAVE: "Top Front Right" */ + QT_CHANNEL_BIT_TOP_BACK_LEFT = 1<<15, + QT_CHANNEL_BIT_TOP_BACK_CENTER = 1<<16, + QT_CHANNEL_BIT_TOP_BACK_RIGHT = 1<<17, + QT_CHANNEL_BIT_FULL = 0x3ffff, +} lsmash_channel_bitmap; + +typedef enum +{ + QT_CHANNEL_FLAGS_ALL_OFF = 0, + QT_CHANNEL_FLAGS_RECTANGULAR_COORDINATES = 1, + QT_CHANNEL_FLAGS_SPHERICAL_COORDINATES = 1<<1, + QT_CHANNEL_FLAGS_METERS = 1<<2, +} lsmash_channel_flags; + +typedef enum +{ + /* indices for accessing the coordinates array in Channel Descriptions */ + /* for rectangulare coordinates */ + QT_CHANNEL_COORDINATES_LEFT_RIGHT = 0, /* Negative is left and positive is right. */ + QT_CHANNEL_COORDINATES_BACK_FRONT = 1, /* Negative is back and positive is front. */ + QT_CHANNEL_COORDINATES_DOWN_UP = 2, /* Negative is below ground level, 0 is ground level, and positive is above ground level. */ + /* for spherical coordinates */ + QT_CHANNEL_COORDINATES_AZIMUTH = 0, /* 0 is front center, positive is right, negative is left. This is measured in degrees. */ + QT_CHANNEL_COORDINATES_ELEVATION = 1, /* +90 is zenith, 0 is horizontal, -90 is nadir. This is measured in degrees. */ + QT_CHANNEL_COORDINATES_DISTANCE = 2, /* The units are described by flags. */ +} lsmash_channel_coordinates_index; + +typedef enum +{ + /* channel abbreviations: + * L - left + * R - right + * C - center + * Ls - left surround + * Rs - right surround + * Cs - center surround + * Rls - rear left surround + * Rrs - rear right surround + * Lw - left wide + * Rw - right wide + * Lsd - left surround direct + * Rsd - right surround direct + * Lc - left center + * Rc - right center + * Ts - top surround + * Vhl - vertical height left + * Vhc - vertical height center + * Vhr - vertical height right + * Lt - left matrix total. for matrix encoded stereo. + * Rt - right matrix total. for matrix encoded stereo. */ + + /* General layouts */ + QT_CHANNEL_LAYOUT_USE_CHANNEL_DESCRIPTIONS = 0, /* use the array of Channel Descriptions to define the mapping. */ + QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP = 1<<16, /* use the bitmap to define the mapping. */ + + QT_CHANNEL_LAYOUT_MONO = (100<<16) | 1, /* a standard mono stream */ + QT_CHANNEL_LAYOUT_STEREO = (101<<16) | 2, /* a standard stereo stream (L R) - implied playback */ + QT_CHANNEL_LAYOUT_STEREO_HEADPHONES = (102<<16) | 2, /* a standard stereo stream (L R) - implied headphone playback */ + QT_CHANNEL_LAYOUT_MATRIX_STEREO = (103<<16) | 2, /* a matrix encoded stereo stream (Lt, Rt) */ + QT_CHANNEL_LAYOUT_MID_SIDE = (104<<16) | 2, /* mid/side recording */ + QT_CHANNEL_LAYOUT_XY = (105<<16) | 2, /* coincident mic pair (often 2 figure 8's) */ + QT_CHANNEL_LAYOUT_BINAURAL = (106<<16) | 2, /* binaural stereo (left, right) */ + QT_CHANNEL_LAYOUT_AMBISONIC_B_FORMAT = (107<<16) | 4, /* W, X, Y, Z */ + + QT_CHANNEL_LAYOUT_QUADRAPHONIC = (108<<16) | 4, /* front left, front right, back left, back right */ + + QT_CHANNEL_LAYOUT_PENTAGONAL = (109<<16) | 5, /* left, right, rear left, rear right, center */ + + QT_CHANNEL_LAYOUT_HEXAGONAL = (110<<16) | 6, /* left, right, rear left, rear right, center, rear */ + + QT_CHANNEL_LAYOUT_OCTAGONAL = (111<<16) | 8, /* front left, front right, rear left, rear right, + * front center, rear center, side left, side right */ + + QT_CHANNEL_LAYOUT_CUBE = (112<<16) | 8, /* left, right, rear left, rear right, + * top left, top right, top rear left, top rear right */ + + /* MPEG defined layouts */ + QT_CHANNEL_LAYOUT_MPEG_1_0 = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_MPEG_2_0 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_MPEG_3_0_A = (113<<16) | 3, /* L R C */ + QT_CHANNEL_LAYOUT_MPEG_3_0_B = (114<<16) | 3, /* C L R */ + QT_CHANNEL_LAYOUT_MPEG_4_0_A = (115<<16) | 4, /* L R C Cs */ + QT_CHANNEL_LAYOUT_MPEG_4_0_B = (116<<16) | 4, /* C L R Cs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_A = (117<<16) | 5, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_B = (118<<16) | 5, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_MPEG_5_0_C = (119<<16) | 5, /* L C R Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_D = (120<<16) | 5, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_1_A = (121<<16) | 6, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_1_B = (122<<16) | 6, /* L R Ls Rs C LFE */ + QT_CHANNEL_LAYOUT_MPEG_5_1_C = (123<<16) | 6, /* L C R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_MPEG_5_1_D = (124<<16) | 6, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_MPEG_6_1_A = (125<<16) | 7, /* L R C LFE Ls Rs Cs */ + QT_CHANNEL_LAYOUT_MPEG_7_1_A = (126<<16) | 8, /* L R C LFE Ls Rs Lc Rc */ + QT_CHANNEL_LAYOUT_MPEG_7_1_B = (127<<16) | 8, /* C Lc Rc L R Ls Rs LFE (doc: IS-13818-7 MPEG2-AAC Table 3.1) */ + QT_CHANNEL_LAYOUT_MPEG_7_1_C = (128<<16) | 8, /* L R C LFE Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_EMAGIC_DEFAULT_7_1 = (129<<16) | 8, /* L R Ls Rs C LFE Lc Rc */ + QT_CHANNEL_LAYOUT_SMPTE_DTV = (130<<16) | 8, /* L R C LFE Ls Rs Lt Rt */ + + /* ITU defined layouts */ + QT_CHANNEL_LAYOUT_ITU_1_0 = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_ITU_2_0 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + + QT_CHANNEL_LAYOUT_ITU_2_1 = (131<<16) | 3, /* L R Cs */ + QT_CHANNEL_LAYOUT_ITU_2_2 = (132<<16) | 4, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_A, /* L R C */ + QT_CHANNEL_LAYOUT_ITU_3_1 = QT_CHANNEL_LAYOUT_MPEG_4_0_A, /* L R C Cs */ + + QT_CHANNEL_LAYOUT_ITU_3_2 = QT_CHANNEL_LAYOUT_MPEG_5_0_A, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_2_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_4_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_C, /* L R C LFE Ls Rs Rls Rrs */ + + /* DVD defined layouts */ + QT_CHANNEL_LAYOUT_DVD_0 = QT_CHANNEL_LAYOUT_MONO, /* C (mono) */ + QT_CHANNEL_LAYOUT_DVD_1 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_DVD_2 = QT_CHANNEL_LAYOUT_ITU_2_1, /* L R Cs */ + QT_CHANNEL_LAYOUT_DVD_3 = QT_CHANNEL_LAYOUT_ITU_2_2, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_4 = (133<<16) | 3, /* L R LFE */ + QT_CHANNEL_LAYOUT_DVD_5 = (134<<16) | 4, /* L R LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_6 = (135<<16) | 5, /* L R LFE Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_7 = QT_CHANNEL_LAYOUT_MPEG_3_0_A, /* L R C */ + QT_CHANNEL_LAYOUT_DVD_8 = QT_CHANNEL_LAYOUT_MPEG_4_0_A, /* L R C Cs */ + QT_CHANNEL_LAYOUT_DVD_9 = QT_CHANNEL_LAYOUT_MPEG_5_0_A, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_10 = (136<<16) | 4, /* L R C LFE */ + QT_CHANNEL_LAYOUT_DVD_11 = (137<<16) | 5, /* L R C LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_12 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + /* 13 through 17 are duplicates of 8 through 12. */ + QT_CHANNEL_LAYOUT_DVD_13 = QT_CHANNEL_LAYOUT_DVD_8, /* L R C Cs */ + QT_CHANNEL_LAYOUT_DVD_14 = QT_CHANNEL_LAYOUT_DVD_9, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_15 = QT_CHANNEL_LAYOUT_DVD_10, /* L R C LFE */ + QT_CHANNEL_LAYOUT_DVD_16 = QT_CHANNEL_LAYOUT_DVD_11, /* L R C LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_17 = QT_CHANNEL_LAYOUT_DVD_12, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_18 = (138<<16) | 5, /* L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DVD_19 = QT_CHANNEL_LAYOUT_MPEG_5_0_B, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_DVD_20 = QT_CHANNEL_LAYOUT_MPEG_5_1_B, /* L R Ls Rs C LFE */ + + /* These are the symmetrical layouts. */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_4 = QT_CHANNEL_LAYOUT_QUADRAPHONIC, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5 = QT_CHANNEL_LAYOUT_PENTAGONAL, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6 = QT_CHANNEL_LAYOUT_HEXAGONAL, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_8 = QT_CHANNEL_LAYOUT_OCTAGONAL, + /* These are the surround-based layouts. */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_B, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6_0 = (139<<16) | 6, /* L R Ls Rs C Cs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_0 = (140<<16) | 7, /* L R Ls Rs C Rls Rrs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_0_FRONT = (148<<16) | 7, /* L R Ls Rs C Lc Rc */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6_1 = QT_CHANNEL_LAYOUT_MPEG_6_1_A, /* L R C LFE Ls Rs Cs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_C, /* L R C LFE Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_1_FRONT = QT_CHANNEL_LAYOUT_MPEG_7_1_A, /* L R C LFE Ls Rs Lc Rc */ + + QT_CHANNEL_LAYOUT_AAC_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_B, /* C L R */ + QT_CHANNEL_LAYOUT_AAC_QUADRAPHONIC = QT_CHANNEL_LAYOUT_QUADRAPHONIC, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_AAC_4_0 = QT_CHANNEL_LAYOUT_MPEG_4_0_B, /* C L R Cs */ + QT_CHANNEL_LAYOUT_AAC_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_D, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_AAC_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_D, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_AAC_6_0 = (141<<16) | 6, /* C L R Ls Rs Cs */ + QT_CHANNEL_LAYOUT_AAC_6_1 = (142<<16) | 7, /* C L R Ls Rs Cs LFE */ + QT_CHANNEL_LAYOUT_AAC_7_0 = (143<<16) | 7, /* C L R Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_AAC_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_B, /* C Lc Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_AAC_OCTAGONAL = (144<<16) | 8, /* C L R Ls Rs Rls Rrs Cs */ + + QT_CHANNEL_LAYOUT_TMH_10_2_STD = (145<<16) | 16, /* L R C Vhc Lsd Rsd Ls Rs Vhl Vhr Lw Rw Csd Cs LFE1 LFE2 */ + QT_CHANNEL_LAYOUT_TMH_10_2_FULL = (146<<16) | 21, /* TMH_10_2_std plus: Lc Rc HI VI Haptic */ + + QT_CHANNEL_LAYOUT_AC3_1_0_1 = (149<<16) | 2, /* C LFE */ + QT_CHANNEL_LAYOUT_AC3_3_0 = (150<<16) | 3, /* L C R */ + QT_CHANNEL_LAYOUT_AC3_3_1 = (151<<16) | 4, /* L C R Cs */ + QT_CHANNEL_LAYOUT_AC3_3_0_1 = (152<<16) | 4, /* L C R LFE */ + QT_CHANNEL_LAYOUT_AC3_2_1_1 = (153<<16) | 4, /* L R Cs LFE */ + QT_CHANNEL_LAYOUT_AC3_3_1_1 = (154<<16) | 5, /* L C R Cs LFE */ + + QT_CHANNEL_LAYOUT_EAC_6_0_A = (155<<16) | 6, /* L C R Ls Rs Cs */ + QT_CHANNEL_LAYOUT_EAC_7_0_A = (156<<16) | 7, /* L C R Ls Rs Rls Rrs */ + + QT_CHANNEL_LAYOUT_EAC3_6_1_A = (157<<16) | 7, /* L C R Ls Rs LFE Cs */ + QT_CHANNEL_LAYOUT_EAC3_6_1_B = (158<<16) | 7, /* L C R Ls Rs LFE Ts */ + QT_CHANNEL_LAYOUT_EAC3_6_1_C = (159<<16) | 7, /* L C R Ls Rs LFE Vhc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_A = (160<<16) | 8, /* L C R Ls Rs LFE Rls Rrs */ + QT_CHANNEL_LAYOUT_EAC3_7_1_B = (161<<16) | 8, /* L C R Ls Rs LFE Lc Rc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_C = (162<<16) | 8, /* L C R Ls Rs LFE Lsd Rsd */ + QT_CHANNEL_LAYOUT_EAC3_7_1_D = (163<<16) | 8, /* L C R Ls Rs LFE Lw Rw */ + QT_CHANNEL_LAYOUT_EAC3_7_1_E = (164<<16) | 8, /* L C R Ls Rs LFE Vhl Vhr */ + + QT_CHANNEL_LAYOUT_EAC3_7_1_F = (165<<16) | 8, /* L C R Ls Rs LFE Cs Ts */ + QT_CHANNEL_LAYOUT_EAC3_7_1_G = (166<<16) | 8, /* L C R Ls Rs LFE Cs Vhc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_H = (167<<16) | 8, /* L C R Ls Rs LFE Ts Vhc */ + + QT_CHANNEL_LAYOUT_DTS_3_1 = (168<<16) | 4, /* C L R LFE */ + QT_CHANNEL_LAYOUT_DTS_4_1 = (169<<16) | 5, /* C L R Cs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_0_A = (170<<16) | 6, /* Lc Rc L R Ls Rs */ + QT_CHANNEL_LAYOUT_DTS_6_0_B = (171<<16) | 6, /* C L R Rls Rrs Ts */ + QT_CHANNEL_LAYOUT_DTS_6_0_C = (172<<16) | 6, /* C Cs L R Rls Rrs */ + QT_CHANNEL_LAYOUT_DTS_6_1_A = (173<<16) | 7, /* Lc Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_B = (174<<16) | 7, /* C L R Rls Rrs Ts LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_C = (175<<16) | 7, /* C Cs L R Rls Rrs LFE */ + QT_CHANNEL_LAYOUT_DTS_7_0 = (176<<16) | 7, /* Lc C Rc L R Ls Rs */ + QT_CHANNEL_LAYOUT_DTS_7_1 = (177<<16) | 8, /* Lc C Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_8_0_A = (178<<16) | 8, /* Lc Rc L R Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_DTS_8_0_B = (179<<16) | 8, /* Lc C Rc L R Ls Cs Rs */ + QT_CHANNEL_LAYOUT_DTS_8_1_A = (180<<16) | 9, /* Lc Rc L R Ls Rs Rls Rrs LFE */ + QT_CHANNEL_LAYOUT_DTS_8_1_B = (181<<16) | 9, /* Lc C Rc L R Ls Cs Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_D = (182<<16) | 7, /* C L R Ls Rs LFE Cs */ + + QT_CHANNEL_LAYOUT_ALAC_MONO = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_ALAC_STEREO = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_ALAC_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_B, /* C L R */ + QT_CHANNEL_LAYOUT_ALAC_4_0 = QT_CHANNEL_LAYOUT_MPEG_4_0_B, /* C L R Cs */ + QT_CHANNEL_LAYOUT_ALAC_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_D, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_ALAC_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_D, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_ALAC_6_1 = QT_CHANNEL_LAYOUT_AAC_6_1, /* C L R Ls Rs Cs LFE */ + QT_CHANNEL_LAYOUT_ALAC_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_B, /* C Lc Rc L R Ls Rs LFE */ + + QT_CHANNEL_LAYOUT_DISCRETE_IN_ORDER = 147<<16, /* needs to be ORed with the actual number of channels */ + QT_CHANNEL_LAYOUT_UNKNOWN = 0xffff0000, /* needs to be ORed with the actual number of channels */ +} lsmash_channel_layout_tag; + +typedef enum +{ + /* In MP4 and/or ISO base media file format, if in a presentation all tracks have neither track_in_movie nor track_in_preview set, + * then all tracks shall be treated as if both flags were set on all tracks. */ + ISOM_TRACK_ENABLED = 0x000001, /* Track_enabled: Indicates that the track is enabled. + * A disabled track is treated as if it were not present. */ + ISOM_TRACK_IN_MOVIE = 0x000002, /* Track_in_movie: Indicates that the track is used in the presentation. */ + ISOM_TRACK_IN_PREVIEW = 0x000004, /* Track_in_preview: Indicates that the track is used when previewing the presentation. */ + + QT_TRACK_IN_POSTER = 0x000008, /* Track_in_poster: Indicates that the track is used in the movie's poster. (only defined in QuickTime file format) */ +} lsmash_track_mode; + +typedef enum +{ + ISOM_SCALING_METHOD_FILL = 1, + ISOM_SCALING_METHOD_HIDDEN = 2, + ISOM_SCALING_METHOD_MEET = 3, + ISOM_SCALING_METHOD_SLICE_X = 4, + ISOM_SCALING_METHOD_SLICE_Y = 5, +} lsmash_scaling_method; + +typedef enum +{ + ISOM_EDIT_MODE_NORMAL = 1<<16, + ISOM_EDIT_MODE_DWELL = 0, + ISOM_EDIT_MODE_EMPTY = -1, +} lsmash_edit_mode; + +typedef enum +{ + /* allow_ealier */ + QT_SAMPLE_EARLIER_PTS_ALLOWED = 1, + /* leading */ + ISOM_SAMPLE_LEADING_UNKNOWN = 0, + ISOM_SAMPLE_IS_UNDECODABLE_LEADING = 1, + ISOM_SAMPLE_IS_NOT_LEADING = 2, + ISOM_SAMPLE_IS_DECODABLE_LEADING = 3, + /* independent */ + ISOM_SAMPLE_INDEPENDENCY_UNKNOWN = 0, + ISOM_SAMPLE_IS_NOT_INDEPENDENT = 1, + ISOM_SAMPLE_IS_INDEPENDENT = 2, + /* disposable */ + ISOM_SAMPLE_DISPOSABLE_UNKNOWN = 0, + ISOM_SAMPLE_IS_NOT_DISPOSABLE = 1, + ISOM_SAMPLE_IS_DISPOSABLE = 2, + /* redundant */ + ISOM_SAMPLE_REDUNDANCY_UNKNOWN = 0, + ISOM_SAMPLE_HAS_REDUNDANCY = 1, + ISOM_SAMPLE_HAS_NO_REDUNDANCY = 2, +} lsmash_sample_dependency; + +/* objectTypeIndication */ +typedef enum { + MP4SYS_OBJECT_TYPE_Forbidden = 0x00, /* Forbidden */ + MP4SYS_OBJECT_TYPE_Systems_ISO_14496_1 = 0x01, /* Systems ISO/IEC 14496-1 */ + /* For all 14496-1 streams unless specifically indicated to the contrary. + Scene Description scenes, which are identified with StreamType=0x03, using + this object type value shall use the BIFSConfig. */ + MP4SYS_OBJECT_TYPE_Systems_ISO_14496_1_BIFSv2 = 0x02, /* Systems ISO/IEC 14496-1 */ + /* This object type shall be used, with StreamType=0x03, for Scene + Description streams that use the BIFSv2Config. + Its use with other StreamTypes is reserved. */ + + MP4SYS_OBJECT_TYPE_Interaction_Stream = 0x03, /* Interaction Stream */ + MP4SYS_OBJECT_TYPE_Extended_BIFS = 0x04, /* Extended BIFS */ + /* Used, with StreamType=0x03, for Scene Description streams that use the BIFSConfigEx; its use with + other StreamTypes is reserved. (Was previously reserved for MUCommandStream but not used for that purpose.) */ + MP4SYS_OBJECT_TYPE_AFX_Stream = 0x05, /* AFX Stream */ + /* Used, with StreamType=0x03, for Scene Description streams that use the AFXConfig; + its use with other StreamTypes is reserved. */ + MP4SYS_OBJECT_TYPE_Font_Data_Stream = 0x06, /* Font Data Stream */ + MP4SYS_OBJECT_TYPE_Synthetised_Texture = 0x07, /* Synthetised Texture */ + MP4SYS_OBJECT_TYPE_Text_Stream = 0x08, /* Text Stream */ + + MP4SYS_OBJECT_TYPE_Visual_ISO_14496_2 = 0x20, /* Visual ISO/IEC 14496-2 */ + MP4SYS_OBJECT_TYPE_Visual_H264_ISO_14496_10 = 0x21, /* Visual ITU-T Recommendation H.264 | ISO/IEC 14496-10 */ + /* The actual object types are within the DecoderSpecificInfo and defined in H.264 | 14496-10. */ + MP4SYS_OBJECT_TYPE_Parameter_Sets_H_264_ISO_14496_10 = 0x22, /* Parameter Sets for ITU-T Recommendation H.264 | ISO/IEC 14496-10 */ + /* The actual object types are within the DecoderSpecificInfo and defined in 14496-2. */ + + MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3 = 0x40, /* Audio ISO/IEC 14496-3 (MPEG-4 Audio) */ + //MP4SYS_OBJECT_TYPE_MP4A_AUDIO = 0x40, + /* The actual object types are defined in 14496-3 and are in the DecoderSpecificInfo as specified in 14496-3. */ + + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Simple_Profile = 0x60, /* Visual ISO/IEC 13818-2 Simple Profile (MPEG-2 Video) */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Main_Profile = 0x61, /* Visual ISO/IEC 13818-2 Main Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_SNR_Profile = 0x62, /* Visual ISO/IEC 13818-2 SNR Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Spatial_Profile = 0x63, /* Visual ISO/IEC 13818-2 Spatial Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_High_Profile = 0x64, /* Visual ISO/IEC 13818-2 High Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_422_Profile = 0x65, /* Visual ISO/IEC 13818-2 422 Profile */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile = 0x66, /* Audio ISO/IEC 13818-7 Main Profile (MPEG-2 Audio)(AAC) */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_LC_Profile = 0x67, /* Audio ISO/IEC 13818-7 LowComplexity Profile */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_SSR_Profile = 0x68, /* Audio ISO/IEC 13818-7 Scaleable Sampling Rate Profile */ + /* For streams kinda 13818-7 the decoder specific information consists of the ADIF header if present + (or none if not present) and an access unit is a "raw_data_block()" as defined in 13818-7. */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3 = 0x69, /* Audio ISO/IEC 13818-3 (MPEG-2 BC-Audio)(redefined MPEG-1 Audio in MPEG-2) */ + /* For streams kinda 13818-3 the decoder specific information is empty since all necessary data is in the bitstream frames itself. + The access units in this case are the "frame()" bitstream element as is defined in 11172-3. */ + MP4SYS_OBJECT_TYPE_Visual_ISO_11172_2 = 0x6A, /* Visual ISO/IEC 11172-2 (MPEG-1 Video) */ + MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3 = 0x6B, /* Audio ISO/IEC 11172-3 (MPEG-1 Audio) */ + MP4SYS_OBJECT_TYPE_Visual_ISO_10918_1 = 0x6C, /* Visual ISO/IEC 10918-1 (JPEG) */ + MP4SYS_OBJECT_TYPE_PNG = 0x6D, /* Portable Network Graphics */ + MP4SYS_OBJECT_TYPE_Visual_ISO_15444_1_JPEG2000 = 0x6E, /* Visual ISO/IEC 15444-1 (JPEG 2000) */ + + /* FIXME: rename these symbols to be explaining, rather than based on four cc */ + MP4SYS_OBJECT_TYPE_EVRC_AUDIO = 0xA0, /* EVRC Voice */ + MP4SYS_OBJECT_TYPE_SSMV_AUDIO = 0xA1, /* SMV Voice */ + MP4SYS_OBJECT_TYPE_3GPP2_CMF = 0xA2, /* 3GPP2 Compact Multimedia Format (CMF) */ + MP4SYS_OBJECT_TYPE_VC_1_VIDEO = 0xA3, /* SMPTE VC-1 Video */ + MP4SYS_OBJECT_TYPE_DRAC_VIDEO = 0xA4, /* Dirac Video Coder */ + MP4SYS_OBJECT_TYPE_AC_3_AUDIO = 0xA5, /* AC-3 Audio */ + MP4SYS_OBJECT_TYPE_EC_3_AUDIO = 0xA6, /* Enhanced AC-3 audio */ + MP4SYS_OBJECT_TYPE_DRA1_AUDIO = 0xA7, /* DRA Audio */ + MP4SYS_OBJECT_TYPE_G719_AUDIO = 0xA8, /* ITU G.719 Audio */ + MP4SYS_OBJECT_TYPE_DTSC_AUDIO = 0xA9, /* DTS Coherent Acoustics audio */ + MP4SYS_OBJECT_TYPE_DTSH_AUDIO = 0xAA, /* DTS-HD High Resolution Audio */ + MP4SYS_OBJECT_TYPE_DTSL_AUDIO = 0xAB, /* DTS-HD Master Audio */ + MP4SYS_OBJECT_TYPE_DTSE_AUDIO = 0xAC, /* DTS Express low bit rate audio, also known as DTS LBR */ + + MP4SYS_OBJECT_TYPE_SQCP_AUDIO = 0xE1, /* 13K Voice */ + + MP4SYS_OBJECT_TYPE_NONE = 0xFF, /* no object type specified */ + /* Streams with this value with a StreamType indicating a systems stream (values 1,2,3,6,7,8,9) + shall be treated as if the ObjectTypeIndication had been set to 0x01. */ +} lsmash_mp4sys_object_type_indication; + +/* streamType */ +typedef enum { + MP4SYS_STREAM_TYPE_Forbidden = 0x00, /* Forbidden */ + MP4SYS_STREAM_TYPE_ObjectDescriptorStream = 0x01, /* ObjectDescriptorStream */ + MP4SYS_STREAM_TYPE_ClockReferenceStream = 0x02, /* ClockReferenceStream */ + MP4SYS_STREAM_TYPE_SceneDescriptionStream = 0x03, /* SceneDescriptionStream */ + MP4SYS_STREAM_TYPE_VisualStream = 0x04, /* VisualStream */ + MP4SYS_STREAM_TYPE_AudioStream = 0x05, /* AudioStream */ + MP4SYS_STREAM_TYPE_MPEG7Stream = 0x06, /* MPEG7Stream */ + MP4SYS_STREAM_TYPE_IPMPStream = 0x07, /* IPMPStream */ + MP4SYS_STREAM_TYPE_ObjectContentInfoStream = 0x08, /* ObjectContentInfoStream */ + MP4SYS_STREAM_TYPE_MPEGJStream = 0x09, /* MPEGJStream */ + MP4SYS_STREAM_TYPE_InteractionStream = 0x0A, /* Interaction Stream */ + MP4SYS_STREAM_TYPE_IPMPToolStream = 0x0B, /* IPMPToolStream */ + MP4SYS_STREAM_TYPE_FontDataStream = 0x0C, /* FontDataStream */ + MP4SYS_STREAM_TYPE_StreamingText = 0x0D, /* StreamingText */ +} lsmash_mp4sys_stream_type; + +/* Audio Object Types */ +typedef enum { + MP4A_AUDIO_OBJECT_TYPE_NULL = 0, + MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN = 1, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_LC = 2, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_SSR = 3, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_LTP = 4, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_SBR = 5, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_scalable = 6, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_TwinVQ = 7, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_CELP = 8, /* ISO/IEC 14496-3 subpart 3 */ + MP4A_AUDIO_OBJECT_TYPE_HVXC = 9, /* ISO/IEC 14496-3 subpart 2 */ + MP4A_AUDIO_OBJECT_TYPE_TTSI = 12, /* ISO/IEC 14496-3 subpart 6 */ + MP4A_AUDIO_OBJECT_TYPE_Main_synthetic = 13, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_Wavetable_synthesis = 14, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_General_MIDI = 15, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_Algorithmic_Synthesis_Audio_FX = 16, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC = 17, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP = 19, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable = 20, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ = 21, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_BSAC = 22, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD = 23, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_CELP = 24, /* ISO/IEC 14496-3 subpart 3 */ + MP4A_AUDIO_OBJECT_TYPE_ER_HVXC = 25, /* ISO/IEC 14496-3 subpart 2 */ + MP4A_AUDIO_OBJECT_TYPE_ER_HILN = 26, /* ISO/IEC 14496-3 subpart 7 */ + MP4A_AUDIO_OBJECT_TYPE_ER_Parametric = 27, /* ISO/IEC 14496-3 subpart 2 and 7 */ + MP4A_AUDIO_OBJECT_TYPE_SSC = 28, /* ISO/IEC 14496-3 subpart 8 */ + MP4A_AUDIO_OBJECT_TYPE_PS = 29, /* ISO/IEC 14496-3 subpart 8 */ + MP4A_AUDIO_OBJECT_TYPE_MPEG_Surround = 30, /* ISO/IEC 23003-1 */ + MP4A_AUDIO_OBJECT_TYPE_ESCAPE = 31, + MP4A_AUDIO_OBJECT_TYPE_Layer_1 = 32, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_Layer_2 = 33, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_Layer_3 = 34, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_DST = 35, /* ISO/IEC 14496-3 subpart 10 */ + MP4A_AUDIO_OBJECT_TYPE_ALS = 36, /* ISO/IEC 14496-3 subpart 11 */ + MP4A_AUDIO_OBJECT_TYPE_SLS = 37, /* ISO/IEC 14496-3 subpart 12 */ + MP4A_AUDIO_OBJECT_TYPE_SLS_non_core = 38, /* ISO/IEC 14496-3 subpart 12 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_ELD = 39, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_SMR_Simple = 40, /* ISO/IEC 14496-23 */ + MP4A_AUDIO_OBJECT_TYPE_SMR_Main = 41, /* ISO/IEC 14496-23 */ + MP4A_AUDIO_OBJECT_TYPE_SAOC = 43, /* ISO/IEC 23003-2 */ +} lsmash_mp4a_AudioObjectType; + +/* see ISO/IEC 14496-3 Signaling of SBR, SBR Signaling and Corresponding Decoder Behavior */ +typedef enum { + MP4A_AAC_SBR_NOT_SPECIFIED = 0x0, /* not mention to SBR presence. Implicit signaling. */ + MP4A_AAC_SBR_NONE, /* explicitly signals SBR does not present. Useless in general. */ + MP4A_AAC_SBR_BACKWARD_COMPATIBLE, /* explicitly signals SBR present. Recommended method to signal SBR. */ + MP4A_AAC_SBR_HIERARCHICAL /* SBR exists. SBR dedicated method. */ +} lsmash_mp4a_aac_sbr_mode; + +typedef enum +{ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_NONE = 0, /* not random access point */ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_SYNC = 1, /* sync sample */ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP = 1, /* the first sample of a closed GOP */ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_OPEN_RAP = 2, /* the first sample of an open GOP */ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_UNKNOWN_RAP = 3, /* the first sample of an open or closed GOP */ + ISOM_SAMPLE_RANDOM_ACCESS_TYPE_RECOVERY = 4, /* starting point of gradual decoder refresh */ + + QT_SAMPLE_RANDOM_ACCESS_TYPE_NONE = 0, /* not random access point */ + QT_SAMPLE_RANDOM_ACCESS_TYPE_SYNC = 1, /* sync sample */ + QT_SAMPLE_RANDOM_ACCESS_TYPE_PARTIAL_SYNC = 2, /* partial sync sample */ + QT_SAMPLE_RANDOM_ACCESS_TYPE_CLOSED_RAP = 1, /* the first sample of a closed GOP */ + QT_SAMPLE_RANDOM_ACCESS_TYPE_OPEN_RAP = 2, /* the first sample of an open GOP */ +} lsmash_random_access_type; + +typedef enum +{ + /* UTF String type */ + ITUNES_METADATA_TYPE_ALBUM_NAME = LSMASH_4CC( 0xA9, 'a', 'l', 'b' ), /* Album Name */ + ITUNES_METADATA_TYPE_ARTIST = LSMASH_4CC( 0xA9, 'A', 'R', 'T' ), /* Artist */ + ITUNES_METADATA_TYPE_USER_COMMENT = LSMASH_4CC( 0xA9, 'c', 'm', 't' ), /* User Comment */ + ITUNES_METADATA_TYPE_RELEASE_DATE = LSMASH_4CC( 0xA9, 'd', 'a', 'y' ), /* YYYY-MM-DD format string (may be incomplete, i.e. only year) */ + ITUNES_METADATA_TYPE_ENCODED_BY = LSMASH_4CC( 0xA9, 'e', 'n', 'c' ), /* Person or company that encoded the recording */ + ITUNES_METADATA_TYPE_USER_GENRE = LSMASH_4CC( 0xA9, 'g', 'e', 'n' ), /* User Genre user-specified string */ + ITUNES_METADATA_TYPE_0XA9_GROUPING = LSMASH_4CC( 0xA9, 'g', 'r', 'p' ), /* Grouping */ + ITUNES_METADATA_TYPE_LYRICS = LSMASH_4CC( 0xA9, 'l', 'y', 'r' ), /* Lyrics */ + ITUNES_METADATA_TYPE_TITLE = LSMASH_4CC( 0xA9, 'n', 'a', 'm' ), /* Title / Song Name */ + ITUNES_METADATA_TYPE_TRACK_SUBTITLE = LSMASH_4CC( 0xA9, 's', 't', '3' ), /* Track Sub-Title */ + ITUNES_METADATA_TYPE_ENCODING_TOOL = LSMASH_4CC( 0xA9, 't', 'o', 'o' ), /* Software which encoded the recording */ + ITUNES_METADATA_TYPE_COMPOSER = LSMASH_4CC( 0xA9, 'w', 'r', 't' ), /* Composer */ + ITUNES_METADATA_TYPE_ALBUM_ARTIST = LSMASH_4CC( 'a', 'A', 'R', 'T' ), /* Artist for the whole album (if different than the individual tracks) */ + ITUNES_METADATA_TYPE_PODCAST_CATEGORY = LSMASH_4CC( 'c', 'a', 't', 'g' ), /* Podcast Category */ + ITUNES_METADATA_TYPE_COPYRIGHT = LSMASH_4CC( 'c', 'p', 'r', 't' ), /* Copyright */ + ITUNES_METADATA_TYPE_DESCRIPTION = LSMASH_4CC( 'd', 'e', 's', 'c' ), /* Description (limited to 255 bytes) */ + ITUNES_METADATA_TYPE_GROUPING = LSMASH_4CC( 'g', 'r', 'u', 'p' ), /* Grouping */ + ITUNES_METADATA_TYPE_PODCAST_KEYWORD = LSMASH_4CC( 'k', 'e', 'y', 'w' ), /* Podcast Keywords */ + ITUNES_METADATA_TYPE_LONG_DESCRIPTION = LSMASH_4CC( 'l', 'd', 'e', 's' ), /* Long Description */ + ITUNES_METADATA_TYPE_PURCHASE_DATE = LSMASH_4CC( 'p', 'u', 'r', 'd' ), /* Purchase Date */ + ITUNES_METADATA_TYPE_TV_EPISODE_ID = LSMASH_4CC( 't', 'v', 'e', 'n' ), /* TV Episode ID */ + ITUNES_METADATA_TYPE_TV_NETWORK = LSMASH_4CC( 't', 'v', 'n', 'n' ), /* TV Network Name */ + ITUNES_METADATA_TYPE_TV_SHOW_NAME = LSMASH_4CC( 't', 'v', 's', 'h' ), /* TV Show Name */ + ITUNES_METADATA_TYPE_ITUNES_PURCHASE_ACCOUNT_ID = LSMASH_4CC( 'a', 'p', 'I', 'D' ), /* iTunes Account Used for Purchase */ + + /* Integer type + * (X): X means length of bytes */ + ITUNES_METADATA_TYPE_EPISODE_GLOBAL_ID = LSMASH_4CC( 'e', 'g', 'i', 'd' ), /* (1) Episode Global Unique ID */ + ITUNES_METADATA_TYPE_PREDEFINED_GENRE = LSMASH_4CC( 'g', 'n', 'r', 'e' ), /* (4) Pre-defined Genre / Enumerated value from ID3 tag set, plus 1 */ + ITUNES_METADATA_TYPE_PODCAST_URL = LSMASH_4CC( 'p', 'u', 'r', 'l' ), /* (?) Podcast URL */ + ITUNES_METADATA_TYPE_CONTENT_RATING = LSMASH_4CC( 'r', 't', 'n', 'g' ), /* (1) Content Rating / Does song have explicit content? 0: none, 2: clean, 4: explicit */ + ITUNES_METADATA_TYPE_MEDIA_TYPE = LSMASH_4CC( 's', 't', 'i', 'k' ), /* (1) Media Type */ + ITUNES_METADATA_TYPE_BEATS_PER_MINUTE = LSMASH_4CC( 't', 'm', 'p', 'o' ), /* (2) Beats Per Minute */ + ITUNES_METADATA_TYPE_TV_EPISODE = LSMASH_4CC( 't', 'v', 'e', 's' ), /* (4) TV Episode */ + ITUNES_METADATA_TYPE_TV_SEASON = LSMASH_4CC( 't', 'v', 's', 'n' ), /* (4) TV Season */ + ITUNES_METADATA_TYPE_ITUNES_ACCOUNT_TYPE = LSMASH_4CC( 'a', 'k', 'I', 'D' ), /* (1) iTunes Account Type / 0: iTunes, 1: AOL */ + ITUNES_METADATA_TYPE_ITUNES_ARTIST_ID = LSMASH_4CC( 'a', 't', 'I', 'D' ), /* (4) iTunes Artist ID */ + ITUNES_METADATA_TYPE_ITUNES_COMPOSER_ID = LSMASH_4CC( 'c', 'm', 'I', 'D' ), /* (4) iTunes Composer ID */ + ITUNES_METADATA_TYPE_ITUNES_CATALOG_ID = LSMASH_4CC( 'c', 'n', 'I', 'D' ), /* (4) iTunes Catalog ID */ + ITUNES_METADATA_TYPE_ITUNES_TV_GENRE_ID = LSMASH_4CC( 'g', 'e', 'I', 'D' ), /* (4) iTunes TV Genre ID */ + ITUNES_METADATA_TYPE_ITUNES_PLAYLIST_ID = LSMASH_4CC( 'p', 'l', 'I', 'D' ), /* (8) iTunes Playlist ID */ + ITUNES_METADATA_TYPE_ITUNES_COUNTRY_CODE = LSMASH_4CC( 's', 'f', 'I', 'D' ), /* (4) iTunes Country Code */ + + /* Boolean type */ + ITUNES_METADATA_TYPE_DISC_COMPILATION = LSMASH_4CC( 'c', 'p', 'i', 'l' ), /* Disc Compilation / Is disc part of a compilation? 0: No, 1: Yes */ + ITUNES_METADATA_TYPE_HIGH_DEFINITION_VIDEO = LSMASH_4CC( 'h', 'd', 'v', 'd' ), /* High Definition Video / 0: No, 1: Yes */ + ITUNES_METADATA_TYPE_PODCAST = LSMASH_4CC( 'p', 'c', 's', 't' ), /* Podcast / 0: No, 1: Yes */ + ITUNES_METADATA_TYPE_GAPLESS_PLAYBACK = LSMASH_4CC( 'p', 'g', 'a', 'p' ), /* Gapless Playback / 0: insert gap, 1: no gap */ + + /* Binary type */ + ITUNES_METADATA_TYPE_COVER_ART = LSMASH_4CC( 'c', 'o', 'v', 'r' ), /* One or more cover art images */ + ITUNES_METADATA_TYPE_DISC_NUMBER = LSMASH_4CC( 'd', 'i', 's', 'k' ), /* Disc Number */ + ITUNES_METADATA_TYPE_TRACH_NUMBER = LSMASH_4CC( 't', 'r', 'k', 'n' ), /* Track Number */ + + /* Custom type */ + ITUNES_METADATA_TYPE_CUSTOM = LSMASH_4CC( '-', '-', '-', '-' ), /* Custom */ +} lsmash_itunes_metadata_type; + +/* public data types */ +typedef struct +{ + uint32_t complete; /* recovery point: the identifier necessary for the recovery from its starting point to be completed */ + uint32_t identifier; /* the identifier for samples + * If this identifier equals a certain identifier of recovery point, + * then this sample is the recovery point of the earliest group in the pool. */ +} lsmash_post_roll_t; + +typedef struct +{ + uint16_t distance; /* pre-roll distance for representing audio decoder delay derived from composition + * For example, typical AAC encoding uses a transform over consecutive sets of 2048 audio samples, + * applied every 1024 audio samples (MDCTs are overlapped). + * For correct audio to be decoded, both transforms for any period of 1024 audio samples are needed. + * For this AAC stream, therefore, shall be set to 1 (one AAC access unit). + * Note: the number of priming audio sample i.e. encoder delay shall be represented by media_time in edit. */ +} lsmash_pre_roll_t; + +typedef struct +{ + uint8_t allow_earlier; + uint8_t leading; + uint8_t independent; + uint8_t disposable; + uint8_t redundant; + lsmash_random_access_type random_access_type; + lsmash_post_roll_t post_roll; + lsmash_pre_roll_t pre_roll; +} lsmash_sample_property_t; + +typedef struct +{ + uint32_t length; + uint8_t *data; + uint64_t dts; + uint64_t cts; + uint32_t index; + lsmash_sample_property_t prop; +} lsmash_sample_t; + +typedef struct +{ + uint64_t dts; + uint64_t cts; +} lsmash_media_ts_t; + +typedef struct +{ + uint32_t sample_count; + lsmash_media_ts_t *timestamp; +} lsmash_media_ts_list_t; + +/* */ +typedef int (*lsmash_adhoc_remux_callback)( void* param, uint64_t done, uint64_t total ); +typedef struct { + uint64_t buffer_size; + lsmash_adhoc_remux_callback func; + void* param; +} lsmash_adhoc_remux_t; + +/* L-SMASH's original structure, summary of audio/video stream configuration */ +/* NOTE: For audio, currently assuming AAC-LC. */ + +#define LSMASH_BASE_SUMMARY \ + lsmash_codec_type sample_type; /* Codec type */ \ + lsmash_mp4sys_object_type_indication object_type_indication; \ + lsmash_mp4sys_stream_type stream_type; \ + void *exdata; /* typically payload of DecoderSpecificInfo (that's called AudioSpecificConfig in mp4a) */ \ + uint32_t exdata_length; /* length of exdata */ \ + uint32_t max_au_length; /* buffer length for 1 access unit, typically max size of 1 audio/video frame */ + +typedef struct +{ + LSMASH_BASE_SUMMARY +} lsmash_summary_t; + +typedef struct +{ + LSMASH_BASE_SUMMARY + // mp4a_audioProfileLevelIndication pli; /* I wonder we should have this or not. */ + lsmash_mp4a_AudioObjectType aot; /* Detailed codec type. If not mp4a, just ignored. */ + uint32_t frequency; /* Even if the stream is HE-AAC v1/SBR, this is base AAC's one. */ + uint32_t channels; /* Even if the stream is HE-AAC v2/SBR+PS, this is base AAC's one. */ + uint32_t bit_depth; /* If AAC, AAC stream itself does not mention to accuracy (bit_depth of decoded PCM data), we assume 16bit. */ + uint32_t samples_in_frame; /* Even if the stream is HE-AAC/aacPlus/SBR(+PS), this is base AAC's one, so 1024. */ + lsmash_mp4a_aac_sbr_mode sbr_mode; /* SBR treatment. Currently we always set this as mp4a_AAC_SBR_NOT_SPECIFIED(Implicit signaling). + * User can set this for treatment in other way. */ + lsmash_channel_layout_tag layout_tag; /* channel layout */ + lsmash_channel_bitmap bitmap; /* Only available when layout_tag is set to QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP. */ + /* LPCM descriptions */ + uint8_t sample_format; /* 0: integer, 1: floating point */ + uint8_t endianness; /* 0: big endian, 1: little endian */ + uint8_t signedness; /* 0: unsigned, 1: signed / This is only valid when sample format is integer. */ + uint8_t packed; /* 0: unpacked, 1: packed i.e. the sample bits occupy the entire available bits for the channel */ + uint8_t alignment; /* 0: low bit placement, 1: high bit placement / This is only valid when unpacked. */ + uint8_t interleaved; /* 0: non-interleaved, 1: interleaved i.e. the samples for each channels are stored in one stream */ +} lsmash_audio_summary_t; + +typedef struct +{ + LSMASH_BASE_SUMMARY + // mp4sys_visualProfileLevelIndication pli; /* I wonder we should have this or not. */ + // lsmash_mp4v_VideoObjectType vot; /* Detailed codec type. If not mp4v, just ignored. */ + uint32_t timescale; /* media timescale + * User can't set this parameter manually. */ + uint32_t timebase; /* increment unit of timestamp + * User can't set this parameter manually. */ + uint8_t vfr; /* whether a stream is assumed as variable frame rate + * User can't set this parameter manually. */ + uint8_t full_range; + uint32_t width; /* pixel counts of width samples have */ + uint32_t height; /* pixel counts of height samples have */ + uint32_t crop_top; + uint32_t crop_left; + uint32_t crop_bottom; + uint32_t crop_right; + uint32_t par_h; /* horizontal factor of pixel aspect ratio */ + uint32_t par_v; /* vertical factor of pixel aspect ratio */ + lsmash_scaling_method scaling_method; /* If not set, video samples are scaled into the visual presentation region to fill it. */ + lsmash_color_parameter primaries; + lsmash_color_parameter transfer; + lsmash_color_parameter matrix; +} lsmash_video_summary_t; + +typedef struct +{ + lsmash_media_type handler_type; /* the nature of the media + * You can't change handler_type through this parameter manually. */ + uint32_t timescale; /* media timescale: timescale for this media */ + uint64_t duration; /* the duration of this media, expressed in the media timescale + * You can't set this parameter manually. */ + uint8_t roll_grouping; /* roll recovery grouping present + * Require 'avc1' brand, or ISO Base Media File Format version 2 or later. */ + uint8_t rap_grouping; /* random access point grouping present + * Require ISO Base Media File Format version 6 or later. */ + /* Use either type of language code. */ + uint16_t MAC_language; /* Macintosh language code for this media */ + uint16_t ISO_language; /* ISO 639-2/T language code for this media */ + /* human-readable name for the track type (for debugging and inspection purposes) */ + char *media_handler_name; + char *data_handler_name; + /* Any user shouldn't use the following parameters. */ + PRIVATE char media_handler_name_shadow[256]; + PRIVATE char data_handler_name_shadow[256]; +} lsmash_media_parameters_t; + +typedef struct +{ + lsmash_track_mode mode; + uint32_t track_ID; /* an integer that uniquely identifies the track + * Don't set to value already used except for zero value. + * Zero value don't override established track_ID. */ + uint64_t duration; /* the duration of this track expressed in the movie timescale units + * If there is any edit, your setting is ignored. */ + int16_t alternate_group; /* an integer that specifies a group or collection of tracks + * If this field is not 0, it should be the same for tracks that contain alternate data for one another + * and different for tracks belonging to different such groups. + * Only one track within an alternate group should be played or streamed at any one time. + * Note: alternate_group is ignored when a file is read as an MPEG-4. */ + /* The following parameters are ignored when a file is read as an MPEG-4 or 3GPP file format. */ + int16_t video_layer; /* the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer. */ + int16_t audio_volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + int32_t matrix[9]; /* transformation matrix for the video + * Each value represents, in order, a, b, u, c, d, v, x, y and w. + * All the values in a matrix are stored as 16.16 fixed-point values, + * except for u, v and w, which are stored as 2.30 fixed-point values. + * Not all derived specifications use matrices. + * If a matrix is used, the point (p, q) is transformed into (p', q') using the matrix as follows: + * | a b u | + * (p, q, 1) * | c d v | = z * (p', q', 1) + * | x y w | + * p' = (a * p + c * q + x) / z; q' = (b * p + d * q + y) / z; z = u * p + v * q + w + * Note: transformation matrix is applied after scaling to display size up to display_width and display_height. */ + /* visual presentation region size */ + uint32_t display_width; /* visual presentation region size of horizontal direction as fixed point 16.16 number. */ + uint32_t display_height; /* visual presentation region size of vertical direction as fixed point 16.16 number. */ + /* */ + uint8_t aperture_modes; /* track aperture modes present + * This feature is only available under QuickTime file format. + * Automatically disabled if multiple sample description is present or scaling method is specified. */ +} lsmash_track_parameters_t; + +typedef struct +{ + lsmash_brand_type major_brand; /* the best used brand */ + lsmash_brand_type *brands; /* the list of compatible brands */ + uint32_t number_of_brands; /* the number of compatible brands used in the movie */ + uint32_t minor_version; /* minor version of best used brand */ + double max_chunk_duration; /* max duration per chunk in seconds. 0.5 is default value. */ + double max_async_tolerance; /* max tolerance, in seconds, for amount of interleaving asynchronization between tracks. + * 2.0 is default value. At least twice of max_chunk_duration is used. */ + uint64_t max_chunk_size; /* max size per chunk in bytes. 4*1024*1024 (4MiB) is default value. */ + uint64_t max_read_size; /* max size of reading from a chunk at a time. 4*1024*1024 (4MiB) is default value. */ + uint32_t timescale; /* movie timescale: timescale for the entire presentation */ + uint64_t duration; /* the duration, expressed in movie timescale, of the longest track + * You can't set this parameter manually. */ + uint32_t number_of_tracks; /* the number of tracks in the movie + * You can't set this parameter manually. */ + /* The following parameters are recognized only when a file is read as an Apple MPEG-4 or QuickTime file fromat. */ + int32_t playback_rate; /* fixed point 16.16 number. 0x00010000 is normal forward playback and default value. */ + int32_t playback_volume; /* fixed point 8.8 number. 0x0100 is full volume and default value. */ + int32_t preview_time; /* the time value in the movie at which the preview begins */ + int32_t preview_duration; /* the duration of the movie preview in movie timescale units */ + int32_t poster_time; /* the time value of the time of the movie poster */ + /* Any user shouldn't use the following parameter. */ + PRIVATE lsmash_brand_type brands_shadow[50]; +} lsmash_movie_parameters_t; + +typedef enum +{ + LSMASH_BOOLEAN_FALSE = 0, + LSMASH_BOOLEAN_TRUE = 1 +} lsmash_boolean_t; + +typedef struct lsmash_root_tag lsmash_root_t; +typedef void lsmash_itunes_metadata_list_t; + +/* public functions */ +int lsmash_add_sps_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *sps, uint32_t sps_size ); +int lsmash_add_pps_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *pps, uint32_t pps_size ); +int lsmash_add_spsext_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint8_t *spsext, uint32_t spsext_size ); +int lsmash_add_sample_entry( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_type, void* summary ); + +int lsmash_add_btrt( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number ); +int lsmash_add_free( lsmash_root_t *root, uint8_t *data, uint64_t data_length ); + +int lsmash_write_free( lsmash_root_t *root ); + +uint64_t lsmash_get_media_duration( lsmash_root_t *root, uint32_t track_ID ); +uint64_t lsmash_get_track_duration( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_last_sample_delta( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_start_time_offset( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_composition_to_decode_shift( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_media_timescale( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_movie_timescale( lsmash_root_t *root ); + +int lsmash_set_avc_config( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, + uint8_t configurationVersion, uint8_t AVCProfileIndication, uint8_t profile_compatibility, + uint8_t AVCLevelIndication, uint8_t lengthSizeMinusOne, + uint8_t chroma_format, uint8_t bit_depth_luma_minus8, uint8_t bit_depth_chroma_minus8 ); +int lsmash_set_last_sample_delta( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_delta ); +int lsmash_set_free( lsmash_root_t *root, uint8_t *data, uint64_t data_length ); +int lsmash_set_tyrant_chapter( lsmash_root_t *root, char *file_name, int add_bom ); + +int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ); +int lsmash_create_object_descriptor( lsmash_root_t *root ); + +int lsmash_create_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint64_t segment_duration, int64_t media_time, int32_t media_rate ); +int lsmash_modify_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint32_t entry_number, uint64_t segment_duration, int64_t media_time, int32_t media_rate ); +int lsmash_delete_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID ); + +int lsmash_update_media_modification_time( lsmash_root_t *root, uint32_t track_ID ); +int lsmash_update_track_modification_time( lsmash_root_t *root, uint32_t track_ID ); +int lsmash_update_movie_modification_time( lsmash_root_t *root ); +int lsmash_update_track_duration( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ); + +uint16_t lsmash_pack_iso_language( char *iso_language ); + +lsmash_root_t *lsmash_open_movie( const char *filename, lsmash_file_mode mode ); +void lsmash_initialize_movie_parameters( lsmash_movie_parameters_t *param ); +int lsmash_set_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ); +int lsmash_get_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ); +uint32_t lsmash_create_track( lsmash_root_t *root, lsmash_media_type media_type ); +uint32_t lsmash_get_track_ID( lsmash_root_t *root, uint32_t track_number ); +void lsmash_initialize_track_parameters( lsmash_track_parameters_t *param ); +int lsmash_set_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ); +int lsmash_get_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ); +void lsmash_initialize_media_parameters( lsmash_media_parameters_t *param ); +int lsmash_set_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ); +int lsmash_get_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ); +lsmash_sample_t *lsmash_create_sample( uint32_t size ); +int lsmash_sample_alloc( lsmash_sample_t *sample, uint32_t size ); +void lsmash_delete_sample( lsmash_sample_t *sample ); +int lsmash_append_sample( lsmash_root_t *root, uint32_t track_ID, lsmash_sample_t *sample ); +int lsmash_flush_pooled_samples( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ); +int lsmash_finish_movie( lsmash_root_t *root, lsmash_adhoc_remux_t* remux ); +void lsmash_discard_boxes( lsmash_root_t *root ); +void lsmash_destroy_root( lsmash_root_t *root ); + +int lsmash_create_fragment_movie( lsmash_root_t *root ); +int lsmash_create_fragment_empty_duration( lsmash_root_t *root, uint32_t track_ID, uint32_t duration ); + +void lsmash_delete_track( lsmash_root_t *root, uint32_t track_ID ); +void lsmash_delete_tyrant_chapter( lsmash_root_t *root ); + +/* track_ID == 0 means copyright declaration applies to the entire presentation, not an entire track. */ +int lsmash_set_copyright( lsmash_root_t *root, uint32_t track_ID, uint16_t ISO_language, char *notice ); + +/* When type is specified as ITUNES_METADATA_TYPE_CUSTOM, meaning is mandatory while name is optionally valid. + * Otherwise, meaning and name are just ignored. */ +int lsmash_set_itunes_metadata_string( lsmash_root_t *root, lsmash_itunes_metadata_type type, char *value, char *meaning, char *name ); +int lsmash_set_itunes_metadata_integer( lsmash_root_t *root, lsmash_itunes_metadata_type type, uint64_t value, char *meaning, char *name ); +int lsmash_set_itunes_metadata_boolean( lsmash_root_t *root, lsmash_itunes_metadata_type type, lsmash_boolean_t value, char *meaning, char *name ); + +#ifdef LSMASH_DEMUXER_ENABLED +int lsmash_print_movie( lsmash_root_t *root ); + +/* This function might output BOM on Windows. Make sure that this is the first function that outputs something to stdout. */ +int lsmash_print_chapter_list( lsmash_root_t *root ); + +int lsmash_copy_timeline_map( lsmash_root_t *dst, uint32_t dst_track_ID, lsmash_root_t *src, uint32_t src_track_ID ); +int lsmash_copy_decoder_specific_info( lsmash_root_t *dst, uint32_t dst_track_ID, lsmash_root_t *src, uint32_t src_track_ID ); +int lsmash_construct_timeline( lsmash_root_t *root, uint32_t track_ID ); +void lsmash_destruct_timeline( lsmash_root_t *root, uint32_t track_ID ); +int lsmash_get_last_sample_delta_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t *last_sample_delta ); +int lsmash_get_dts_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint64_t *dts ); +int lsmash_get_cts_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint64_t *cts ); +int lsmash_get_composition_to_decode_shift_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t *ctd_shift ); +int lsmash_get_closest_random_accessible_point_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint32_t *rap_number ); +int lsmash_get_closest_random_accessible_point_detail_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, + uint32_t *rap_number, lsmash_random_access_type *type, uint32_t *leading, uint32_t *distance ); +uint32_t lsmash_get_sample_count_in_media_timeline( lsmash_root_t *root, uint32_t track_ID ); +uint32_t lsmash_get_max_sample_size_in_media_timeline( lsmash_root_t *root, uint32_t track_ID ); +lsmash_sample_t *lsmash_get_sample_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number ); +int lsmash_check_sample_existence_in_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number ); + +lsmash_itunes_metadata_list_t *lsmash_export_itunes_metadata( lsmash_root_t *root ); +int lsmash_import_itunes_metadata( lsmash_root_t *root, lsmash_itunes_metadata_list_t *list ); +void lsmash_destroy_itunes_metadata( lsmash_itunes_metadata_list_t *list ); + +int lsmash_set_media_timestamps( lsmash_root_t *root, uint32_t track_ID, lsmash_media_ts_list_t *ts_list ); +int lsmash_get_media_timestamps( lsmash_root_t *root, uint32_t track_ID, lsmash_media_ts_list_t *ts_list ); +int lsmash_get_media_timeline_shift( lsmash_root_t *root, uint32_t track_ID, int32_t *timeline_shift ); +#endif + +/* to facilitate to make exdata (typically DecoderSpecificInfo or AudioSpecificConfig). */ +int lsmash_setup_AudioSpecificConfig( lsmash_audio_summary_t* summary ); +int lsmash_summary_add_exdata( lsmash_summary_t *summary, void* exdata, uint32_t exdata_length ); + +lsmash_summary_t *lsmash_create_summary( lsmash_mp4sys_stream_type stream_type ); +void lsmash_cleanup_summary( lsmash_summary_t *summary ); + +#undef PRIVATE + +#endif diff --git a/output/mp4/mp4a.c b/output/mp4/mp4a.c new file mode 100644 index 0000000..bc1d3f4 --- /dev/null +++ b/output/mp4/mp4a.c @@ -0,0 +1,901 @@ +/***************************************************************************** + * mp4a.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#define MP4A_INTERNAL +#include "mp4a.h" + +#include +#include + +/*************************************************************************** + implementation of part of ISO/IEC 14496-3 (ISO/IEC 14496-1 relevant) +***************************************************************************/ + +/* ISO/IEC 14496-3 samplingFrequencyIndex */ +/* ISO/IEC 14496-3 Sampling frequency mapping */ +const uint32_t mp4a_sampling_frequency_table[13][5] = { + /* threshold, exact, idx_for_ga, idx_for_sbr, idx */ + { 92017, 96000, 0x0, 0xF, 0x0 }, /* SBR is not allowed */ + { 75132, 88200, 0x1, 0xF, 0x1 }, /* SBR is not allowed */ + { 55426, 64000, 0x2, 0xF, 0x2 }, /* SBR is not allowed */ + { 46009, 48000, 0x3, 0x0, 0x3 }, + { 37566, 44100, 0x4, 0x1, 0x4 }, + { 27713, 32000, 0x5, 0x2, 0x5 }, + { 23004, 24000, 0x6, 0x3, 0x6 }, + { 18783, 22050, 0x7, 0x4, 0x7 }, + { 13856, 16000, 0x8, 0x5, 0x8 }, + { 11502, 12000, 0x9, 0x6, 0x9 }, + { 9391, 11025, 0xA, 0x7, 0xA }, + { 8000, 8000, 0xB, 0x8, 0xB }, + { 0, 7350, 0xB, 0xF, 0xC } /* samplingFrequencyIndex for GASpecificConfig is 0xB (same as 8000Hz). */ +}; + +/* ISO/IEC 14496-3 Interface to ISO/IEC 14496-1 (MPEG-4 Systems), Syntax of AudioSpecificConfig(). */ +/* This structure is represent of regularized AudioSpecificConfig. */ +/* for actual definition, see Syntax of GetAudioObjectType() for audioObjectType and extensionAudioObjectType. */ +typedef struct { + lsmash_mp4a_aac_sbr_mode sbr_mode; /* L-SMASH's original, including sbrPresent flag. */ + lsmash_mp4a_AudioObjectType audioObjectType; + uint8_t samplingFrequencyIndex; + uint32_t samplingFrequency; + uint8_t channelConfiguration; + lsmash_mp4a_AudioObjectType extensionAudioObjectType; + uint8_t extensionSamplingFrequencyIndex; + uint8_t extensionSamplingFrequency; + /* if( audioObjectType in + #[ 1, 2, 3, 4, 6, 7, *17, *19, *20, *21, *22, *23 ] // GASpecificConfig, AAC relatives and TwinVQ, BSAC + [ 8 ] // CelpSpecificConfig, not supported + [ 9 ] // HvxcSpecificConfig, not supported + [ 12 ] // TTSSpecificConfig, not supported + [ 13, 14, 15, 16 ] // StructuredAudioSpecificConfig, notsupported + [ 24 ] // ErrorResilientCelpSpecificConfig, notsupported + [ 25 ] // ErrorResilientHvxcSpecificConfig, notsupported + [ 26, 27 ] // ParametricSpecificConfig, notsupported + [ 28 ] // SSCSpecificConfig, notsupported + #[ 32, 33, 34 ] // MPEG_1_2_SpecificConfig + [ 35 ] // DSTSpecificConfig, notsupported + ){ */ + void* deepAudioSpecificConfig; // L-SMASH's original name, reperesents such as GASpecificConfig. */ + /* } */ + /* + // error resilient stuff, not supported + if( audioObjectType in [17, 19, 20, 21, 22, 23, 24, 25, 26, 27] ){ + uint8_t epConfig // 2bit + if( epConfig == 2 || epConfig == 3 ){ + ErrorProtectionSpecificConfig(); + } + if( epConfig == 3 ){ + uint8_t directMapping; // 1bit, currently always 1. + if( !directMapping ){ + // tbd + } + } + } + */ +} mp4a_AudioSpecificConfig_t; + +/* ISO/IEC 14496-3 Decoder configuration (GASpecificConfig), Syntax of GASpecificConfig() */ +/* ISO/IEC 14496-3 GASpecificConfig(), Sampling frequency mapping */ +typedef struct { + uint8_t frameLengthFlag; /* FIXME: AAC_SSR: shall be 0, Others: depends, but noramally 0. */ + uint8_t dependsOnCoreCoder; /* FIXME: used if scalable AAC. */ + /* + if( dependsOnCoreCoder ){ + uint16_t coreCoderDelay; // 14bits + } + */ + uint8_t extensionFlag; /* 1bit, 1 if ErrorResilience */ + /* if( !channelConfiguration ){ */ + void* program_config_element; /* currently not supported. */ + /* } */ + /* + // we do not support AAC_scalable + if( (audioObjectType == MP4A_AUDIO_OBJECT_TYPE_AAC_scalable) || (audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable) ){ + uint8_t layerNr; // 3bits + } + */ + /* + // we do not support special AACs + if( extensionFlag ){ + if( audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_BSAC ){ + uint8_t numOfSubFrame; // 5bits + uint8_t layer_length; // 11bits + } + if( audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP + || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD + ){ + uint8_t aacSectionDataResilienceFlag; // 1bit + uint8_t aacScalefactorDataResilienceFlag; // 1bit + uint8_t aacSpectralDataResilienceFlag; // 1bit + } + uint8_t extensionFlag3; // 1bit + if( extensionFlag3 ){ + // tbd in version 3 + } + } + */ +} mp4a_GASpecificConfig_t; + +/* ISO/IEC 14496-3 MPEG_1_2_SpecificConfig */ +typedef struct { + uint8_t extension; /* shall be 0. */ +} mp4a_MPEG_1_2_SpecificConfig_t; + +/* ISO/IEC 14496-3 ALSSpecificConfig */ +typedef struct +{ + uint32_t size; + uint8_t *data; + uint32_t samp_freq; + uint16_t channels; + uint8_t resolution; + uint8_t floating; + uint16_t frame_length; + uint16_t max_order; + uint8_t block_switching; + uint8_t bgmc_mode; + uint8_t RLSLMS; +} mp4a_ALSSpecificConfig_t; + +static inline void mp4a_remove_GASpecificConfig( mp4a_GASpecificConfig_t* gasc ) +{ + debug_if( !gasc ) + return; + if( gasc->program_config_element ) + free( gasc->program_config_element ); + free( gasc ); +} + +static inline void mp4a_remove_MPEG_1_2_SpecificConfig( mp4a_MPEG_1_2_SpecificConfig_t* mpeg_1_2_sc ) +{ + debug_if( mpeg_1_2_sc ) + free( mpeg_1_2_sc ); +} + +void mp4a_remove_AudioSpecificConfig( mp4a_AudioSpecificConfig_t* asc ) +{ + if( !asc ) + return; + switch( asc->audioObjectType ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: + mp4a_remove_GASpecificConfig( (mp4a_GASpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + mp4a_remove_MPEG_1_2_SpecificConfig( (mp4a_MPEG_1_2_SpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + default: + if( asc->deepAudioSpecificConfig ) + free( asc->deepAudioSpecificConfig ); + break; + } + free( asc ); +} + +/* ADIF/PCE(program config element) style GASpecificConfig is not not supported. */ +/* channelConfig/samplingFrequencyIndex will be used when we support ADIF/PCE style GASpecificConfig. */ +static mp4a_GASpecificConfig_t* mp4a_create_GASpecificConfig( uint8_t samplingFrequencyIndex, uint8_t channelConfig, lsmash_mp4a_AudioObjectType aot ) +{ + debug_if( aot != MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LC + && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_SSR && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + && aot != MP4A_AUDIO_OBJECT_TYPE_TwinVQ ) + return NULL; + if( samplingFrequencyIndex > 0xB || channelConfig == 0 || channelConfig == 7 ) + return NULL; + mp4a_GASpecificConfig_t *gasc = (mp4a_GASpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_GASpecificConfig_t) ); + if( !gasc ) + return NULL; + gasc->frameLengthFlag = 0; /* FIXME: AAC_SSR: shall be 0, Others: depends, but noramally 0. */ + gasc->dependsOnCoreCoder = 0; /* FIXME: used if scalable AAC. */ + switch( aot ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: + gasc->extensionFlag = 0; + break; + /* currently never occures. */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: + gasc->extensionFlag = 1; + break; + default: + gasc->extensionFlag = 0; + break; + } + return gasc; +} + +static mp4a_MPEG_1_2_SpecificConfig_t* mp4a_create_MPEG_1_2_SpecificConfig() +{ + mp4a_MPEG_1_2_SpecificConfig_t *mpeg_1_2_sc = (mp4a_MPEG_1_2_SpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_MPEG_1_2_SpecificConfig_t) ); + if( !mpeg_1_2_sc ) + return NULL; + mpeg_1_2_sc->extension = 0; /* shall be 0. */ + return mpeg_1_2_sc; +} + +static mp4a_ALSSpecificConfig_t *mp4a_create_ALSSpecificConfig( uint8_t *exdata, uint32_t exdata_length ) +{ + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_ALSSpecificConfig_t) ); + if( !alssc ) + return NULL; + alssc->data = lsmash_memdup( exdata, exdata_length ); + if( !alssc->data ) + return NULL; + alssc->size = exdata_length; + return alssc; +} + +/* Currently, only normal AAC, MPEG_1_2 are supported. + For AAC, other than normal AAC, such as AAC_scalable, ER_AAC_xxx, are not supported. + ADIF/PCE(program config element) style AudioSpecificConfig is not supported. + aot shall not be MP4A_AUDIO_OBJECT_TYPE_SBR even if you wish to signal SBR explicitly, use sbr_mode instead. + Frequency/channels shall be base AAC's one, even if SBR/PS. + If other than AAC with SBR, sbr_mode shall be MP4A_AAC_SBR_NOT_SPECIFIED. */ +mp4a_AudioSpecificConfig_t *mp4a_create_AudioSpecificConfig( + lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length +) +{ + if( aot != MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LC + && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_SSR && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + && aot != MP4A_AUDIO_OBJECT_TYPE_TwinVQ && aot != MP4A_AUDIO_OBJECT_TYPE_ALS ) + return NULL; + if( frequency == 0 ) + return NULL; + + uint8_t channelConfig; + switch( channels ){ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + channelConfig = channels; + break; + case 8: + channelConfig = 7; + break; + default: + return NULL; + } + + mp4a_AudioSpecificConfig_t *asc = (mp4a_AudioSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_AudioSpecificConfig_t) ); + if( !asc ) + return NULL; + + asc->sbr_mode = sbr_mode; + asc->audioObjectType = aot; + asc->channelConfiguration = channelConfig; + + uint8_t samplingFrequencyIndex = 0xF; + uint8_t i = 0x0; + if( sbr_mode != MP4A_AAC_SBR_NOT_SPECIFIED + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_LC + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_SSR + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + || aot == MP4A_AUDIO_OBJECT_TYPE_SBR ) + { + while( frequency < mp4a_sampling_frequency_table[i][0] ) + i++; + asc->samplingFrequencyIndex = frequency == mp4a_sampling_frequency_table[i][1] ? i : 0xF; + asc->samplingFrequency = frequency; + samplingFrequencyIndex = mp4a_sampling_frequency_table[i][2]; + /* SBR settings */ + if( sbr_mode != MP4A_AAC_SBR_NOT_SPECIFIED ) + { + /* SBR limitation */ + /* see ISO/IEC 14496-3 Levels within the profiles / Levels for the High Efficiency AAC Profile */ + if( i < 0x3 ) + { + free( asc ); + return NULL; + } + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_SBR; + } + else + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_NULL; + + if( sbr_mode == MP4A_AAC_SBR_BACKWARD_COMPATIBLE || sbr_mode == MP4A_AAC_SBR_BACKWARD_COMPATIBLE ) + { + asc->extensionSamplingFrequency = frequency * 2; + asc->extensionSamplingFrequencyIndex = i == 0xC ? 0xF : mp4a_sampling_frequency_table[i][3]; + } + else + { + asc->extensionSamplingFrequencyIndex = asc->samplingFrequencyIndex; + asc->extensionSamplingFrequency = asc->samplingFrequency; + } + } + else + { + while( i < 0xD && frequency != mp4a_sampling_frequency_table[i][1] ) + i++; + asc->samplingFrequencyIndex = i != 0xD ? i : 0xF; + asc->samplingFrequency = frequency; + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_NULL; + asc->extensionSamplingFrequencyIndex = asc->samplingFrequencyIndex; + asc->extensionSamplingFrequency = asc->samplingFrequency; + } + + switch( aot ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: +#if 0 /* FIXME: here, stop currently unsupported codecs. */ + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: /* NOTE: I think we already have a support for TwinVQ, but how to test this? */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: +#endif + asc->deepAudioSpecificConfig = mp4a_create_GASpecificConfig( samplingFrequencyIndex, channelConfig, aot ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + asc->deepAudioSpecificConfig = mp4a_create_MPEG_1_2_SpecificConfig(); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + asc->deepAudioSpecificConfig = mp4a_create_ALSSpecificConfig( exdata, exdata_length ); + break; + default: + break; /* this case is trapped below. */ + } + if( !asc->deepAudioSpecificConfig ){ + free( asc ); + return NULL; + } + return asc; +} + +/* ADIF/PCE(program config element) style GASpecificConfig is not supported. */ +static void mp4a_put_GASpecificConfig( lsmash_bits_t* bits, mp4a_GASpecificConfig_t* gasc ) +{ + debug_if( !bits || !gasc ) + return; + lsmash_bits_put( bits, gasc->frameLengthFlag, 1 ); + lsmash_bits_put( bits, gasc->dependsOnCoreCoder, 1 ); + lsmash_bits_put( bits, gasc->extensionFlag, 1 ); +} + +static void mp4a_put_MPEG_1_2_SpecificConfig( lsmash_bits_t* bits, mp4a_MPEG_1_2_SpecificConfig_t* mpeg_1_2_sc ) +{ + debug_if( !bits || !mpeg_1_2_sc ) + return; + lsmash_bits_put( bits, mpeg_1_2_sc->extension, 1 ); /* shall be 0 */ +} + +static void mp4a_put_ALSSpecificConfig( lsmash_bits_t *bits, mp4a_ALSSpecificConfig_t *alssc ) +{ + debug_if( !bits || !alssc ) + return; + lsmash_bits_put( bits, 0, 5 ); /* fillBits for byte alignment */ + lsmash_bits_import_data( bits, alssc->data, alssc->size ); +} + +static inline void mp4a_put_AudioObjectType( lsmash_bits_t* bits, lsmash_mp4a_AudioObjectType aot ) +{ + if( aot > MP4A_AUDIO_OBJECT_TYPE_ESCAPE ) + { + lsmash_bits_put( bits, MP4A_AUDIO_OBJECT_TYPE_ESCAPE, 5 ); + lsmash_bits_put( bits, aot - MP4A_AUDIO_OBJECT_TYPE_ESCAPE - 1, 6 ); + } + else + lsmash_bits_put( bits, aot, 5 ); +} + +static inline void mp4a_put_SamplingFrequencyIndex( lsmash_bits_t* bits, uint8_t samplingFrequencyIndex, uint32_t samplingFrequency ) +{ + lsmash_bits_put( bits, samplingFrequencyIndex, 4 ); + if( samplingFrequencyIndex == 0xF ) + lsmash_bits_put( bits, samplingFrequency, 24 ); +} + +/* Currently, only normal AAC, MPEG_1_2 are supported. + For AAC, other than normal AAC, such as AAC_scalable, ER_AAC_xxx, are not supported. + ADIF/PCE(program config element) style AudioSpecificConfig is not supported either. */ +void mp4a_put_AudioSpecificConfig( lsmash_bs_t* bs, mp4a_AudioSpecificConfig_t* asc ) +{ + debug_if( !bs || !asc ) + return; + lsmash_bits_t bits; + lsmash_bits_init( &bits, bs ); + + if( asc->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + mp4a_put_AudioObjectType( &bits, asc->extensionAudioObjectType ); /* puts MP4A_AUDIO_OBJECT_TYPE_SBR */ + else + mp4a_put_AudioObjectType( &bits, asc->audioObjectType ); + mp4a_put_SamplingFrequencyIndex( &bits, asc->samplingFrequencyIndex, asc->samplingFrequency ); + lsmash_bits_put( &bits, asc->channelConfiguration, 4 ); + if( asc->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + { + mp4a_put_SamplingFrequencyIndex( &bits, asc->extensionSamplingFrequencyIndex, asc->extensionSamplingFrequency ); + mp4a_put_AudioObjectType( &bits, asc->audioObjectType ); + } + switch( asc->audioObjectType ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: +#if 0 /* FIXME: here, stop currently unsupported codecs */ + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: /* NOTE: I think we already have a support for TwinVQ, but how to test this? */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: +#endif + mp4a_put_GASpecificConfig( &bits, (mp4a_GASpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + mp4a_put_MPEG_1_2_SpecificConfig( &bits, (mp4a_MPEG_1_2_SpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + mp4a_put_ALSSpecificConfig( &bits, (mp4a_ALSSpecificConfig_t *)asc->deepAudioSpecificConfig ); + break; + default: + break; /* FIXME: do we have to return error? */ + } + + /* FIXME: Error Resiliant stuff omitted here. */ + + if( asc->sbr_mode == MP4A_AAC_SBR_BACKWARD_COMPATIBLE || asc->sbr_mode == MP4A_AAC_SBR_NONE ) + { + lsmash_bits_put( &bits, 0x2b7, 11 ); + mp4a_put_AudioObjectType( &bits, asc->extensionAudioObjectType ); /* puts MP4A_AUDIO_OBJECT_TYPE_SBR */ + if( asc->extensionAudioObjectType == MP4A_AUDIO_OBJECT_TYPE_SBR ) /* this is always true, due to current spec */ + { + /* sbrPresentFlag */ + if( asc->sbr_mode == MP4A_AAC_SBR_NONE ) + lsmash_bits_put( &bits, 0x0, 1 ); + else + { + lsmash_bits_put( &bits, 0x1, 1 ); + mp4a_put_SamplingFrequencyIndex( &bits, asc->extensionSamplingFrequencyIndex, asc->extensionSamplingFrequency ); + } + } + } + lsmash_bits_put_align( &bits ); +} + +static int mp4a_get_GASpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + mp4a_GASpecificConfig_t *gasc = (mp4a_GASpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_GASpecificConfig_t) ); + if( !gasc ) + return -1; + asc->deepAudioSpecificConfig = gasc; + gasc->frameLengthFlag = lsmash_bits_get( bits, 1 ); + gasc->dependsOnCoreCoder = lsmash_bits_get( bits, 1 ); + if( gasc->dependsOnCoreCoder ) + lsmash_bits_get( bits, 14 ); /* coreCoderDelay */ + gasc->extensionFlag = lsmash_bits_get( bits, 1 ); + return 0; +} + +static int mp4a_get_MPEG_1_2_SpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + lsmash_bits_get( bits, 1 ); + return 0; +} + +static int mp4a_get_ALSSpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_ALSSpecificConfig_t) ); + if( !alssc ) + return -1; + asc->deepAudioSpecificConfig = alssc; + lsmash_bits_get( bits, 32 ); /* als_id */ + alssc->samp_freq = lsmash_bits_get( bits, 32 ); + lsmash_bits_get( bits, 32 ); /* samples */ + alssc->channels = lsmash_bits_get( bits, 16 ); + lsmash_bits_get( bits, 3 ); /* file_type */ + alssc->resolution = lsmash_bits_get( bits, 3 ); + alssc->floating = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 1 ); /* msb_first */ + alssc->frame_length = lsmash_bits_get( bits, 16 ); + lsmash_bits_get( bits, 8 ); /* random_access */ + lsmash_bits_get( bits, 2 ); /* ra_flag */ + lsmash_bits_get( bits, 1 ); /* adapt_order */ + lsmash_bits_get( bits, 2 ); /* coef_table */ + lsmash_bits_get( bits, 1 ); /* long_term_prediction */ + alssc->max_order = lsmash_bits_get( bits, 10 ); + alssc->block_switching = lsmash_bits_get( bits, 2 ); + alssc->bgmc_mode = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 1 ); /* sb_part */ + lsmash_bits_get( bits, 1 ); /* joint_stereo */ + lsmash_bits_get( bits, 1 ); /* mc_coding */ + lsmash_bits_get( bits, 1 ); /* chan_config */ + lsmash_bits_get( bits, 1 ); /* chan_sort */ + lsmash_bits_get( bits, 1 ); /* crc_enabled */ + alssc->RLSLMS = lsmash_bits_get( bits, 1 ); + return 0; +} + +static mp4a_AudioSpecificConfig_t * mp4a_get_AudioSpecificConfig( lsmash_bits_t *bits, uint8_t *dsi_payload, uint32_t dsi_payload_length ) +{ + if( lsmash_bits_import_data( bits, dsi_payload, dsi_payload_length ) ) + return NULL; + mp4a_AudioSpecificConfig_t *asc = (mp4a_AudioSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_AudioSpecificConfig_t) ); + if( !asc ) + return NULL; + asc->audioObjectType = lsmash_bits_get( bits, 5 ); + if( asc->audioObjectType == 31 ) + asc->extensionAudioObjectType = asc->audioObjectType += 1 + lsmash_bits_get( bits, 6 ); + asc->samplingFrequencyIndex = lsmash_bits_get( bits, 4 ); + if( asc->samplingFrequencyIndex == 0xf ) + asc->samplingFrequency = lsmash_bits_get( bits, 24 ); + asc->channelConfiguration = lsmash_bits_get( bits, 4 ); + int ret = 0; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD : + ret = mp4a_get_GASpecificConfig( bits, asc ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + ret = mp4a_get_MPEG_1_2_SpecificConfig( bits, asc ); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS : + lsmash_bits_get( bits, 5 ); + ret = mp4a_get_ALSSpecificConfig( bits, asc ); + break; + default : + break; + } + return ret ? NULL : asc; +} + +int mp4a_setup_summary_from_AudioSpecificConfig( lsmash_audio_summary_t *summary, uint8_t *dsi_payload, uint32_t dsi_payload_length ) +{ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return -1; + mp4a_AudioSpecificConfig_t *asc = mp4a_get_AudioSpecificConfig( bits, dsi_payload, dsi_payload_length ); + if( !asc ) + goto fail; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + summary->stream_type = MP4SYS_STREAM_TYPE_AudioStream; + summary->exdata = NULL; + summary->exdata_length = 0; + summary->aot = asc->audioObjectType; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD : + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + if( asc->samplingFrequencyIndex == 0xf ) + summary->frequency = asc->samplingFrequency; + else + { + uint8_t i = 0x0; + while( i != 0xc ) + { + if( mp4a_sampling_frequency_table[i][2] == asc->samplingFrequencyIndex ) + { + summary->frequency = mp4a_sampling_frequency_table[i][1]; + break; + } + ++i; + } + if( i == 0xc ) + goto fail; + } + if( asc->channelConfiguration < 8 ) + summary->channels = asc->channelConfiguration != 7 ? asc->channelConfiguration : 8; + else + summary->channels = 0; /* reserved */ + summary->bit_depth = 16; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + summary->samples_in_frame = 1024; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + summary->samples_in_frame = 384; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + summary->samples_in_frame = 1152; + break; + default : + summary->samples_in_frame = !((mp4a_GASpecificConfig_t *)asc->deepAudioSpecificConfig)->frameLengthFlag ? 1024 : 960; + break; + } + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS : + { + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)asc->deepAudioSpecificConfig; + summary->frequency = alssc->samp_freq; + summary->channels = alssc->channels + 1; + summary->bit_depth = (alssc->resolution + 1) * 8; + summary->samples_in_frame = alssc->frame_length + 1; + break; + } + default : + break; + } + mp4a_remove_AudioSpecificConfig( asc ); + lsmash_bits_adhoc_cleanup( bits ); + return 0; +fail: + mp4a_remove_AudioSpecificConfig( asc ); + lsmash_bits_adhoc_cleanup( bits ); + return -1; +} + +/*************************************************************************** + audioProfileLevelIndication +***************************************************************************/ +/* NOTE: This function is not strictly preferable, but accurate. + The spec of audioProfileLevelIndication is too much complicated. */ +mp4a_audioProfileLevelIndication mp4a_get_audioProfileLevelIndication( lsmash_audio_summary_t *summary ) +{ + if( !summary || summary->stream_type != MP4SYS_STREAM_TYPE_AudioStream ) + return MP4A_AUDIO_PLI_NONE_REQUIRED; /* means error. */ + if( summary->object_type_indication != MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3 ) + return MP4A_AUDIO_PLI_NOT_SPECIFIED; /* This is of audio stream, but not described in ISO/IEC 14496-3. */ + if( summary->channels == 0 || summary->frequency == 0 ) + return MP4A_AUDIO_PLI_NONE_REQUIRED; /* means error. */ + + mp4a_audioProfileLevelIndication pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + switch( summary->aot ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + if( summary->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + { + /* NOTE: This is not strictly preferable, but accurate; just possibly over-estimated. + We do not expect to use MP4A_AAC_SBR_HIERARCHICAL mode without SBR, nor downsampled mode with SBR. */ + if( summary->channels <= 2 && summary->frequency <= 24000 ) + pli = MP4A_AUDIO_PLI_HE_AAC_L2; + else if( summary->channels <= 5 && summary->frequency <= 48000 ) + pli = MP4A_AUDIO_PLI_HE_AAC_L5; + else + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + break; + } + /* pretending plain AAC-LC, if actually HE-AAC. */ + static const uint32_t mp4sys_aac_pli_table[5][3] = { + /* channels, frequency, audioProfileLevelIndication */ + { 6, 96000, MP4A_AUDIO_PLI_AAC_L5 }, /* FIXME: 6ch is not strictly correct, but works in many case. */ + { 6, 48000, MP4A_AUDIO_PLI_AAC_L4 }, /* FIXME: 6ch is not strictly correct, but works in many case. */ + { 2, 48000, MP4A_AUDIO_PLI_AAC_L2 }, + { 2, 24000, MP4A_AUDIO_PLI_AAC_L1 }, + { 0, 0, MP4A_AUDIO_PLI_NOT_SPECIFIED } + }; + for( int i = 0; summary->channels <= mp4sys_aac_pli_table[i][0] && summary->frequency <= mp4sys_aac_pli_table[i][1] ; i++ ) + pli = mp4sys_aac_pli_table[i][2]; + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + /* FIXME: this is not stricly. Summary shall carry max_order, block_switching, bgmc_mode and RLSLMS. */ + if( summary->channels <= 2 && summary->frequency <= 48000 && summary->bit_depth <= 16 && summary->samples_in_frame <= 4096 ) + pli = MP4A_AUDIO_PLI_ALS_Simple_L1; + else + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; /* 14496-3, Audio profiles and levels, does not allow any pli. */ + break; + default: + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; /* something we don't know/support, or what the spec never covers. */ + break; + } + return pli; +} + +static int mp4sys_is_same_profile( mp4a_audioProfileLevelIndication a, mp4a_audioProfileLevelIndication b ) +{ + switch( a ) + { + case MP4A_AUDIO_PLI_Main_L1: + case MP4A_AUDIO_PLI_Main_L2: + case MP4A_AUDIO_PLI_Main_L3: + case MP4A_AUDIO_PLI_Main_L4: + if( MP4A_AUDIO_PLI_Main_L1 <= b && b <= MP4A_AUDIO_PLI_Main_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Scalable_L1: + case MP4A_AUDIO_PLI_Scalable_L2: + case MP4A_AUDIO_PLI_Scalable_L3: + case MP4A_AUDIO_PLI_Scalable_L4: + if( MP4A_AUDIO_PLI_Scalable_L1 <= b && b <= MP4A_AUDIO_PLI_Scalable_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Speech_L1: + case MP4A_AUDIO_PLI_Speech_L2: + if( MP4A_AUDIO_PLI_Speech_L1 <= b && b <= MP4A_AUDIO_PLI_Speech_L2 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Synthetic_L1: + case MP4A_AUDIO_PLI_Synthetic_L2: + case MP4A_AUDIO_PLI_Synthetic_L3: + if( MP4A_AUDIO_PLI_Synthetic_L1 <= b && b <= MP4A_AUDIO_PLI_Synthetic_L3 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_HighQuality_L1: + case MP4A_AUDIO_PLI_HighQuality_L2: + case MP4A_AUDIO_PLI_HighQuality_L3: + case MP4A_AUDIO_PLI_HighQuality_L4: + case MP4A_AUDIO_PLI_HighQuality_L5: + case MP4A_AUDIO_PLI_HighQuality_L6: + case MP4A_AUDIO_PLI_HighQuality_L7: + case MP4A_AUDIO_PLI_HighQuality_L8: + if( MP4A_AUDIO_PLI_HighQuality_L1 <= b && b <= MP4A_AUDIO_PLI_HighQuality_L8 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_LowDelay_L1: + case MP4A_AUDIO_PLI_LowDelay_L2: + case MP4A_AUDIO_PLI_LowDelay_L3: + case MP4A_AUDIO_PLI_LowDelay_L4: + case MP4A_AUDIO_PLI_LowDelay_L5: + case MP4A_AUDIO_PLI_LowDelay_L6: + case MP4A_AUDIO_PLI_LowDelay_L7: + case MP4A_AUDIO_PLI_LowDelay_L8: + if( MP4A_AUDIO_PLI_LowDelay_L1 <= b && b <= MP4A_AUDIO_PLI_LowDelay_L8 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Natural_L1: + case MP4A_AUDIO_PLI_Natural_L2: + case MP4A_AUDIO_PLI_Natural_L3: + case MP4A_AUDIO_PLI_Natural_L4: + if( MP4A_AUDIO_PLI_Natural_L1 <= b && b <= MP4A_AUDIO_PLI_Natural_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_MobileInternetworking_L1: + case MP4A_AUDIO_PLI_MobileInternetworking_L2: + case MP4A_AUDIO_PLI_MobileInternetworking_L3: + case MP4A_AUDIO_PLI_MobileInternetworking_L4: + case MP4A_AUDIO_PLI_MobileInternetworking_L5: + case MP4A_AUDIO_PLI_MobileInternetworking_L6: + if( MP4A_AUDIO_PLI_MobileInternetworking_L1 <= b && b <= MP4A_AUDIO_PLI_MobileInternetworking_L6 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_AAC_L1: + case MP4A_AUDIO_PLI_AAC_L2: + case MP4A_AUDIO_PLI_AAC_L4: + case MP4A_AUDIO_PLI_AAC_L5: + if( MP4A_AUDIO_PLI_AAC_L1 <= b && b <= MP4A_AUDIO_PLI_AAC_L5 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_HE_AAC_L2: + case MP4A_AUDIO_PLI_HE_AAC_L3: + case MP4A_AUDIO_PLI_HE_AAC_L4: + case MP4A_AUDIO_PLI_HE_AAC_L5: + if( MP4A_AUDIO_PLI_HE_AAC_L2 <= b && b <= MP4A_AUDIO_PLI_HE_AAC_L5 ) + return 1; + return 0; + break; + default: + break; + } + return 0; +} + +/* NOTE: This function is not strictly preferable, but accurate. + The spec of audioProfileLevelIndication is too much complicated. */ +mp4a_audioProfileLevelIndication mp4a_max_audioProfileLevelIndication( mp4a_audioProfileLevelIndication a, mp4a_audioProfileLevelIndication b ) +{ + /* NONE_REQUIRED is minimal priotity, and NOT_SPECIFIED is max priority. */ + if( a == MP4A_AUDIO_PLI_NOT_SPECIFIED || b == MP4A_AUDIO_PLI_NONE_REQUIRED ) + return a; + if( a == MP4A_AUDIO_PLI_NONE_REQUIRED || b == MP4A_AUDIO_PLI_NOT_SPECIFIED ) + return b; + mp4a_audioProfileLevelIndication c, d; + if( a < b ) + { + c = a; + d = b; + } + else + { + c = b; + d = a; + } + /* AAC-LC and SBR specific; If mixtured there, use correspond HE_AAC profile. */ + if( MP4A_AUDIO_PLI_AAC_L1 <= c && c <= MP4A_AUDIO_PLI_AAC_L5 + && MP4A_AUDIO_PLI_HE_AAC_L2 <= d && d <= MP4A_AUDIO_PLI_HE_AAC_L5 ) + { + if( c <= MP4A_AUDIO_PLI_AAC_L2 ) + return d; + c += 4; /* upgrade to HE-AAC */ + return c > d ? c : d; + } + /* General */ + if( mp4sys_is_same_profile( c, d ) ) + return d; + return MP4A_AUDIO_PLI_NOT_SPECIFIED; +} diff --git a/output/mp4/mp4a.h b/output/mp4/mp4a.h new file mode 100644 index 0000000..ab42b3f --- /dev/null +++ b/output/mp4/mp4a.h @@ -0,0 +1,130 @@ +/***************************************************************************** + * mp4a.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef MP4A_H +#define MP4A_H + +#include "utils.h" + +/*************************************************************************** + MPEG-4 Systems for MPEG-4 Audio +***************************************************************************/ + +/* 14496-3 audioProfileLevelIndication */ +typedef enum { + MP4A_AUDIO_PLI_Reserved = 0x00, /* Reserved for ISO use */ + MP4A_AUDIO_PLI_Main_L1 = 0x01, /* Main Audio Profile L1 */ + MP4A_AUDIO_PLI_Main_L2 = 0x02, /* Main Audio Profile L2 */ + MP4A_AUDIO_PLI_Main_L3 = 0x03, /* Main Audio Profile L3 */ + MP4A_AUDIO_PLI_Main_L4 = 0x04, /* Main Audio Profile L4 */ + MP4A_AUDIO_PLI_Scalable_L1 = 0x05, /* Scalable Audio Profile L1 */ + MP4A_AUDIO_PLI_Scalable_L2 = 0x06, /* Scalable Audio Profile L2 */ + MP4A_AUDIO_PLI_Scalable_L3 = 0x07, /* Scalable Audio Profile L3 */ + MP4A_AUDIO_PLI_Scalable_L4 = 0x08, /* Scalable Audio Profile L4 */ + MP4A_AUDIO_PLI_Speech_L1 = 0x09, /* Speech Audio Profile L1 */ + MP4A_AUDIO_PLI_Speech_L2 = 0x0A, /* Speech Audio Profile L2 */ + MP4A_AUDIO_PLI_Synthetic_L1 = 0x0B, /* Synthetic Audio Profile L1 */ + MP4A_AUDIO_PLI_Synthetic_L2 = 0x0C, /* Synthetic Audio Profile L2 */ + MP4A_AUDIO_PLI_Synthetic_L3 = 0x0D, /* Synthetic Audio Profile L3 */ + MP4A_AUDIO_PLI_HighQuality_L1 = 0x0E, /* High Quality Audio Profile L1 */ + MP4A_AUDIO_PLI_HighQuality_L2 = 0x0F, /* High Quality Audio Profile L2 */ + MP4A_AUDIO_PLI_HighQuality_L3 = 0x10, /* High Quality Audio Profile L3 */ + MP4A_AUDIO_PLI_HighQuality_L4 = 0x11, /* High Quality Audio Profile L4 */ + MP4A_AUDIO_PLI_HighQuality_L5 = 0x12, /* High Quality Audio Profile L5 */ + MP4A_AUDIO_PLI_HighQuality_L6 = 0x13, /* High Quality Audio Profile L6 */ + MP4A_AUDIO_PLI_HighQuality_L7 = 0x14, /* High Quality Audio Profile L7 */ + MP4A_AUDIO_PLI_HighQuality_L8 = 0x15, /* High Quality Audio Profile L8 */ + MP4A_AUDIO_PLI_LowDelay_L1 = 0x16, /* Low Delay Audio Profile L1 */ + MP4A_AUDIO_PLI_LowDelay_L2 = 0x17, /* Low Delay Audio Profile L2 */ + MP4A_AUDIO_PLI_LowDelay_L3 = 0x18, /* Low Delay Audio Profile L3 */ + MP4A_AUDIO_PLI_LowDelay_L4 = 0x19, /* Low Delay Audio Profile L4 */ + MP4A_AUDIO_PLI_LowDelay_L5 = 0x1A, /* Low Delay Audio Profile L5 */ + MP4A_AUDIO_PLI_LowDelay_L6 = 0x1B, /* Low Delay Audio Profile L6 */ + MP4A_AUDIO_PLI_LowDelay_L7 = 0x1C, /* Low Delay Audio Profile L7 */ + MP4A_AUDIO_PLI_LowDelay_L8 = 0x1D, /* Low Delay Audio Profile L8 */ + MP4A_AUDIO_PLI_Natural_L1 = 0x1E, /* Natural Audio Profile L1 */ + MP4A_AUDIO_PLI_Natural_L2 = 0x1F, /* Natural Audio Profile L2 */ + MP4A_AUDIO_PLI_Natural_L3 = 0x20, /* Natural Audio Profile L3 */ + MP4A_AUDIO_PLI_Natural_L4 = 0x21, /* Natural Audio Profile L4 */ + MP4A_AUDIO_PLI_MobileInternetworking_L1 = 0x22, /* Mobile Audio Internetworking Profile L1 */ + MP4A_AUDIO_PLI_MobileInternetworking_L2 = 0x23, /* Mobile Audio Internetworking Profile L2 */ + MP4A_AUDIO_PLI_MobileInternetworking_L3 = 0x24, /* Mobile Audio Internetworking Profile L3 */ + MP4A_AUDIO_PLI_MobileInternetworking_L4 = 0x25, /* Mobile Audio Internetworking Profile L4 */ + MP4A_AUDIO_PLI_MobileInternetworking_L5 = 0x26, /* Mobile Audio Internetworking Profile L5 */ + MP4A_AUDIO_PLI_MobileInternetworking_L6 = 0x27, /* Mobile Audio Internetworking Profile L6 */ + MP4A_AUDIO_PLI_AAC_L1 = 0x28, /* AAC Profile L1 */ + MP4A_AUDIO_PLI_AAC_L2 = 0x29, /* AAC Profile L2 */ + MP4A_AUDIO_PLI_AAC_L4 = 0x2A, /* AAC Profile L4 */ + MP4A_AUDIO_PLI_AAC_L5 = 0x2B, /* AAC Profile L5 */ + MP4A_AUDIO_PLI_HE_AAC_L2 = 0x2C, /* High Efficiency AAC Profile L2 */ + MP4A_AUDIO_PLI_HE_AAC_L3 = 0x2D, /* High Efficiency AAC Profile L3 */ + MP4A_AUDIO_PLI_HE_AAC_L4 = 0x2E, /* High Efficiency AAC Profile L4 */ + MP4A_AUDIO_PLI_HE_AAC_L5 = 0x2F, /* High Efficiency AAC Profile L5 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L2 = 0x30, /* High Efficiency AAC v2 Profile L2 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L3 = 0x31, /* High Efficiency AAC v2 Profile L3 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L4 = 0x32, /* High Efficiency AAC v2 Profile L4 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L5 = 0x33, /* High Efficiency AAC v2 Profile L5 */ + MP4A_AUDIO_PLI_LowDelay_AAC_L1 = 0x34, /* Low Delay AAC Profile L1 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L1= 0x35, /* Baseline MPEG Surround Profile L1 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L2= 0x36, /* Baseline MPEG Surround Profile L2 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L3= 0x37, /* Baseline MPEG Surround Profile L3 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L4= 0x38, /* Baseline MPEG Surround Profile L4 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L5= 0x39, /* Baseline MPEG Surround Profile L5 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L6= 0x3A, /* Baseline MPEG Surround Profile L6 */ + MP4A_AUDIO_PLI_HD_AAC_L1 = 0x3B, /* High Definition AAC Profile L1 */ + MP4A_AUDIO_PLI_ALS_Simple_L1 = 0x3C, /* ALS Simple Profile L1 */ + MP4A_AUDIO_PLI_NOT_SPECIFIED = 0xFE, /* no audio profile specified */ + MP4A_AUDIO_PLI_NONE_REQUIRED = 0xFF, /* no audio capability required */ +} mp4a_audioProfileLevelIndication; + +#ifndef MP4A_INTERNAL + +typedef void mp4a_AudioSpecificConfig_t; + +/* export for mp4sys / importer */ +mp4a_AudioSpecificConfig_t *mp4a_create_AudioSpecificConfig( + lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length +); +void mp4a_put_AudioSpecificConfig( lsmash_bs_t* bs, mp4a_AudioSpecificConfig_t* asc ); +void mp4a_remove_AudioSpecificConfig( mp4a_AudioSpecificConfig_t* asc ); + +/* export for importer */ +extern const uint32_t mp4a_sampling_frequency_table[13][5]; + +/* setup for summary */ +int mp4a_setup_summary_from_AudioSpecificConfig( lsmash_audio_summary_t *summary, uint8_t *dsi_payload, uint32_t dsi_payload_length ); + +/* profileLevelIndication relative functions. */ +mp4a_audioProfileLevelIndication mp4a_get_audioProfileLevelIndication( lsmash_audio_summary_t *summary ); +mp4a_audioProfileLevelIndication mp4a_max_audioProfileLevelIndication( + mp4a_audioProfileLevelIndication a, + mp4a_audioProfileLevelIndication b +); + +#endif + +#endif diff --git a/output/mp4/mp4sys.c b/output/mp4/mp4sys.c new file mode 100644 index 0000000..e4572e1 --- /dev/null +++ b/output/mp4/mp4sys.c @@ -0,0 +1,977 @@ +/***************************************************************************** + * mp4sys.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include "utils.h" + +#include +#include + +#include "mp4a.h" +#define MP4SYS_INTERNAL +#include "mp4sys.h" + +/*************************************************************************** + MPEG-4 Systems +***************************************************************************/ + +#define ALWAYS_28BITS_LENGTH_CODING 1 // for some weird (but originator's) devices + +/* List of Class Tags for Descriptors */ +typedef enum { + MP4SYS_DESCRIPTOR_TAG_Forbidden = 0x00, /* Forbidden */ + MP4SYS_DESCRIPTOR_TAG_ObjectDescrTag = 0x01, /* ObjectDescrTag */ + MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag = 0x02, /* InitialObjectDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ES_DescrTag = 0x03, /* ES_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag = 0x04, /* DecoderConfigDescrTag */ + MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag = 0x05, /* DecSpecificInfoTag */ + MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag = 0x06, /* SLConfigDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentIdentDescrTag = 0x07, /* ContentIdentDescrTag */ + MP4SYS_DESCRIPTOR_TAG_SupplContentIdentDescrTag = 0x08, /* SupplContentIdentDescrTag */ + MP4SYS_DESCRIPTOR_TAG_IPI_DescrPointerTag = 0x09, /* IPI_DescrPointerTag */ + MP4SYS_DESCRIPTOR_TAG_IPMP_DescrPointerTag = 0x0A, /* IPMP_DescrPointerTag */ + MP4SYS_DESCRIPTOR_TAG_IPMP_DescrTag = 0x0B, /* IPMP_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_QoS_DescrTag = 0x0C, /* QoS_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_RegistrationDescrTag = 0x0D, /* RegistrationDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag = 0x0E, /* ES_ID_IncTag */ + MP4SYS_DESCRIPTOR_TAG_ES_ID_RefTag = 0x0F, /* ES_ID_RefTag */ + MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag = 0x10, /* MP4_IOD_Tag, InitialObjectDescriptor for MP4 */ + MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag = 0x11, /* MP4_OD_Tag, ObjectDescriptor for MP4 */ + MP4SYS_DESCRIPTOR_TAG_IPI_DescrPointerRefTag = 0x12, /* IPI_DescrPointerRefTag */ + MP4SYS_DESCRIPTOR_TAG_ExtendedProfileLevelDescrTag = 0x13, /* ExtendedProfileLevelDescrTag */ + MP4SYS_DESCRIPTOR_TAG_profileLevelIndicationIndexDescrTag = 0x14, /* profileLevelIndicationIndexDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentClassificationDescrTag = 0x40, /* ContentClassificationDescrTag */ + MP4SYS_DESCRIPTOR_TAG_KeyWordDescrTag = 0x41, /* KeyWordDescrTag */ + MP4SYS_DESCRIPTOR_TAG_RatingDescrTag = 0x42, /* RatingDescrTag */ + MP4SYS_DESCRIPTOR_TAG_LanguageDescrTag = 0x43, /* LanguageDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ShortTextualDescrTag = 0x44, /* ShortTextualDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ExpandedTextualDescrTag = 0x45, /* ExpandedTextualDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentCreatorNameDescrTag = 0x46, /* ContentCreatorNameDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentCreationDateDescrTag = 0x47, /* ContentCreationDateDescrTag */ + MP4SYS_DESCRIPTOR_TAG_OCICreatorNameDescrTag = 0x48, /* OCICreatorNameDescrTag */ + MP4SYS_DESCRIPTOR_TAG_OCICreationDateDescrTag = 0x49, /* OCICreationDateDescrTag */ + MP4SYS_DESCRIPTOR_TAG_SmpteCameraPositionDescrTag = 0x4A, /* SmpteCameraPositionDescrTag */ + MP4SYS_DESCRIPTOR_TAG_Forbidden1 = 0xFF, /* Forbidden */ +} mp4sys_descriptor_tag; +// MP4SYS_DESCRIPTOR_TAG_ES_DescrRemoveRefTag = 0x07, /* FIXME: (command tag), see 14496-14 Object Descriptors */ + +typedef struct { + uint32_t size; // 2^28 at most + mp4sys_descriptor_tag tag; +} mp4sys_descriptor_head_t; + +/* DecoderSpecificInfo */ +/* contents varies depends on ObjectTypeIndication and StreamType. */ +typedef struct { + mp4sys_descriptor_head_t header; + uint8_t* data; +} mp4sys_DecoderSpecificInfo_t; + +/* DecoderConfigDescriptor */ +typedef struct { + mp4sys_descriptor_head_t header; + lsmash_mp4sys_object_type_indication objectTypeIndication; + lsmash_mp4sys_stream_type streamType; + uint8_t upStream; /* bit(1), always 0 in this muxer, used for interactive contents. */ + uint8_t reserved; /* const bit(1), always 1. */ + uint32_t bufferSizeDB; /* maybe CPB size in bytes, NOT bits. */ + uint32_t maxBitrate; + uint32_t avgBitrate; /* 0 if VBR */ + mp4sys_DecoderSpecificInfo_t* decSpecificInfo; /* can be NULL. */ + /* 14496-1 seems to say if we are in IOD(InitialObjectDescriptor), we might use this. + See ExtensionProfileLevelDescr, The Initial Object Descriptor. + But I don't think this is mandatory despite 14496-1, because 14496-14 says, in OD or IOD, + we have to use ES_ID_Inc instead of ES_Descriptor, which does not have DecoderConfigDescriptor. */ + // profileLevelIndicationIndexDescriptor profileLevelIndicationIndexDescr [0..255]; +} mp4sys_DecoderConfigDescriptor_t; + +/* SLConfigDescriptor */ +typedef struct { + mp4sys_descriptor_head_t header; + uint8_t predefined; /* default the values from a set of predefined parameter sets as detailed below. + * 0x00 : Custum + * 0x01 : null SL packet header + * 0x02 : Reserved for use in MP4 files + * 0x03 - 0xFF : Reserved for ISO use + * MP4 file that does not use URL_Flag shall have constant value 0x02. */ + /* Custom values + * The following fields are placed if predefined == 0x00. */ + unsigned useAccessUnitStartFlag : 1; + unsigned useAccessUnitEndFlag : 1; + unsigned useRandomAccessPointFlag : 1; + unsigned hasRandomAccessUnitsOnlyFlag : 1; + unsigned usePaddingFlag : 1; + unsigned useTimeStampsFlag : 1; + unsigned useIdleFlag : 1; + unsigned durationFlag : 1; + uint32_t timeStampResolution; + uint32_t OCRResolution; + uint8_t timeStampLength; + uint8_t OCRLength; + uint8_t AU_Length; + uint8_t instantBitrateLength; + unsigned degradationPriorityLength : 4; + unsigned AU_seqNumLength : 5; + unsigned packetSeqNumLength : 5; + unsigned reserved : 2; + /* The following fields are placed if durationFlag is true. */ + uint32_t timeScale; + uint16_t accessUnitDuration; + uint16_t compositionUnitDuration; + /* The following fields are placed if useTimeStampsFlag is false. */ + uint64_t startDecodingTimeStamp; + uint64_t startCompositionTimeStamp; +} mp4sys_SLConfigDescriptor_t; + +/* ES_Descriptor */ +typedef struct +{ + mp4sys_descriptor_head_t header; + uint16_t ES_ID; + unsigned streamDependenceFlag : 1; /* no stream depencies between streams in this muxer, ES_ID of another elementary stream */ + unsigned URL_Flag : 1; /* no external URL referencing stream in MP4 */ + unsigned OCRstreamFlag : 1; /* no Object Clock Reference stream in this muxer (shall be false in MP4, useful if we're importing from MPEG-2?) */ + unsigned streamPriority : 5; /* no priority among streams in this muxer, higher is important */ + uint16_t dependsOn_ES_ID; + uint8_t URLlength; + uint8_t URLstring[255]; + uint16_t OCR_ES_Id; + mp4sys_DecoderConfigDescriptor_t *decConfigDescr; /* cannot be NULL. */ + mp4sys_SLConfigDescriptor_t *slConfigDescr; + /* descriptors below are not mandatory, I think Language Descriptor may somewhat useful */ + /* + IPI_DescrPointer ipiPtr[0 .. 1]; // used to indicate using other ES's IP_IdentificationDataSet + IP_IdentificationDataSet ipIDS[0 .. 255]; // abstract class, actually ContentIdentificationDescriptor(for commercial contents management), + // or SupplementaryContentIdentificationDescriptor(for embedding titles) + IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; // used to intellectual property / protection management + LanguageDescriptor langDescr[0 .. 255]; // used to identify the language of the audio/speech or text object + QoS_Descriptor qosDescr[0 .. 1]; // used to achieve QoS + RegistrationDescriptor regDescr[0 .. 1]; // used to carry elementary streams with data whose format is not recognized by ISO/IEC 14496-1 + ExtensionDescriptor extDescr[0 .. 255]; // abstract class, actually defined no subclass, maybe useless + */ +} mp4sys_ES_Descriptor_t; + +/* 14496-14 Object Descriptors (ES_ID_Inc) */ +typedef struct { + mp4sys_descriptor_head_t header; + uint32_t Track_ID; +} mp4sys_ES_ID_Inc_t; + +/* 14496-1 ObjectDescriptor / InitialObjectDescriptor */ +typedef struct { + mp4sys_descriptor_head_t header; + uint16_t ObjectDescriptorID; + // uint8_t URL_Flag; /* bit(1) */ + uint8_t includeInlineProfileLevelFlag; /* bit(1) */ + //const uint8_t reserved=0x0F(0b1111) or 0x1F(0b1.1111); /* bit(4 or 5), width is 4 for IOD, 5 for OD */ + /* if (URL_Flag) { + uint8_t URLlength; // bit(8) + char URLstring[256]; // bit(8)[] + }*/ + /* else { */ + mp4sys_ODProfileLevelIndication ODProfileLevelIndication; + mp4sys_sceneProfileLevelIndication sceneProfileLevelIndication; + mp4a_audioProfileLevelIndication audioProfileLevelIndication; + mp4sys_visualProfileLevelIndication visualProfileLevelIndication; + mp4sys_graphicsProfileLevelIndication graphicsProfileLevelIndication; + lsmash_entry_list_t* esDescr; /* List of ES_ID_Inc, not ES_Descriptor defined in 14496-1. 14496-14 overrides. */ + // OCI_Descriptor ociDescr[0 .. 255]; + // IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; + /* } */ + // ExtensionDescriptor extDescr[0 .. 255]; +} mp4sys_ObjectDescriptor_t; + +int mp4sys_remove_DecoderSpecificInfo( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd || !esd->decConfigDescr ) + return -1; + if( !esd->decConfigDescr->decSpecificInfo ) + return 0; + if( esd->decConfigDescr->decSpecificInfo->data ) + free( esd->decConfigDescr->decSpecificInfo->data ); + free( esd->decConfigDescr->decSpecificInfo ); + esd->decConfigDescr->decSpecificInfo = NULL; + return 0; +} + +int mp4sys_remove_DecoderConfigDescriptor( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd ) + return -1; + if( !esd->decConfigDescr ) + return 0; + mp4sys_remove_DecoderSpecificInfo( esd ); + free( esd->decConfigDescr ); + esd->decConfigDescr = NULL; + return 0; +} + +int mp4sys_remove_SLConfigDescriptor( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd ) + return -1; + if( !esd->slConfigDescr ) + return 0; + free( esd->slConfigDescr ); + esd->slConfigDescr = NULL; + return 0; +} + +int mp4sys_remove_ES_Descriptor( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd ) + return 0; + mp4sys_remove_DecoderConfigDescriptor( esd ); + mp4sys_remove_SLConfigDescriptor( esd ); + free( esd ); + return 0; +} + +int mp4sys_remove_ES_ID_Incs( mp4sys_ObjectDescriptor_t* od ) +{ + if( !od ) + return -1; + if( od->esDescr ) + { + lsmash_remove_list( od->esDescr, NULL ); + od->esDescr = NULL; + } + return 0; +} + +int mp4sys_remove_ObjectDescriptor( mp4sys_ObjectDescriptor_t* od ) +{ + if( !od ) + return 0; + mp4sys_remove_ES_ID_Incs( od ); + free( od ); + return 0; +} + +int mp4sys_add_DecoderSpecificInfo( mp4sys_ES_Descriptor_t* esd, void* dsi_payload, uint32_t dsi_payload_length ) +{ + if( !esd || !esd->decConfigDescr || dsi_payload == NULL || dsi_payload_length == 0 ) + return -1; + mp4sys_DecoderSpecificInfo_t *dsi = (mp4sys_DecoderSpecificInfo_t *)lsmash_malloc_zero( sizeof(mp4sys_DecoderSpecificInfo_t) ); + if( !dsi ) + return -1; + dsi->header.tag = MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag; + dsi->data = lsmash_memdup( dsi_payload, dsi_payload_length ); + if( !dsi->data ) + { + free( dsi ); + return -1; + } + dsi->header.size = dsi_payload_length; + debug_if( mp4sys_remove_DecoderSpecificInfo( esd ) ) + { + free( dsi->data ); + free( dsi ); + return -1; + } + esd->decConfigDescr->decSpecificInfo = dsi; + return 0; +} + +/* + bufferSizeDB is byte unit, NOT bit unit. + avgBitrate is 0 if VBR +*/ +int mp4sys_add_DecoderConfigDescriptor( + mp4sys_ES_Descriptor_t* esd, + lsmash_mp4sys_object_type_indication objectTypeIndication, + lsmash_mp4sys_stream_type streamType, + uint32_t bufferSizeDB, + uint32_t maxBitrate, + uint32_t avgBitrate +){ + if( !esd ) + return -1; + mp4sys_DecoderConfigDescriptor_t *dcd = (mp4sys_DecoderConfigDescriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_DecoderConfigDescriptor_t) ); + if( !dcd ) + return -1; + dcd->header.tag = MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag; + dcd->objectTypeIndication = objectTypeIndication; + dcd->streamType = streamType; + dcd->reserved = 1; + dcd->bufferSizeDB = bufferSizeDB; + dcd->maxBitrate = maxBitrate; + dcd->avgBitrate = avgBitrate; + debug_if( mp4sys_remove_DecoderConfigDescriptor( esd ) ) + { + free( dcd ); + return -1; + } + esd->decConfigDescr = dcd; + return 0; +} + +/* + bufferSizeDB is byte unit, NOT bit unit. + avgBitrate is 0 if VBR +*/ +int mp4sys_update_DecoderConfigDescriptor( mp4sys_ES_Descriptor_t* esd, uint32_t bufferSizeDB, uint32_t maxBitrate, uint32_t avgBitrate ) +{ + if( !esd || !esd->decConfigDescr ) + return -1; + mp4sys_DecoderConfigDescriptor_t* dcd = esd->decConfigDescr; + dcd->bufferSizeDB = bufferSizeDB; + dcd->maxBitrate = maxBitrate; + dcd->avgBitrate = avgBitrate; + return 0; +} + +int mp4sys_add_SLConfigDescriptor( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd ) + return -1; + mp4sys_SLConfigDescriptor_t *slcd = (mp4sys_SLConfigDescriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_SLConfigDescriptor_t) ); + if( !slcd ) + return -1; + slcd->header.tag = MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag; + slcd->predefined = 0x02; /* MP4 file which does not use URL_Flag shall have constant value 0x02 */ + debug_if( mp4sys_remove_SLConfigDescriptor( esd ) ) + { + free( slcd ); + return -1; + } + esd->slConfigDescr = slcd; + return 0; +} + +/* ES_ID might be usually 0 or lower 16 bits of the TrackID + 14496-14 says, "set to 0 as stored; when built into a stream, the lower 16 bits of the TrackID are used." + I'm not sure about actual meaning of "stored" and "built into a stream", but maybe 0 will do in stsd(esds). */ +mp4sys_ES_Descriptor_t* mp4sys_create_ES_Descriptor( uint16_t ES_ID ) +{ + mp4sys_ES_Descriptor_t *esd = (mp4sys_ES_Descriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_ES_Descriptor_t) ); + if( !esd ) + return NULL; + esd->header.tag = MP4SYS_DESCRIPTOR_TAG_ES_DescrTag; + esd->ES_ID = ES_ID; + return esd; +} + +/* NOTE: This is only for MP4_IOD and MP4_OD, not for Iso Base Media's ObjectDescriptor and InitialObjectDescriptor */ +int mp4sys_add_ES_ID_Inc( mp4sys_ObjectDescriptor_t* od, uint32_t Track_ID ) +{ + if( !od ) + return -1; + if( !od->esDescr && ( od->esDescr = lsmash_create_entry_list() ) == NULL ) + return -1; + mp4sys_ES_ID_Inc_t *es_id_inc = (mp4sys_ES_ID_Inc_t *)lsmash_malloc_zero( sizeof(mp4sys_ES_ID_Inc_t) ); + if( !es_id_inc ) + return -1; + es_id_inc->header.tag = MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag; + es_id_inc->Track_ID = Track_ID; + if( lsmash_add_entry( od->esDescr, es_id_inc ) ) + { + free( es_id_inc ); + return -1; + } + return 0; +} + +/* NOTE: This is only for MP4_OD, not for Iso Base Media's ObjectDescriptor */ +mp4sys_ObjectDescriptor_t* mp4sys_create_ObjectDescriptor( uint16_t ObjectDescriptorID ) +{ + mp4sys_ObjectDescriptor_t *od = (mp4sys_ObjectDescriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_ObjectDescriptor_t) ); + if( !od ) + return NULL; + od->header.tag = MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag; + od->ObjectDescriptorID = ObjectDescriptorID; + od->includeInlineProfileLevelFlag = 1; /* 1 as part of reserved flag. */ + od->ODProfileLevelIndication = MP4SYS_OD_PLI_NONE_REQUIRED; + od->sceneProfileLevelIndication = MP4SYS_SCENE_PLI_NONE_REQUIRED; + od->audioProfileLevelIndication = MP4A_AUDIO_PLI_NONE_REQUIRED; + od->visualProfileLevelIndication = MP4SYS_VISUAL_PLI_NONE_REQUIRED; + od->graphicsProfileLevelIndication = MP4SYS_GRAPHICS_PLI_NONE_REQUIRED; + return od; +} + +/* NOTE: This is only for MP4_IOD, not for Iso Base Media's InitialObjectDescriptor */ +int mp4sys_to_InitialObjectDescriptor( + mp4sys_ObjectDescriptor_t* od, + uint8_t include_inline_pli, + mp4sys_ODProfileLevelIndication od_pli, + mp4sys_sceneProfileLevelIndication scene_pli, + mp4a_audioProfileLevelIndication audio_pli, + mp4sys_visualProfileLevelIndication visual_pli, + mp4sys_graphicsProfileLevelIndication graph_pli +){ + if( !od ) + return -1; + od->header.tag = MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag; + od->includeInlineProfileLevelFlag = include_inline_pli; + od->ODProfileLevelIndication = od_pli; + od->sceneProfileLevelIndication = scene_pli; + od->audioProfileLevelIndication = audio_pli; + od->visualProfileLevelIndication = visual_pli; + od->graphicsProfileLevelIndication = graph_pli; + return 0; +} + +/* returns total size of descriptor, including header, 2 at least */ +static inline uint32_t mp4sys_get_descriptor_size( uint32_t payload_size_in_byte ) +{ +#if ALWAYS_28BITS_LENGTH_CODING + return payload_size_in_byte + 4 + 1; /* +4 means 28bits length coding, +1 means tag's space */ +#else + /* descriptor length will be split into 7bits + see 14496-1 Expandable classes and Length encoding of descriptors and commands */ + uint32_t i; + for( i = 1; payload_size_in_byte >> ( 7 * i ); i++ ); + return payload_size_in_byte + i + 1; /* +1 means tag's space */ +#endif +} + +static uint32_t mp4sys_update_DecoderSpecificInfo_size( mp4sys_ES_Descriptor_t* esd ) +{ + debug_if( !esd || !esd->decConfigDescr ) + return 0; + if( !esd->decConfigDescr->decSpecificInfo ) + return 0; + /* no need to update, header.size is already set */ + return mp4sys_get_descriptor_size( esd->decConfigDescr->decSpecificInfo->header.size ); +} + +static uint32_t mp4sys_update_DecoderConfigDescriptor_size( mp4sys_ES_Descriptor_t* esd ) +{ + debug_if( !esd ) + return 0; + if( !esd->decConfigDescr ) + return 0; + uint32_t size = 13; + size += mp4sys_update_DecoderSpecificInfo_size( esd ); + esd->decConfigDescr->header.size = size; + return mp4sys_get_descriptor_size( size ); +} + +static uint32_t mp4sys_update_SLConfigDescriptor_size( mp4sys_ES_Descriptor_t* esd ) +{ + debug_if( !esd ) + return 0; + if( !esd->slConfigDescr ) + return 0; + mp4sys_SLConfigDescriptor_t *slcd = esd->slConfigDescr; + uint32_t size = 1; + if( slcd->predefined == 0x00 ) + size += 15; + if( slcd->durationFlag ) + size += 8; + if( !slcd->useTimeStampsFlag ) + size += (2 * slcd->timeStampLength + 7) / 8; + esd->slConfigDescr->header.size = size; + return mp4sys_get_descriptor_size( size ); +} + +uint32_t mp4sys_update_ES_Descriptor_size( mp4sys_ES_Descriptor_t* esd ) +{ + if( !esd ) + return 0; + uint32_t size = 3; + if( esd->streamDependenceFlag ) + size += 2; + if( esd->URL_Flag ) + size += 1 + esd->URLlength; + if( esd->OCRstreamFlag ) + size += 2; + size += mp4sys_update_DecoderConfigDescriptor_size( esd ); + size += mp4sys_update_SLConfigDescriptor_size( esd ); + esd->header.size = size; + return mp4sys_get_descriptor_size( size ); +} + +static uint32_t mp4sys_update_ES_ID_Inc_size( mp4sys_ES_ID_Inc_t* es_id_inc ) +{ + debug_if( !es_id_inc ) + return 0; + es_id_inc->header.size = 4; + return mp4sys_get_descriptor_size( es_id_inc->header.size ); +} + +/* This function works as aggregate of ES_ID_Incs, so this function itself updates no size information */ +static uint32_t mp4sys_update_ES_ID_Incs_size( mp4sys_ObjectDescriptor_t* od ) +{ + debug_if( !od ) + return 0; + if( !od->esDescr ) + return 0; + uint32_t size = 0; + for( lsmash_entry_t *entry = od->esDescr->head; entry; entry = entry->next ) + size += mp4sys_update_ES_ID_Inc_size( (mp4sys_ES_ID_Inc_t*)entry->data ); + return size; +} + +uint32_t mp4sys_update_ObjectDescriptor_size( mp4sys_ObjectDescriptor_t* od ) +{ + if( !od ) + return 0; + uint32_t size = od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ? 7 : 2; + size += mp4sys_update_ES_ID_Incs_size( od ); + od->header.size = size; + return mp4sys_get_descriptor_size( size ); +} + +static int mp4sys_put_descriptor_header( lsmash_bs_t *bs, mp4sys_descriptor_head_t* header ) +{ + debug_if( !bs || !header ) + return -1; + lsmash_bs_put_byte( bs, header->tag ); + /* descriptor length will be splitted into 7bits + see 14496-1 Expandable classes and Length encoding of descriptors and commands */ +#if ALWAYS_28BITS_LENGTH_CODING + lsmash_bs_put_byte( bs, ( header->size >> 21 ) | 0x80 ); + lsmash_bs_put_byte( bs, ( header->size >> 14 ) | 0x80 ); + lsmash_bs_put_byte( bs, ( header->size >> 7 ) | 0x80 ); +#else + for( uint32_t i = mp4sys_get_descriptor_size( header->size ) - header->size - 2; i; i-- ){ + lsmash_bs_put_byte( bs, ( header->size >> ( 7 * i ) ) | 0x80 ); + } +#endif + lsmash_bs_put_byte( bs, header->size & 0x7F ); + return 0; +} + +static int mp4sys_write_DecoderSpecificInfo( lsmash_bs_t *bs, mp4sys_DecoderSpecificInfo_t* dsi ) +{ + debug_if( !bs ) + return -1; + if( !dsi ) + return 0; /* can be NULL */ + debug_if( mp4sys_put_descriptor_header( bs, &dsi->header ) ) + return -1; + if( dsi->data && dsi->header.size != 0 ) + lsmash_bs_put_bytes( bs, dsi->data, dsi->header.size ); + return lsmash_bs_write_data( bs ); +} + +static int mp4sys_write_DecoderConfigDescriptor( lsmash_bs_t *bs, mp4sys_DecoderConfigDescriptor_t* dcd ) +{ + debug_if( !bs ) + return -1; + if( !dcd ) + return -1; /* cannot be NULL */ + debug_if( mp4sys_put_descriptor_header( bs, &dcd->header ) ) + return -1; + lsmash_bs_put_byte( bs, dcd->objectTypeIndication ); + uint8_t temp; + temp = (dcd->streamType << 2) & 0x3F; + temp |= (dcd->upStream << 1) & 0x01; + temp |= dcd->reserved & 0x01; + lsmash_bs_put_byte( bs, temp ); + lsmash_bs_put_be24( bs, dcd->bufferSizeDB ); + lsmash_bs_put_be32( bs, dcd->maxBitrate ); + lsmash_bs_put_be32( bs, dcd->avgBitrate ); + if( lsmash_bs_write_data( bs ) ) + return -1; + return mp4sys_write_DecoderSpecificInfo( bs, dcd->decSpecificInfo ); + /* here, profileLevelIndicationIndexDescriptor is omitted */ +} + +static int mp4sys_write_SLConfigDescriptor( lsmash_bs_t *bs, mp4sys_SLConfigDescriptor_t* slcd ) +{ + debug_if( !bs ) + return -1; + if( !slcd ) + return 0; + debug_if( mp4sys_put_descriptor_header( bs, &slcd->header ) ) + return -1; + lsmash_bs_put_byte( bs, slcd->predefined ); + if( slcd->predefined == 0x00 ) + { + uint8_t temp8; + temp8 = slcd->useAccessUnitStartFlag << 7; + temp8 |= slcd->useAccessUnitEndFlag << 6; + temp8 |= slcd->useRandomAccessPointFlag << 5; + temp8 |= slcd->hasRandomAccessUnitsOnlyFlag << 4; + temp8 |= slcd->usePaddingFlag << 3; + temp8 |= slcd->useTimeStampsFlag << 2; + temp8 |= slcd->useIdleFlag << 1; + temp8 |= slcd->durationFlag; + lsmash_bs_put_byte( bs, temp8 ); + lsmash_bs_put_be32( bs, slcd->timeStampResolution ); + lsmash_bs_put_be32( bs, slcd->OCRResolution ); + lsmash_bs_put_byte( bs, slcd->timeStampLength ); + lsmash_bs_put_byte( bs, slcd->OCRLength ); + lsmash_bs_put_byte( bs, slcd->AU_Length ); + lsmash_bs_put_byte( bs, slcd->instantBitrateLength ); + uint16_t temp16; + temp16 = slcd->degradationPriorityLength << 12; + temp16 |= slcd->AU_seqNumLength << 7; + temp16 |= slcd->packetSeqNumLength << 2; + temp16 |= slcd->reserved; + lsmash_bs_put_be16( bs, temp16 ); + } + if( slcd->durationFlag ) + { + lsmash_bs_put_be32( bs, slcd->timeScale ); + lsmash_bs_put_be16( bs, slcd->accessUnitDuration ); + lsmash_bs_put_be16( bs, slcd->compositionUnitDuration ); + } + if( !slcd->useTimeStampsFlag ) + { + lsmash_bits_t *bits = lsmash_bits_create( bs ); + if( !bits ) + return -1; + lsmash_bits_put( bits, slcd->startDecodingTimeStamp, slcd->timeStampLength ); + lsmash_bits_put( bits, slcd->startCompositionTimeStamp, slcd->timeStampLength ); + lsmash_bits_put_align( bits ); + lsmash_bits_cleanup( bits ); + } + return lsmash_bs_write_data( bs ); +} + +int mp4sys_write_ES_Descriptor( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t* esd ) +{ + if( !bs || !esd ) + return -1; + debug_if( mp4sys_put_descriptor_header( bs, &esd->header ) ) + return -1; + lsmash_bs_put_be16( bs, esd->ES_ID ); + uint8_t temp; + temp = esd->streamDependenceFlag << 7; + temp |= esd->URL_Flag << 6; + temp |= esd->OCRstreamFlag << 5; + temp |= esd->streamPriority; + lsmash_bs_put_byte( bs, temp ); + if( esd->streamDependenceFlag ) + lsmash_bs_put_be16( bs, esd->dependsOn_ES_ID ); + if( esd->URL_Flag ) + { + lsmash_bs_put_byte( bs, esd->URLlength ); + lsmash_bs_put_bytes( bs, esd->URLstring, esd->URLlength ); + } + if( esd->OCRstreamFlag ) + lsmash_bs_put_be16( bs, esd->OCR_ES_Id ); + /* here, some syntax elements are omitted due to previous flags (all 0) */ + if( lsmash_bs_write_data( bs ) ) + return -1; + if( mp4sys_write_DecoderConfigDescriptor( bs, esd->decConfigDescr ) ) + return -1; + return mp4sys_write_SLConfigDescriptor( bs, esd->slConfigDescr ); +} + +static int mp4sys_put_ES_ID_Inc( lsmash_bs_t *bs, mp4sys_ES_ID_Inc_t* es_id_inc ) +{ + debug_if( !es_id_inc ) + return 0; + debug_if( mp4sys_put_descriptor_header( bs, &es_id_inc->header ) ) + return -1; + lsmash_bs_put_be32( bs, es_id_inc->Track_ID ); + return 0; +} + +/* This function works as aggregate of ES_ID_Incs */ +static int mp4sys_write_ES_ID_Incs( lsmash_bs_t *bs, mp4sys_ObjectDescriptor_t* od ) +{ + debug_if( !od ) + return 0; + if( !od->esDescr ) + return 0; /* This may violate the spec, but some muxer do this */ + for( lsmash_entry_t *entry = od->esDescr->head; entry; entry = entry->next ) + mp4sys_put_ES_ID_Inc( bs, (mp4sys_ES_ID_Inc_t*)entry->data ); + return lsmash_bs_write_data( bs ); +} + +int mp4sys_write_ObjectDescriptor( lsmash_bs_t *bs, mp4sys_ObjectDescriptor_t* od ) +{ + if( !bs || !od ) + return -1; + debug_if( mp4sys_put_descriptor_header( bs, &od->header ) ) + return -1; + uint16_t temp = (od->ObjectDescriptorID << 6); + // temp |= (0x0 << 5); /* URL_Flag */ + temp |= (od->includeInlineProfileLevelFlag << 4); /* if MP4_OD, includeInlineProfileLevelFlag is 0x1. */ + temp |= 0xF; /* reserved */ + lsmash_bs_put_be16( bs, temp ); + /* here, since we don't support URL_Flag, we put ProfileLevelIndications */ + if( od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ) + { + lsmash_bs_put_byte( bs, od->ODProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->sceneProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->audioProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->visualProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->graphicsProfileLevelIndication ); + } + if( lsmash_bs_write_data( bs ) ) + return -1; + return mp4sys_write_ES_ID_Incs( bs, od ); +} + +#ifdef LSMASH_DEMUXER_ENABLED +static int mp4sys_copy_DecoderSpecificInfo( mp4sys_ES_Descriptor_t *dst, mp4sys_ES_Descriptor_t *src ) +{ + if( !src || !src->decConfigDescr || !dst || !dst->decConfigDescr + || mp4sys_remove_DecoderSpecificInfo( dst ) ) + return -1; + mp4sys_DecoderSpecificInfo_t *dsi = src->decConfigDescr->decSpecificInfo; + if( !dsi || !dsi->data || !dsi->header.size ) + return 0; + return mp4sys_add_DecoderSpecificInfo( dst, dsi->data, dsi->header.size ); +} + +static int mp4sys_copy_DecoderConfigDescriptor( mp4sys_ES_Descriptor_t *dst, mp4sys_ES_Descriptor_t *src ) +{ + if( !src || !dst + || mp4sys_remove_DecoderConfigDescriptor( dst ) ) + return -1; + if( !src->decConfigDescr ) + return 0; + if( mp4sys_add_DecoderConfigDescriptor( dst, 0, 0, 0, 0, 0 ) ) + return -1; + *dst->decConfigDescr = *src->decConfigDescr; + dst->decConfigDescr->decSpecificInfo = NULL; + return mp4sys_copy_DecoderSpecificInfo( dst, src ); +} + +static int mp4sys_copy_SLConfigDescriptor( mp4sys_ES_Descriptor_t *dst, mp4sys_ES_Descriptor_t *src ) +{ + if( !src || !dst + || mp4sys_remove_SLConfigDescriptor( dst ) ) + return -1; + if( !src->slConfigDescr ) + return 0; + if( mp4sys_add_SLConfigDescriptor( dst ) ) + return -1; + *dst->slConfigDescr = *src->slConfigDescr; + return 0; +} + +mp4sys_ES_Descriptor_t *mp4sys_duplicate_ES_Descriptor( mp4sys_ES_Descriptor_t *src ) +{ + if( !src ) + return NULL; + mp4sys_ES_Descriptor_t *dst = mp4sys_create_ES_Descriptor( 0 ); + if( !dst ) + return NULL; + *dst = *src; + dst->decConfigDescr = NULL; + dst->slConfigDescr = NULL; + if( mp4sys_copy_DecoderConfigDescriptor( dst, src ) + || mp4sys_copy_SLConfigDescriptor( dst, src ) ) + { + mp4sys_remove_ES_Descriptor( dst ); + return NULL; + } + return dst; +} + +static void mp4sys_get_descriptor_header( lsmash_bs_t *bs, mp4sys_descriptor_head_t* header ) +{ + header->tag = lsmash_bs_get_byte( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + int nextByte = temp & 0x80; + uint32_t sizeOfInstance = temp & 0x7F; + while( nextByte ) + { + temp = lsmash_bs_get_byte( bs ); + nextByte = temp & 0x80; + sizeOfInstance = (sizeOfInstance << 7) | (temp & 0x7F); + } + header->size = sizeOfInstance; +} + +static int mp4sys_get_DecoderSpecificInfo( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t *esd ) +{ + mp4sys_DecoderSpecificInfo_t *dsi = (mp4sys_DecoderSpecificInfo_t *)lsmash_malloc_zero( sizeof(mp4sys_DecoderSpecificInfo_t) ); + if( !dsi ) + return -1; + esd->decConfigDescr->decSpecificInfo = dsi; + mp4sys_get_descriptor_header( bs, &dsi->header ); + if( dsi->header.size ) + { + dsi->data = lsmash_bs_get_bytes( bs, dsi->header.size ); + if( !dsi->data ) + { + mp4sys_remove_DecoderSpecificInfo( esd ); + return -1; + } + } + return 0; +} + +static int mp4sys_get_DecoderConfigDescriptor( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t *esd ) +{ + mp4sys_DecoderConfigDescriptor_t *dcd = (mp4sys_DecoderConfigDescriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_DecoderConfigDescriptor_t) ); + if( !dcd ) + return -1; + esd->decConfigDescr = dcd; + mp4sys_get_descriptor_header( bs, &dcd->header ); + dcd->objectTypeIndication = lsmash_bs_get_byte( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + dcd->streamType = (temp >> 2) & 0x3F; + dcd->upStream = (temp >> 1) & 0x01; + dcd->reserved = temp & 0x01; + dcd->bufferSizeDB = lsmash_bs_get_be24( bs ); + dcd->maxBitrate = lsmash_bs_get_be32( bs ); + dcd->avgBitrate = lsmash_bs_get_be32( bs ); + if( dcd->header.size > 13 + && mp4sys_get_DecoderSpecificInfo( bs, esd ) ) + { + mp4sys_remove_DecoderConfigDescriptor( esd ); + return -1; + } + return 0; +} + +static int mp4sys_get_SLConfigDescriptor( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t *esd ) +{ + mp4sys_SLConfigDescriptor_t *slcd = (mp4sys_SLConfigDescriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_SLConfigDescriptor_t) ); + if( !slcd ) + return -1; + esd->slConfigDescr = slcd; + mp4sys_get_descriptor_header( bs, &slcd->header ); + slcd->predefined = lsmash_bs_get_byte( bs ); + if( slcd->predefined == 0x00 ) + { + uint8_t temp8 = lsmash_bs_get_byte( bs ); + slcd->useAccessUnitStartFlag = (temp8 >> 7) & 0x01; + slcd->useAccessUnitEndFlag = (temp8 >> 6) & 0x01; + slcd->useRandomAccessPointFlag = (temp8 >> 5) & 0x01; + slcd->hasRandomAccessUnitsOnlyFlag = (temp8 >> 4) & 0x01; + slcd->usePaddingFlag = (temp8 >> 3) & 0x01; + slcd->useTimeStampsFlag = (temp8 >> 2) & 0x01; + slcd->useIdleFlag = (temp8 >> 1) & 0x01; + slcd->durationFlag = temp8 & 0x01; + slcd->timeStampResolution = lsmash_bs_get_be32( bs ); + slcd->OCRResolution = lsmash_bs_get_be32( bs ); + slcd->timeStampLength = lsmash_bs_get_byte( bs ); + slcd->OCRLength = lsmash_bs_get_byte( bs ); + slcd->AU_Length = lsmash_bs_get_byte( bs ); + slcd->instantBitrateLength = lsmash_bs_get_byte( bs ); + uint16_t temp16 = lsmash_bs_get_be16( bs ); + slcd->degradationPriorityLength = (temp16 >> 12) & 0x0F; + slcd->AU_seqNumLength = (temp16 >> 7) & 0x1F; + slcd->packetSeqNumLength = (temp16 >> 2) & 0x1F; + slcd->reserved = temp16 & 0x03; + } + else if( slcd->predefined == 0x01 ) + { + slcd->timeStampResolution = 1000; + slcd->timeStampLength = 32; + } + else if( slcd->predefined == 0x02 ) + slcd->useTimeStampsFlag = 1; + if( slcd->durationFlag ) + { + slcd->timeScale = lsmash_bs_get_be32( bs ); + slcd->accessUnitDuration = lsmash_bs_get_be16( bs ); + slcd->compositionUnitDuration = lsmash_bs_get_be16( bs ); + } + if( !slcd->useTimeStampsFlag ) + { + lsmash_bits_t *bits = lsmash_bits_create( bs ); + if( !bits ) + return -1; + slcd->startDecodingTimeStamp = lsmash_bits_get( bits, slcd->timeStampLength ); + slcd->startCompositionTimeStamp = lsmash_bits_get( bits, slcd->timeStampLength ); + lsmash_bits_cleanup( bits ); + } + return 0; +} + +mp4sys_ES_Descriptor_t *mp4sys_get_ES_Descriptor( lsmash_bs_t *bs ) +{ + mp4sys_ES_Descriptor_t *esd = (mp4sys_ES_Descriptor_t *)lsmash_malloc_zero( sizeof(mp4sys_ES_Descriptor_t) ); + if( !esd ) + return NULL; + mp4sys_get_descriptor_header( bs, &esd->header ); + esd->ES_ID = lsmash_bs_get_be16( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + esd->streamDependenceFlag = (temp >> 7) & 0x01; + esd->URL_Flag = (temp >> 6) & 0x01; + esd->OCRstreamFlag = (temp >> 5) & 0x01; + esd->streamPriority = temp & 0x1F; + if( esd->streamDependenceFlag ) + esd->dependsOn_ES_ID = lsmash_bs_get_be16( bs ); + if( esd->URL_Flag ) + { + esd->URLlength = lsmash_bs_get_byte( bs ); + for( uint8_t i = 0; i < esd->URLlength; i++ ) + esd->URLstring[i] = lsmash_bs_get_byte( bs ); + } + if( esd->OCRstreamFlag ) + esd->OCR_ES_Id = lsmash_bs_get_be16( bs ); + if( mp4sys_get_DecoderConfigDescriptor( bs, esd ) + || mp4sys_get_SLConfigDescriptor( bs, esd ) ) + { + mp4sys_remove_ES_Descriptor( esd ); + return NULL; + } + return esd; +} + +static uint8_t *mp4sys_export_DecoderSpecificInfo( mp4sys_ES_Descriptor_t *esd, uint32_t *dsi_payload_length ) +{ + if( !esd || !esd->decConfigDescr || !esd->decConfigDescr->decSpecificInfo ) + return NULL; + mp4sys_DecoderSpecificInfo_t *dsi = (mp4sys_DecoderSpecificInfo_t *)esd->decConfigDescr->decSpecificInfo; + uint8_t *dsi_payload = NULL; + /* DecoderSpecificInfo can be absent. */ + if( dsi->header.size ) + { + dsi_payload = lsmash_memdup( dsi->data, dsi->header.size ); + if( !dsi_payload ) + return NULL; + } + if( dsi_payload_length ) + *dsi_payload_length = dsi->header.size; + return dsi_payload; +} + +/* Sumamry is needed to decide ProfileLevelIndication. + * Currently, support audio's only. */ +int mp4sys_setup_summary_from_DecoderSpecificInfo( lsmash_audio_summary_t *summary, mp4sys_ES_Descriptor_t *esd ) +{ + uint32_t dsi_payload_length = UINT32_MAX; /* arbitrary */ + uint8_t *dsi_payload = mp4sys_export_DecoderSpecificInfo( esd, &dsi_payload_length ); + if( !dsi_payload && dsi_payload_length ) + return -1; + if( dsi_payload_length && mp4a_setup_summary_from_AudioSpecificConfig( summary, dsi_payload, dsi_payload_length ) ) + { + free( dsi_payload ); + return -1; + } + return 0; +} +#endif /* LSMASH_DEMUXER_ENABLED */ + +/**** following functions are for facilitation purpose ****/ + +mp4sys_ES_Descriptor_t* mp4sys_setup_ES_Descriptor( mp4sys_ES_Descriptor_params_t* params ) +{ + if( !params ) + return NULL; + mp4sys_ES_Descriptor_t* esd = mp4sys_create_ES_Descriptor( params->ES_ID ); + if( !esd ) + return NULL; + if( mp4sys_add_SLConfigDescriptor( esd ) + || mp4sys_add_DecoderConfigDescriptor( esd, params->objectTypeIndication, params->streamType, params->bufferSizeDB, params->maxBitrate, params->avgBitrate ) + || ( params->dsi_payload && params->dsi_payload_length != 0 && mp4sys_add_DecoderSpecificInfo( esd, params->dsi_payload, params->dsi_payload_length ) ) + ){ + mp4sys_remove_ES_Descriptor( esd ); + return NULL; + } + return esd; +} diff --git a/output/mp4/mp4sys.h b/output/mp4/mp4sys.h new file mode 100644 index 0000000..8413ad4 --- /dev/null +++ b/output/mp4/mp4sys.h @@ -0,0 +1,205 @@ +/***************************************************************************** + * mp4sys.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef MP4SYS_H +#define MP4SYS_H + +/*************************************************************************** + MPEG-4 Systems +***************************************************************************/ + +/* ODProfileLevelIndication */ +typedef enum { + MP4SYS_OD_PLI_Forbidden = 0x00, /* Forbidden */ + MP4SYS_OD_PLI_NOT_SPECIFIED = 0xFE, /* no OD profile specified */ + MP4SYS_OD_PLI_NONE_REQUIRED = 0xFF, /* no OD capability required */ +} mp4sys_ODProfileLevelIndication; + +/* sceneProfileLevelIndication */ +typedef enum { + MP4SYS_SCENE_PLI_RESERVED = 0x00, /* Reserved for ISO use */ + MP4SYS_SCENE_PLI_Simple2D_L1 = 0x01, /* Simple 2D L1 */ + MP4SYS_SCENE_PLI_Simple2D_L2 = 0x02, /* Simple 2D L2 */ + MP4SYS_SCENE_PLI_Audio_L1 = 0x03, /* Audio L1 */ + MP4SYS_SCENE_PLI_Audio_L2 = 0x04, /* Audio L2 */ + MP4SYS_SCENE_PLI_Audio_L3 = 0x05, /* Audio L3 */ + MP4SYS_SCENE_PLI_Audio_L4 = 0x06, /* Audio L4 */ + MP4SYS_SCENE_PLI_3D_Audio_L1 = 0x07, /* 3D Audio L1 */ + MP4SYS_SCENE_PLI_3D_Audio_L2 = 0x08, /* 3D Audio L2 */ + MP4SYS_SCENE_PLI_3D_Audio_L3 = 0x09, /* 3D Audio L3 */ + MP4SYS_SCENE_PLI_3D_Audio_L4 = 0x0A, /* 3D Audio L4 */ + MP4SYS_SCENE_PLI_Basic2D_L1 = 0x0B, /* Basic 2D L1 */ + MP4SYS_SCENE_PLI_Core2D_L1 = 0x0C, /* Core 2D L1 */ + MP4SYS_SCENE_PLI_Core2D_L2 = 0x0D, /* Core 2D L2 */ + MP4SYS_SCENE_PLI_Advanced2D_L1 = 0x0E, /* Advanced 2D L1 */ + MP4SYS_SCENE_PLI_Advanced2D_L2 = 0x0F, /* Advanced 2D L2 */ + MP4SYS_SCENE_PLI_Advanced2D_L3 = 0x10, /* Advanced 2D L3 */ + MP4SYS_SCENE_PLI_Main2D_L1 = 0x11, /* Main 2D L1 */ + MP4SYS_SCENE_PLI_Main2D_L2 = 0x12, /* Main 2D L2 */ + MP4SYS_SCENE_PLI_Main2D_L3 = 0x13, /* Main 2D L3 */ + MP4SYS_SCENE_PLI_NOT_SPECIFIED = 0xFE, /* no scene profile specified */ + MP4SYS_SCENE_PLI_NONE_REQUIRED = 0xFF, /* no scene capability required */ +} mp4sys_sceneProfileLevelIndication; + +/* 14496-2 Profile and level indication and restrictions */ +typedef enum { + MP4SYS_VISUAL_PLI_Reserved = 0x00, /* 0b00000000, Reserved */ + MP4SYS_VISUAL_PLI_Simple_PL1 = 0x01, /* 0b00000001, Simple Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_PL2 = 0x02, /* 0b00000010, Simple Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Simple_PL3 = 0x03, /* 0b00000011, Simple Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Simple_Scalable_PL1 = 0x11, /* 0b00010001, Simple Scalable Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Scalable_PL2 = 0x12, /* 0b00010010, Simple Scalable Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Core_PL1 = 0x21, /* 0b00100001, Core Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Core_PL2 = 0x22, /* 0b00100010, Core Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Main_PL2 = 0x32, /* 0b00110010, Main Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Main_PL3 = 0x33, /* 0b00110011, Main Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Main_PL4 = 0x34, /* 0b00110100, Main Profile/Level 4 */ + MP4SYS_VISUAL_PLI_N_bit_PL2 = 0x42, /* 0b01000010, N-bit Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Scalable_Texture_PL1 = 0x51, /* 0b01010001, Scalable Texture Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Face_Animation_PL1 = 0x61, /* 0b01100001, Simple Face Animation Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Face_Animation_PL2 = 0x62, /* 0b01100010, Simple Face Animation Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Simple_FBA_PL1 = 0x63, /* 0b01100011, Simple FBA Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_FBA_PL2 = 0x64, /* 0b01100100, Simple FBA Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Basic_Animated_Texture_PL1 = 0x71, /* 0b01110001, Basic Animated Texture Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Basic_Animated_Texture_PL2 = 0x72, /* 0b01110010, Basic Animated Texture Profile/Level 2 */ + MP4SYS_VISUAL_PLI_H264_AVC = 0x7F, /* ISO/IEC 14496-10 Advanced Video Codec / H.264, defined in ISO/IEC 14496-1:2001/Amd.7:2004 */ + /* NOTE: Some other implementations seem to use 0x15(0b00010101) for AVC, but I think that's wrong. */ + MP4SYS_VISUAL_PLI_Hybrid_PL1 = 0x81, /* 0b10000001, Hybrid Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Hybrid_PL2 = 0x82, /* 0b10000010, Hybrid Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL1 = 0x91, /* 0b10010001, Advanced Real Time Simple Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL2 = 0x92, /* 0b10010010, Advanced Real Time Simple Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL3 = 0x93, /* 0b10010011, Advanced Real Time Simple Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL4 = 0x94, /* 0b10010100, Advanced Real Time Simple Profile/Level 4 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL1 = 0xA1, /* 0b10100001, Core Scalable Profile/Level1 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL2 = 0xA2, /* 0b10100010, Core Scalable Profile/Level2 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL3 = 0xA3, /* 0b10100011, Core Scalable Profile/Level3 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL1 = 0xB1, /* 0b10110001, Advanced Coding Efficiency Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL2 = 0xB2, /* 0b10110010, Advanced Coding Efficiency Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL3 = 0xB3, /* 0b10110011, Advanced Coding Efficiency Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL4 = 0xB4, /* 0b10110100, Advanced Coding Efficiency Profile/Level 4 */ + MP4SYS_VISUAL_PLI_Advanced_Core_PL1 = 0xC1, /* 0b11000001, Advanced Core Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Core_PL2 = 0xC2, /* 0b11000010, Advanced Core Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L1 = 0xD1, /* 0b11010001, Advanced Scalable Texture/Level1 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L2 = 0xD2, /* 0b11010010, Advanced Scalable Texture/Level2 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L3 = 0xD3, /* 0b11010011, Advanced Scalable Texture/Level3 */ + MP4SYS_VISUAL_PLI_NOT_SPECIFIED = 0xFE, /* no visual profile specified */ + MP4SYS_VISUAL_PLI_NONE_REQUIRED = 0xFF, /* no visual capability required */ +} mp4sys_visualProfileLevelIndication; + +/* graphicsProfileLevelIndication */ +typedef enum { + MP4SYS_GRAPHICS_PLI_RESERVED = 0x00, /* Reserved for ISO use */ + MP4SYS_GRAPHICS_PLI_Simple2D_L1 = 0x01, /* Simple2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Simple2D_Text_L1 = 0x02, /* Simple 2D + Text profile L1 */ + MP4SYS_GRAPHICS_PLI_Simple2D_Text_L2 = 0x03, /* Simple 2D + Text profile L2 */ + MP4SYS_GRAPHICS_PLI_Core2D_L1 = 0x04, /* Core 2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Core2D_L2 = 0x05, /* Core 2D profile L2 */ + MP4SYS_GRAPHICS_PLI_Advanced2D_L1 = 0x06, /* Advanced 2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Advanced2D_L2 = 0x07, /* Advanced 2D profile L2 */ + MP4SYS_GRAPHICS_PLI_NOT_SPECIFIED = 0xFE, /* no graphics profile specified */ + MP4SYS_GRAPHICS_PLI_NONE_REQUIRED = 0xFF, /* no graphics capability required */ +} mp4sys_graphicsProfileLevelIndication; + +/* Just for mp4sys_setup_ES_Descriptor, to facilitate to make ES_Descriptor */ +typedef struct { + uint16_t ES_ID; /* Maybe 0 in stsd(esds), or alternatively, lower 16 bits of the TrackID */ + lsmash_mp4sys_object_type_indication objectTypeIndication; + lsmash_mp4sys_stream_type streamType; + uint32_t bufferSizeDB; /* byte unit, NOT bit unit. */ + uint32_t maxBitrate; + uint32_t avgBitrate; /* 0 if VBR */ + void* dsi_payload; /* AudioSpecificConfig or so */ + uint32_t dsi_payload_length ; /* size of dsi_payload */ +} mp4sys_ES_Descriptor_params_t; + +#ifndef MP4SYS_INTERNAL + +#include "utils.h" + +typedef void mp4sys_ES_Descriptor_t; +typedef void mp4sys_ObjectDescriptor_t; + +int mp4sys_remove_DecoderSpecificInfo( mp4sys_ES_Descriptor_t* esd ); +int mp4sys_remove_DecoderConfigDescriptor( mp4sys_ES_Descriptor_t* esd ); +int mp4sys_remove_SLConfigDescriptor( mp4sys_ES_Descriptor_t* esd ); +int mp4sys_remove_ES_Descriptor( mp4sys_ES_Descriptor_t* esd ); +int mp4sys_remove_ES_ID_Incs( mp4sys_ObjectDescriptor_t* od ); +int mp4sys_remove_ObjectDescriptor( mp4sys_ObjectDescriptor_t* od ); + +int mp4sys_add_DecoderSpecificInfo( mp4sys_ES_Descriptor_t* esd, void* dsi_payload, uint32_t dsi_payload_length ); +/* + bufferSizeDB is byte unit, NOT bit unit. + avgBitrate is 0 if VBR +*/ +int mp4sys_add_DecoderConfigDescriptor( + mp4sys_ES_Descriptor_t* esd, + lsmash_mp4sys_object_type_indication objectTypeIndication, + lsmash_mp4sys_stream_type streamType, + uint32_t bufferSizeDB, + uint32_t maxBitrate, + uint32_t avgBitrate +); +int mp4sys_add_SLConfigDescriptor( mp4sys_ES_Descriptor_t* esd ); +int mp4sys_add_ES_ID_Inc( mp4sys_ObjectDescriptor_t* od, uint32_t Track_ID); + +/* ES_ID might be usually 0 or lower 16 bits of the TrackID + 14496-14 says, "set to 0 as stored; when built into a stream, the lower 16 bits of the TrackID are used." + I'm not sure about actual meaning of "stored" and "built into a stream", but maybe 0 will do in stsd(esds). */ +mp4sys_ES_Descriptor_t* mp4sys_create_ES_Descriptor( uint16_t ES_ID ); +mp4sys_ObjectDescriptor_t* mp4sys_create_ObjectDescriptor( uint16_t ObjectDescriptorID ); +int mp4sys_to_InitialObjectDescriptor( + mp4sys_ObjectDescriptor_t* od, + uint8_t include_inline_pli, + mp4sys_ODProfileLevelIndication od_pli, + mp4sys_sceneProfileLevelIndication scene_pli, + mp4a_audioProfileLevelIndication audio_pli, + mp4sys_visualProfileLevelIndication visual_pli, + mp4sys_graphicsProfileLevelIndication graph_pli +); + +uint32_t mp4sys_update_ES_Descriptor_size( mp4sys_ES_Descriptor_t* esd ); +uint32_t mp4sys_update_ObjectDescriptor_size( mp4sys_ObjectDescriptor_t* od ); + +int mp4sys_write_ES_Descriptor( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t* esd ); +int mp4sys_write_ObjectDescriptor( lsmash_bs_t *bs, mp4sys_ObjectDescriptor_t* od ); + +int mp4sys_update_DecoderConfigDescriptor( + mp4sys_ES_Descriptor_t* esd, + uint32_t bufferSizeDB, + uint32_t maxBitrate, + uint32_t avgBitrate +); + +#ifdef LSMASH_DEMUXER_ENABLED +mp4sys_ES_Descriptor_t *mp4sys_duplicate_ES_Descriptor( mp4sys_ES_Descriptor_t *src ); + +mp4sys_ES_Descriptor_t *mp4sys_get_ES_Descriptor( lsmash_bs_t *bs ); + +int mp4sys_setup_summary_from_DecoderSpecificInfo( lsmash_audio_summary_t *summary, mp4sys_ES_Descriptor_t *esd ); +#endif + +/* to facilitate to make ES_Descriptor */ +mp4sys_ES_Descriptor_t* mp4sys_setup_ES_Descriptor( mp4sys_ES_Descriptor_params_t* params ); + +#endif /* #ifndef MP4SYS_INTERNAL */ + +#endif /* #ifndef MP4SYS_H */ diff --git a/output/mp4/summary.c b/output/mp4/summary.c new file mode 100644 index 0000000..85df7e1 --- /dev/null +++ b/output/mp4/summary.c @@ -0,0 +1,121 @@ +/***************************************************************************** + * summary.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include + +#include "importer.h" +#include "mp4a.h" + +/*************************************************************************** + summary and AudioSpecificConfig relative tools +***************************************************************************/ + +/* create AudioSpecificConfig as memory block from summary, and set it into that summary itself */ +int lsmash_setup_AudioSpecificConfig( lsmash_audio_summary_t* summary ) +{ + if( !summary ) + return -1; + lsmash_bs_t* bs = lsmash_bs_create( NULL ); /* no file writing */ + if( !bs ) + return -1; + mp4a_AudioSpecificConfig_t *asc = + mp4a_create_AudioSpecificConfig( summary->aot, + summary->frequency, + summary->channels, + summary->sbr_mode, + summary->exdata, + summary->exdata_length ); + if( !asc ) + { + lsmash_bs_cleanup( bs ); + return -1; + } + mp4a_put_AudioSpecificConfig( bs, asc ); + void* new_asc; + uint32_t new_length; + new_asc = lsmash_bs_export_data( bs, &new_length ); + mp4a_remove_AudioSpecificConfig( asc ); + lsmash_bs_cleanup( bs ); + if( !new_asc ) + return -1; + if( summary->exdata ) + free( summary->exdata ); + summary->exdata = new_asc; + summary->exdata_length = new_length; + return 0 ; +} + +/* Copy exdata into summary from memory block */ +int lsmash_summary_add_exdata( lsmash_summary_t *summary, void* exdata, uint32_t exdata_length ) +{ + if( !summary ) + return -1; + /* atomic operation */ + void* new_exdata = NULL; + if( exdata && exdata_length != 0 ) + { + new_exdata = lsmash_memdup( exdata, exdata_length ); + if( !new_exdata ) + return -1; + summary->exdata_length = exdata_length; + } + else + summary->exdata_length = 0; + + if( summary->exdata ) + free( summary->exdata ); + summary->exdata = new_exdata; + return 0; +} + +lsmash_summary_t *lsmash_create_summary( lsmash_mp4sys_stream_type stream_type ) +{ + size_t summary_size; + switch( stream_type ) + { + case MP4SYS_STREAM_TYPE_VisualStream : + summary_size = sizeof(lsmash_video_summary_t); + break; + case MP4SYS_STREAM_TYPE_AudioStream : + summary_size = sizeof(lsmash_audio_summary_t); + break; + default : + return NULL; + } + lsmash_summary_t *summary = (lsmash_summary_t *)lsmash_malloc_zero( summary_size ); + if( !summary ) + return NULL; + summary->stream_type = stream_type; + return summary; +} + +void lsmash_cleanup_summary( lsmash_summary_t *summary ) +{ + if( !summary ) + return; + if( summary->exdata ) + free( summary->exdata ); + free( summary ); +} diff --git a/output/mp4/utils.c b/output/mp4/utils.c new file mode 100644 index 0000000..127bfa9 --- /dev/null +++ b/output/mp4/utils.c @@ -0,0 +1,792 @@ +/***************************************************************************** + * utils.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include +#include + +#include "utils.h" + +uint64_t lsmash_bs_get_pos( lsmash_bs_t *bs ) +{ + return bs->pos; +} + +void lsmash_bs_empty( lsmash_bs_t *bs ) +{ + if( !bs ) + return; + memset( bs->data, 0, bs->alloc ); + bs->store = 0; + bs->pos = 0; +} + +void lsmash_bs_free( lsmash_bs_t *bs ) +{ + if( bs->data ) + free( bs->data ); + bs->data = NULL; + bs->alloc = 0; + bs->store = 0; + bs->pos = 0; +} + +void lsmash_bs_alloc( lsmash_bs_t *bs, uint64_t size ) +{ + if( (bs->alloc >= size) || bs->error ) + return; + uint64_t alloc = size + (1<<16); + uint8_t *data; + if( !bs->data ) + data = malloc( alloc ); + else + data = realloc( bs->data, alloc ); + if( !data ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return; + } + bs->data = data; + bs->alloc = alloc; +} + +/*---- bitstream writer ----*/ +void lsmash_bs_put_byte( lsmash_bs_t *bs, uint8_t value ) +{ + lsmash_bs_alloc( bs, bs->store + 1 ); + if( bs->error ) + return; + bs->data[bs->store ++] = value; +} + +void lsmash_bs_put_bytes( lsmash_bs_t *bs, void *value, uint32_t size ) +{ + if( !size || !value ) + return; + lsmash_bs_alloc( bs, bs->store + size ); + if( bs->error ) + return; + memcpy( bs->data + bs->store, value, size ); + bs->store += size; +} + +void lsmash_bs_put_be16( lsmash_bs_t *bs, uint16_t value ) +{ + lsmash_bs_put_byte( bs, (uint8_t)((value>>8)&0xff) ); + lsmash_bs_put_byte( bs, (uint8_t)(value&0xff) ); +} + +void lsmash_bs_put_be24( lsmash_bs_t *bs, uint32_t value ) +{ + lsmash_bs_put_byte( bs, (uint8_t)((value>>16)&0xff) ); + lsmash_bs_put_be16( bs, (uint16_t)(value&0xffff) ); +} + +void lsmash_bs_put_be32( lsmash_bs_t *bs, uint32_t value ) +{ + lsmash_bs_put_be16( bs, (uint16_t)((value>>16)&0xffff) ); + lsmash_bs_put_be16( bs, (uint16_t)(value&0xffff) ); +} + +void lsmash_bs_put_be64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be32( bs, (uint32_t)((value>>32)&0xffffffff) ); + lsmash_bs_put_be32( bs, (uint32_t)(value&0xffffffff) ); +} + +void lsmash_bs_put_byte_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_byte( bs, (uint8_t)(value&0xff) ); +} + +void lsmash_bs_put_be16_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be16( bs, (uint16_t)(value&0xffff) ); +} + +void lsmash_bs_put_be24_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be24( bs, (uint32_t)(value&0xffffff) ); +} + +void lsmash_bs_put_be32_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be32( bs, (uint32_t)(value&0xffffffff) ); +} + +int lsmash_bs_write_data( lsmash_bs_t *bs ) +{ + if( !bs ) + return -1; + if( !bs->store || !bs->data ) + return 0; + if( bs->error || !bs->stream || fwrite( bs->data, 1, bs->store, bs->stream ) != bs->store ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return -1; + } + bs->written += bs->store; + bs->store = 0; + return 0; +} + +lsmash_bs_t* lsmash_bs_create( char* filename ) +{ + lsmash_bs_t* bs = lsmash_malloc_zero( sizeof(lsmash_bs_t) ); + if( !bs ) + return NULL; + if( filename && (bs->stream = fopen( filename, "wb" )) == NULL ) + { + free( bs ); + return NULL; + } + return bs; +} + +void lsmash_bs_cleanup( lsmash_bs_t *bs ) +{ + if( !bs ) + return; + if( bs->stream ) + fclose( bs->stream ); + lsmash_bs_free( bs ); + free( bs ); +} + +void* lsmash_bs_export_data( lsmash_bs_t *bs, uint32_t* length ) +{ + if( !bs || !bs->data || bs->store == 0 || bs->error ) + return NULL; + void *buf = lsmash_memdup( bs->data, bs->store ); + if( !buf ) + return NULL; + if( length ) + *length = bs->store; + return buf; +} +/*---- ----*/ + +/*---- bitstream reader ----*/ +uint8_t lsmash_bs_get_byte( lsmash_bs_t *bs ) +{ + if( bs->error || !bs->data ) + return 0; + if( bs->pos + 1 > bs->store ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return 0; + } + return bs->data[bs->pos ++]; +} + +uint8_t *lsmash_bs_get_bytes( lsmash_bs_t *bs, uint32_t size ) +{ + if( bs->error || !size ) + return NULL; + if( bs->pos + size > bs->store ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return NULL; + } + uint8_t *value = lsmash_memdup( bs->data + bs->pos, size ); + if( !value ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return NULL; + } + bs->pos += size; + return value; +} + +uint16_t lsmash_bs_get_be16( lsmash_bs_t *bs ) +{ + uint16_t value = lsmash_bs_get_byte( bs ); + return (value<<8) | lsmash_bs_get_byte( bs ); +} + +uint32_t lsmash_bs_get_be24( lsmash_bs_t *bs ) +{ + uint32_t value = lsmash_bs_get_byte( bs ); + return (value<<16) | lsmash_bs_get_be16( bs ); +} + +uint32_t lsmash_bs_get_be32( lsmash_bs_t *bs ) +{ + uint32_t value = lsmash_bs_get_be16( bs ); + return (value<<16) | lsmash_bs_get_be16( bs ); +} + +uint64_t lsmash_bs_get_be64( lsmash_bs_t *bs ) +{ + uint64_t value = lsmash_bs_get_be32( bs ); + return (value<<32) | lsmash_bs_get_be32( bs ); +} + +uint64_t lsmash_bs_get_byte_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_byte( bs ); +} + +uint64_t lsmash_bs_get_be16_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be16( bs ); +} + +uint64_t lsmash_bs_get_be24_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be24( bs ); +} + +uint64_t lsmash_bs_get_be32_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be32( bs ); +} + +int lsmash_bs_read_data( lsmash_bs_t *bs, uint32_t size ) +{ + if( !bs ) + return -1; + if( !size ) + return 0; + lsmash_bs_alloc( bs, bs->store + size ); + if( bs->error || !bs->stream ) + { + lsmash_bs_free( bs ); + bs->error = 1; + return -1; + } + uint64_t read_size = fread( bs->data + bs->store, 1, size, bs->stream ); + if( read_size != size && !feof( bs->stream ) ) + { + bs->error = 1; + return -1; + } + bs->store += read_size; + return 0; +} + +int lsmash_bs_import_data( lsmash_bs_t *bs, void* data, uint32_t length ) +{ + if( !bs || bs->error || !data || length == 0 ) + return -1; + lsmash_bs_alloc( bs, bs->store + length ); + if( bs->error || !bs->data ) /* means, failed to alloc. */ + { + lsmash_bs_free( bs ); + return -1; + } + memcpy( bs->data + bs->store, data, length ); + bs->store += length; + return 0; +} +/*---- ----*/ + +/*---- bitstream ----*/ +void lsmash_bits_init( lsmash_bits_t *bits, lsmash_bs_t *bs ) +{ + debug_if( !bits || !bs ) + return; + bits->bs = bs; + bits->store = 0; + bits->cache = 0; +} + +lsmash_bits_t *lsmash_bits_create( lsmash_bs_t *bs ) +{ + debug_if( !bs ) + return NULL; + lsmash_bits_t *bits = (lsmash_bits_t *)malloc( sizeof(lsmash_bits_t) ); + if( !bits ) + return NULL; + lsmash_bits_init( bits, bs ); + return bits; +} + +void lsmash_bits_empty( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + lsmash_bs_empty( bits->bs ); + bits->store = 0; + bits->cache = 0; +} + +#define BITS_IN_BYTE 8 +void lsmash_bits_put_align( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + if( !bits->store ) + return; + lsmash_bs_put_byte( bits->bs, bits->cache << ( BITS_IN_BYTE - bits->store ) ); +} + +void lsmash_bits_get_align( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + bits->store = 0; + bits->cache = 0; +} + +/* Must be used ONLY for bits struct created with isom_create_bits. + Otherwise, just free() the bits struct. */ +void lsmash_bits_cleanup( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + free( bits ); +} + +/* we can change value's type to unsigned int for 64-bit operation if needed. */ +static inline uint8_t lsmash_bits_mask_lsb8( uint32_t value, uint32_t width ) +{ + return (uint8_t)( value & ~( ~0U << width ) ); +} + +/* We can change value's type to unsigned int for 64-bit operation if needed. */ +void lsmash_bits_put( lsmash_bits_t *bits, uint32_t value, uint32_t width ) +{ + debug_if( !bits || !width ) + return; + if( bits->store ) + { + if( bits->store + width < BITS_IN_BYTE ) + { + /* cache can contain all of value's bits. */ + bits->cache <<= width; + bits->cache |= lsmash_bits_mask_lsb8( value, width ); + bits->store += width; + return; + } + /* flush cache with value's some leading bits. */ + uint32_t free_bits = BITS_IN_BYTE - bits->store; + bits->cache <<= free_bits; + bits->cache |= lsmash_bits_mask_lsb8( value >> (width -= free_bits), free_bits ); + lsmash_bs_put_byte( bits->bs, bits->cache ); + bits->store = 0; + bits->cache = 0; + } + /* cache is empty here. */ + /* byte unit operation. */ + while( width > BITS_IN_BYTE ) + lsmash_bs_put_byte( bits->bs, (uint8_t)(value >> (width -= BITS_IN_BYTE)) ); + /* bit unit operation for residual. */ + if( width ) + { + bits->cache = lsmash_bits_mask_lsb8( value, width ); + bits->store = width; + } +} + +/* We can change value's type to unsigned int for 64-bit operation if needed. */ +uint32_t lsmash_bits_get( lsmash_bits_t *bits, uint32_t width ) +{ + debug_if( !bits || !width ) + return 0; + uint32_t value = 0; + if( bits->store ) + { + if( bits->store >= width ) + { + /* cache contains all of bits required. */ + bits->store -= width; + return lsmash_bits_mask_lsb8( bits->cache >> bits->store, width ); + } + /* fill value's leading bits with cache's residual. */ + value = lsmash_bits_mask_lsb8( bits->cache, bits->store ); + width -= bits->store; + bits->store = 0; + bits->cache = 0; + } + /* cache is empty here. */ + /* byte unit operation. */ + while( width > BITS_IN_BYTE ) + { + value <<= BITS_IN_BYTE; + width -= BITS_IN_BYTE; + value |= lsmash_bs_get_byte( bits->bs ); + } + /* bit unit operation for residual. */ + if( width ) + { + bits->cache = lsmash_bs_get_byte( bits->bs ); + bits->store = BITS_IN_BYTE - width; + value <<= width; + value |= lsmash_bits_mask_lsb8( bits->cache >> bits->store, width ); + } + return value; +} + +/**** + bitstream with bytestream for adhoc operation +****/ + +lsmash_bits_t* lsmash_bits_adhoc_create() +{ + lsmash_bs_t* bs = lsmash_bs_create( NULL ); /* no file writing */ + if( !bs ) + return NULL; + lsmash_bits_t* bits = lsmash_bits_create( bs ); + if( !bits ) + { + lsmash_bs_cleanup( bs ); + return NULL; + } + return bits; +} + +void lsmash_bits_adhoc_cleanup( lsmash_bits_t* bits ) +{ + if( !bits ) + return; + lsmash_bs_cleanup( bits->bs ); + lsmash_bits_cleanup( bits ); +} + +void* lsmash_bits_export_data( lsmash_bits_t* bits, uint32_t* length ) +{ + lsmash_bits_put_align( bits ); + return lsmash_bs_export_data( bits->bs, length ); +} + +int lsmash_bits_import_data( lsmash_bits_t* bits, void* data, uint32_t length ) +{ + return lsmash_bs_import_data( bits->bs, data, length ); +} +/*---- ----*/ + +/*---- list ----*/ +void lsmash_init_entry_list( lsmash_entry_list_t *list ) +{ + list->head = NULL; + list->tail = NULL; + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + list->entry_count = 0; +} + +lsmash_entry_list_t *lsmash_create_entry_list( void ) +{ + lsmash_entry_list_t *list = malloc( sizeof(lsmash_entry_list_t) ); + if( !list ) + return NULL; + lsmash_init_entry_list( list ); + return list; +} + +int lsmash_add_entry( lsmash_entry_list_t *list, void *data ) +{ + if( !list ) + return -1; + lsmash_entry_t *entry = malloc( sizeof(lsmash_entry_t) ); + if( !entry ) + return -1; + entry->next = NULL; + entry->prev = list->tail; + entry->data = data; + if( list->head ) + list->tail->next = entry; + else + list->head = entry; + list->tail = entry; + list->entry_count += 1; + return 0; +} + +int lsmash_remove_entry_direct( lsmash_entry_list_t *list, lsmash_entry_t *entry, void* eliminator ) +{ + if( !entry ) + return -1; + if( !eliminator ) + eliminator = free; + lsmash_entry_t *next = entry->next; + lsmash_entry_t *prev = entry->prev; + if( entry == list->head ) + list->head = next; + else + prev->next = next; + if( entry == list->tail ) + list->tail = prev; + else + next->prev = prev; + if( entry->data ) + ((lsmash_entry_data_eliminator)eliminator)( entry->data ); + if( entry == list->last_accessed_entry ) + { + if( next ) + list->last_accessed_entry = next; + else if( prev ) + { + list->last_accessed_entry = prev; + list->last_accessed_number -= 1; + } + else + { + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + } + } + else + { + /* We can't know the current entry number immediately, + * so discard the last accessed entry info because time is wasted to know it. */ + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + } + free( entry ); + list->entry_count -= 1; + return 0; +} + +int lsmash_remove_entry( lsmash_entry_list_t *list, uint32_t entry_number, void* eliminator ) +{ + lsmash_entry_t *entry = lsmash_get_entry( list, entry_number ); + return lsmash_remove_entry_direct( list, entry, eliminator ); +} + +void lsmash_remove_entries( lsmash_entry_list_t *list, void* eliminator ) +{ + if( !list ) + return; + if( !eliminator ) + eliminator = free; + for( lsmash_entry_t *entry = list->head; entry; ) + { + lsmash_entry_t *next = entry->next; + if( entry->data ) + ((lsmash_entry_data_eliminator)eliminator)( entry->data ); + free( entry ); + entry = next; + } + lsmash_init_entry_list( list ); +} + +void lsmash_remove_list( lsmash_entry_list_t *list, void* eliminator ) +{ + if( !list ) + return; + lsmash_remove_entries( list, eliminator ); + free( list ); +} + +lsmash_entry_t *lsmash_get_entry( lsmash_entry_list_t *list, uint32_t entry_number ) +{ + if( !list || !entry_number || entry_number > list->entry_count ) + return NULL; + int shortcut = 1; + lsmash_entry_t *entry = NULL; + if( list->last_accessed_entry ) + { + if( entry_number == list->last_accessed_number ) + entry = list->last_accessed_entry; + else if( entry_number == list->last_accessed_number + 1 ) + entry = list->last_accessed_entry->next; + else if( entry_number == list->last_accessed_number - 1 ) + entry = list->last_accessed_entry->prev; + else + shortcut = 0; + } + else + shortcut = 0; + if( !shortcut ) + { + if( entry_number <= (list->entry_count >> 1) ) + { + /* Look for from the head. */ + uint32_t distance_plus_one = entry_number; + for( entry = list->head; entry && --distance_plus_one; entry = entry->next ); + } + else + { + /* Look for from the tail. */ + uint32_t distance = list->entry_count - entry_number; + for( entry = list->tail; entry && distance--; entry = entry->prev ); + } + } + if( entry ) + { + list->last_accessed_entry = entry; + list->last_accessed_number = entry_number; + } + return entry; +} + +void *lsmash_get_entry_data( lsmash_entry_list_t *list, uint32_t entry_number ) +{ + lsmash_entry_t *entry = lsmash_get_entry( list, entry_number ); + return entry ? entry->data : NULL; +} +/*---- ----*/ + +/*---- type ----*/ +double lsmash_fixed2double( uint64_t value, int frac_width ) +{ + return value / (double)(1ULL << frac_width); +} + +float lsmash_int2float32( uint32_t value ) +{ + return (union {uint32_t i; float f;}){value}.f; +} + +double lsmash_int2float64( uint64_t value ) +{ + return (union {uint64_t i; double d;}){value}.d; +} +/*---- ----*/ + +/*---- allocator ----*/ +void *lsmash_malloc_zero( size_t size ) +{ + if( !size ) + return NULL; + void *p = malloc( size ); + if( !p ) + return NULL; + memset( p, 0, size ); + return p; +} + +void *lsmash_memdup( void *src, size_t size ) +{ + if( !size ) + return NULL; + void *dst = malloc( size ); + if( !dst ) + return NULL; + memcpy( dst, src, size ); + return dst; +} + +lsmash_multiple_buffers_t *lsmash_create_multiple_buffers( uint32_t number_of_buffers, uint32_t buffer_size ) +{ + if( (uint64_t)number_of_buffers * buffer_size > UINT32_MAX ) + return NULL; + lsmash_multiple_buffers_t *multiple_buffer = malloc( sizeof(lsmash_multiple_buffers_t) ); + if( !multiple_buffer ) + return NULL; + multiple_buffer->buffers = malloc( number_of_buffers * buffer_size ); + if( !multiple_buffer->buffers ) + { + free( multiple_buffer ); + return NULL; + } + multiple_buffer->number_of_buffers = number_of_buffers; + multiple_buffer->buffer_size = buffer_size; + return multiple_buffer; +} + +void *lsmash_withdraw_buffer( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_number ) +{ + if( !multiple_buffer || !buffer_number || buffer_number > multiple_buffer->number_of_buffers ) + return NULL; + return multiple_buffer->buffers + (buffer_number - 1) * multiple_buffer->buffer_size; +} + +lsmash_multiple_buffers_t *lsmash_resize_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_size ) +{ + if( !multiple_buffer ) + return NULL; + if( buffer_size == multiple_buffer->buffer_size ) + return multiple_buffer; + if( (uint64_t)multiple_buffer->number_of_buffers * buffer_size > UINT32_MAX ) + return NULL; + void *temp; + if( buffer_size > multiple_buffer->buffer_size ) + { + temp = realloc( multiple_buffer->buffers, multiple_buffer->number_of_buffers * buffer_size ); + if( !temp ) + return NULL; + for( uint32_t i = multiple_buffer->number_of_buffers - 1; i ; i-- ) + memmove( temp + buffer_size, temp + i * multiple_buffer->buffer_size, multiple_buffer->buffer_size ); + } + else + { + for( uint32_t i = 1; i < multiple_buffer->number_of_buffers; i++ ) + memmove( multiple_buffer->buffers + buffer_size, multiple_buffer->buffers + i * multiple_buffer->buffer_size, multiple_buffer->buffer_size ); + temp = realloc( multiple_buffer->buffers, multiple_buffer->number_of_buffers * buffer_size ); + if( !temp ) + return NULL; + } + multiple_buffer->buffers = temp; + multiple_buffer->buffer_size = buffer_size; + return multiple_buffer; +} + +void lsmash_destroy_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer ) +{ + if( !multiple_buffer ) + return; + if( multiple_buffer->buffers ) + free( multiple_buffer->buffers ); + free( multiple_buffer ); +} +/*---- ----*/ + +/*---- others ----*/ +void lsmash_log( lsmash_log_level level, const char* message, ... ) +{ + char *prefix; + va_list args; + + va_start( args, message ); + switch( level ) + { + case LSMASH_LOG_ERROR: + prefix = "Error"; + break; + case LSMASH_LOG_WARNING: + prefix = "Warning"; + break; + case LSMASH_LOG_INFO: + prefix = "Info"; + break; + default: + prefix = "Unknown"; + break; + } + + fprintf( stderr, "[%s]: ", prefix ); + vfprintf( stderr, message, args ); + va_end( args ); +} + +/* for qsort function */ +int lsmash_compare_dts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ) +{ + int64_t diff = (int64_t)(a->dts - b->dts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} + +int lsmash_compare_cts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ) +{ + int64_t diff = (int64_t)(a->cts - b->cts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} diff --git a/output/mp4/utils.h b/output/mp4/utils.h new file mode 100644 index 0000000..db78f8d --- /dev/null +++ b/output/mp4/utils.h @@ -0,0 +1,189 @@ +/***************************************************************************** + * utils.h: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_UTIL_H +#define LSMASH_UTIL_H + +#define debug_if(x) if(x) + +#define LSMASH_MAX( a, b ) ((a) > (b) ? (a) : (b)) +#define LSMASH_MIN( a, b ) ((a) < (b) ? (a) : (b)) + +/*---- bytestream ----*/ + +typedef struct +{ + FILE *stream; /* I/O stream */ + uint8_t error; + uint8_t *data; /* buffer for reading/writing */ + uint64_t store; /* valid data size on buffer */ + uint64_t alloc; /* total buffer size including invalid area */ + uint64_t pos; /* data position on buffer to be read next */ + uint64_t written; /* data size written into "stream" already */ +} lsmash_bs_t; + +uint64_t lsmash_bs_get_pos( lsmash_bs_t *bs ); +void lsmash_bs_empty( lsmash_bs_t *bs ); +void lsmash_bs_free( lsmash_bs_t *bs ); +void lsmash_bs_alloc( lsmash_bs_t *bs, uint64_t size ); +lsmash_bs_t* lsmash_bs_create( char* filename ); +void lsmash_bs_cleanup( lsmash_bs_t *bs ); + +/*---- bytestream writer ----*/ + +void lsmash_bs_put_byte( lsmash_bs_t *bs, uint8_t value ); +void lsmash_bs_put_bytes( lsmash_bs_t *bs, void *value, uint32_t size ); +void lsmash_bs_put_be16( lsmash_bs_t *bs, uint16_t value ); +void lsmash_bs_put_be24( lsmash_bs_t *bs, uint32_t value ); +void lsmash_bs_put_be32( lsmash_bs_t *bs, uint32_t value ); +void lsmash_bs_put_be64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_byte_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be16_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be24_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be32_from_64( lsmash_bs_t *bs, uint64_t value ); +int lsmash_bs_write_data( lsmash_bs_t *bs ); + +void* lsmash_bs_export_data( lsmash_bs_t *bs, uint32_t* length ); + +/*---- bytestream reader ----*/ +uint8_t lsmash_bs_get_byte( lsmash_bs_t *bs ); +uint8_t *lsmash_bs_get_bytes( lsmash_bs_t *bs, uint32_t size ); +uint16_t lsmash_bs_get_be16( lsmash_bs_t *bs ); +uint32_t lsmash_bs_get_be24( lsmash_bs_t *bs ); +uint32_t lsmash_bs_get_be32( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_byte_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be16_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be24_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be32_to_64( lsmash_bs_t *bs ); +int lsmash_bs_read_data( lsmash_bs_t *bs, uint32_t size ); + +/*---- bitstream ----*/ +typedef struct { + lsmash_bs_t* bs; + uint8_t store; + uint8_t cache; +} lsmash_bits_t; + +void lsmash_bits_init( lsmash_bits_t* bits, lsmash_bs_t *bs ); +lsmash_bits_t* lsmash_bits_create( lsmash_bs_t *bs ); +void lsmash_bits_empty( lsmash_bits_t *bits ); +void lsmash_bits_put_align( lsmash_bits_t *bits ); +void lsmash_bits_get_align( lsmash_bits_t *bits ); +void lsmash_bits_cleanup( lsmash_bits_t *bits ); + +/*---- bitstream writer ----*/ +void lsmash_bits_put( lsmash_bits_t *bits, uint32_t value, uint32_t width ); +uint32_t lsmash_bits_get( lsmash_bits_t *bits, uint32_t width ); +lsmash_bits_t* lsmash_bits_adhoc_create(); +void lsmash_bits_adhoc_cleanup( lsmash_bits_t* bits ); +void* lsmash_bits_export_data( lsmash_bits_t* bits, uint32_t* length ); +int lsmash_bits_import_data( lsmash_bits_t* bits, void* data, uint32_t length ); + +/*---- list ----*/ + +typedef struct lsmash_entry_tag lsmash_entry_t; + +struct lsmash_entry_tag +{ + lsmash_entry_t *next; + lsmash_entry_t *prev; + void *data; +}; + +typedef struct +{ + lsmash_entry_t *head; + lsmash_entry_t *tail; + lsmash_entry_t *last_accessed_entry; + uint32_t last_accessed_number; + uint32_t entry_count; +} lsmash_entry_list_t; + +typedef void (*lsmash_entry_data_eliminator)(void* data); /* very same as free() of standard c lib; void free(void *); */ + +void lsmash_init_entry_list( lsmash_entry_list_t *list ); +lsmash_entry_list_t *lsmash_create_entry_list( void ); +int lsmash_add_entry( lsmash_entry_list_t *list, void *data ); +int lsmash_remove_entry_direct( lsmash_entry_list_t *list, lsmash_entry_t *entry, void* eliminator ); +int lsmash_remove_entry( lsmash_entry_list_t *list, uint32_t entry_number, void* eliminator ); +void lsmash_remove_entries( lsmash_entry_list_t *list, void* eliminator ); +void lsmash_remove_list( lsmash_entry_list_t *list, void* eliminator ); + +lsmash_entry_t *lsmash_get_entry( lsmash_entry_list_t *list, uint32_t entry_number ); +void *lsmash_get_entry_data( lsmash_entry_list_t *list, uint32_t entry_number ); + +/*---- type ----*/ +double lsmash_fixed2double( uint64_t value, int frac_width ); +float lsmash_int2float32( uint32_t value ); +double lsmash_int2float64( uint64_t value ); + +/*---- allocator ----*/ +void *lsmash_malloc_zero( size_t size ); +void *lsmash_memdup( void *src, size_t size ); + +typedef struct +{ + uint32_t number_of_buffers; + uint32_t buffer_size; + void *buffers; +} lsmash_multiple_buffers_t; + +lsmash_multiple_buffers_t *lsmash_create_multiple_buffers( uint32_t number_of_buffers, uint32_t buffer_size ); +void *lsmash_withdraw_buffer( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_number ); +lsmash_multiple_buffers_t *lsmash_resize_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_size ); +void lsmash_destroy_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer ); + +/*---- others ----*/ +typedef enum +{ + LSMASH_LOG_ERROR, + LSMASH_LOG_WARNING, + LSMASH_LOG_INFO, +} lsmash_log_level; + +void lsmash_log( lsmash_log_level level, const char* message, ... ); +int lsmash_compare_dts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ); +int lsmash_compare_cts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ); + +static inline uint64_t lsmash_get_gcd( uint64_t a, uint64_t b ) +{ + if( !b ) + return a; + while( 1 ) + { + uint64_t c = a % b; + if( !c ) + return b; + a = b; + b = c; + } +} + +static inline uint64_t lsmash_get_lcm( uint64_t a, uint64_t b ) +{ + if( !a ) + return 0; + return (a / lsmash_get_gcd( a, b )) * b; +} + +#endif diff --git a/output/mp4/write.c b/output/mp4/write.c new file mode 100644 index 0000000..04e2410 --- /dev/null +++ b/output/mp4/write.c @@ -0,0 +1,1724 @@ +/***************************************************************************** + * write.c: + ***************************************************************************** + * Copyright (C) 2010-2011 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "isom.h" +#include "mp4a.h" +#include "mp4sys.h" +#include "write.h" + +static void isom_bs_put_basebox_common( lsmash_bs_t *bs, isom_box_t *box ) +{ + if( box->size > UINT32_MAX ) + { + lsmash_bs_put_be32( bs, 1 ); + lsmash_bs_put_be32( bs, box->type ); + lsmash_bs_put_be64( bs, box->size ); /* largesize */ + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)box->size ); + lsmash_bs_put_be32( bs, box->type ); + } + if( box->type == ISOM_BOX_TYPE_UUID ) + lsmash_bs_put_bytes( bs, box->usertype, 16 ); +} + +static void isom_bs_put_fullbox_common( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_basebox_common( bs, box ); + lsmash_bs_put_byte( bs, box->version ); + lsmash_bs_put_be24( bs, box->flags ); +} + +static void isom_bs_put_box_common( lsmash_bs_t *bs, void *box ) +{ + if( !box ) + { + bs->error = 1; + return; + } + isom_box_t *parent = ((isom_box_t *)box)->parent; + if( parent && parent->type == ISOM_BOX_TYPE_STSD ) + { + isom_bs_put_basebox_common( bs, (isom_box_t *)box ); + return; + } + if( isom_is_fullbox( box ) ) + isom_bs_put_fullbox_common( bs, (isom_box_t *)box ); + else + isom_bs_put_basebox_common( bs, (isom_box_t *)box ); +} + +static int isom_write_tkhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_tkhd_t *tkhd = trak->tkhd; + if( !tkhd ) + return -1; + isom_bs_put_box_common( bs, tkhd ); + if( tkhd->version ) + { + lsmash_bs_put_be64( bs, tkhd->creation_time ); + lsmash_bs_put_be64( bs, tkhd->modification_time ); + lsmash_bs_put_be32( bs, tkhd->track_ID ); + lsmash_bs_put_be32( bs, tkhd->reserved1 ); + lsmash_bs_put_be64( bs, tkhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)tkhd->creation_time ); + lsmash_bs_put_be32( bs, (uint32_t)tkhd->modification_time ); + lsmash_bs_put_be32( bs, tkhd->track_ID ); + lsmash_bs_put_be32( bs, tkhd->reserved1 ); + lsmash_bs_put_be32( bs, (uint32_t)tkhd->duration ); + } + lsmash_bs_put_be32( bs, tkhd->reserved2[0] ); + lsmash_bs_put_be32( bs, tkhd->reserved2[1] ); + lsmash_bs_put_be16( bs, tkhd->layer ); + lsmash_bs_put_be16( bs, tkhd->alternate_group ); + lsmash_bs_put_be16( bs, tkhd->volume ); + lsmash_bs_put_be16( bs, tkhd->reserved3 ); + for( uint32_t i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, tkhd->matrix[i] ); + lsmash_bs_put_be32( bs, tkhd->width ); + lsmash_bs_put_be32( bs, tkhd->height ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_clef( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_clef_t *clef = trak->tapt->clef; + if( !clef ) + return 0; + isom_bs_put_box_common( bs, clef ); + lsmash_bs_put_be32( bs, clef->width ); + lsmash_bs_put_be32( bs, clef->height ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_prof( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_prof_t *prof = trak->tapt->prof; + if( !prof ) + return 0; + isom_bs_put_box_common( bs, prof ); + lsmash_bs_put_be32( bs, prof->width ); + lsmash_bs_put_be32( bs, prof->height ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_enof( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_enof_t *enof = trak->tapt->enof; + if( !enof ) + return 0; + isom_bs_put_box_common( bs, enof ); + lsmash_bs_put_be32( bs, enof->width ); + lsmash_bs_put_be32( bs, enof->height ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_tapt( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_tapt_t *tapt = trak->tapt; + if( !tapt ) + return 0; + isom_bs_put_box_common( bs, tapt ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_clef( bs, trak ) + || isom_write_prof( bs, trak ) + || isom_write_enof( bs, trak ) ) + return -1; + return 0; +} + +static int isom_write_elst( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_elst_t *elst = trak->edts->elst; + if( !elst ) + return -1; + if( !elst->list->entry_count ) + return 0; + if( elst->root->fragment && elst->root->bs->stream != stdout ) + elst->pos = elst->root->bs->written; /* Remember to rewrite entries. */ + isom_bs_put_box_common( bs, elst ); + lsmash_bs_put_be32( bs, elst->list->entry_count ); + for( lsmash_entry_t *entry = elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( !data ) + return -1; + if( elst->version ) + { + lsmash_bs_put_be64( bs, data->segment_duration ); + lsmash_bs_put_be64( bs, data->media_time ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)data->segment_duration ); + lsmash_bs_put_be32( bs, (uint32_t)data->media_time ); + } + lsmash_bs_put_be32( bs, data->media_rate ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_edts( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_edts_t *edts = trak->edts; + if( !edts ) + return 0; + isom_bs_put_box_common( bs, edts ); + if( lsmash_bs_write_data( bs ) ) + return -1; + return isom_write_elst( bs, trak ); +} + +static int isom_write_tref( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_tref_t *tref = trak->tref; + if( !tref ) + return 0; + isom_bs_put_box_common( bs, tref ); + if( tref->ref_list ) + for( lsmash_entry_t *entry = tref->ref_list->head; entry; entry = entry->next ) + { + isom_tref_type_t *ref = (isom_tref_type_t *)entry->data; + if( !ref ) + return -1; + isom_bs_put_box_common( bs, ref ); + for( uint32_t i = 0; i < ref->ref_count; i++ ) + lsmash_bs_put_be32( bs, ref->track_ID[i] ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_mdhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_mdhd_t *mdhd = trak->mdia->mdhd; + if( !mdhd ) + return -1; + isom_bs_put_box_common( bs, mdhd ); + if( mdhd->version ) + { + lsmash_bs_put_be64( bs, mdhd->creation_time ); + lsmash_bs_put_be64( bs, mdhd->modification_time ); + lsmash_bs_put_be32( bs, mdhd->timescale ); + lsmash_bs_put_be64( bs, mdhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)mdhd->creation_time ); + lsmash_bs_put_be32( bs, (uint32_t)mdhd->modification_time ); + lsmash_bs_put_be32( bs, mdhd->timescale ); + lsmash_bs_put_be32( bs, (uint32_t)mdhd->duration ); + } + lsmash_bs_put_be16( bs, mdhd->language ); + lsmash_bs_put_be16( bs, mdhd->quality ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_hdlr( lsmash_bs_t *bs, isom_hdlr_t *hdlr, uint32_t parent_type ) +{ + if( !hdlr ) + return parent_type == ISOM_BOX_TYPE_MINF ? 0 : -1; + isom_bs_put_box_common( bs, hdlr ); + lsmash_bs_put_be32( bs, hdlr->componentType ); + lsmash_bs_put_be32( bs, hdlr->componentSubtype ); + lsmash_bs_put_be32( bs, hdlr->componentManufacturer ); + lsmash_bs_put_be32( bs, hdlr->componentFlags ); + lsmash_bs_put_be32( bs, hdlr->componentFlagsMask ); + lsmash_bs_put_bytes( bs, hdlr->componentName, hdlr->componentName_length ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_vmhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_vmhd_t *vmhd = trak->mdia->minf->vmhd; + if( !vmhd ) + return -1; + isom_bs_put_box_common( bs, vmhd ); + lsmash_bs_put_be16( bs, vmhd->graphicsmode ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, vmhd->opcolor[i] ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_smhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_smhd_t *smhd = trak->mdia->minf->smhd; + if( !smhd ) + return -1; + isom_bs_put_box_common( bs, smhd ); + lsmash_bs_put_be16( bs, smhd->balance ); + lsmash_bs_put_be16( bs, smhd->reserved ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_hmhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_hmhd_t *hmhd = trak->mdia->minf->hmhd; + if( !hmhd ) + return -1; + isom_bs_put_box_common( bs, hmhd ); + lsmash_bs_put_be16( bs, hmhd->maxPDUsize ); + lsmash_bs_put_be16( bs, hmhd->avgPDUsize ); + lsmash_bs_put_be32( bs, hmhd->maxbitrate ); + lsmash_bs_put_be32( bs, hmhd->avgbitrate ); + lsmash_bs_put_be32( bs, hmhd->reserved ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_nmhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_nmhd_t *nmhd = trak->mdia->minf->nmhd; + if( !nmhd ) + return -1; + isom_bs_put_box_common( bs, nmhd ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_gmin( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_gmin_t *gmin = trak->mdia->minf->gmhd->gmin; + if( !gmin ) + return -1; + isom_bs_put_box_common( bs, gmin ); + lsmash_bs_put_be16( bs, gmin->graphicsmode ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, gmin->opcolor[i] ); + lsmash_bs_put_be16( bs, gmin->balance ); + lsmash_bs_put_be16( bs, gmin->reserved ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_text( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_text_t *text = trak->mdia->minf->gmhd->text; + if( !text ) + return -1; + isom_bs_put_box_common( bs, text ); + for( uint32_t i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, text->matrix[i] ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_gmhd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_gmhd_t *gmhd = trak->mdia->minf->gmhd; + if( !gmhd ) + return -1; + isom_bs_put_box_common( bs, gmhd ); + if( isom_write_gmin( bs, trak ) || + isom_write_text( bs, trak ) ) + return -1; + return 0; +} + +static int isom_write_dref( lsmash_bs_t *bs, isom_dref_t *dref ) +{ + if( !dref || !dref->list ) + return -1; + isom_bs_put_box_common( bs, dref ); + lsmash_bs_put_be32( bs, dref->list->entry_count ); + for( lsmash_entry_t *entry = dref->list->head; entry; entry = entry->next ) + { + isom_dref_entry_t *data = (isom_dref_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + if( data->type == ISOM_BOX_TYPE_URN ) + lsmash_bs_put_bytes( bs, data->name, data->name_length ); + lsmash_bs_put_bytes( bs, data->location, data->location_length ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_dinf( lsmash_bs_t *bs, isom_dinf_t *dinf, uint32_t parent_type ) +{ + if( !dinf ) + return parent_type == ISOM_BOX_TYPE_MINF ? -1 : 0; + isom_bs_put_box_common( bs, dinf ); + if( lsmash_bs_write_data( bs ) ) + return -1; + return isom_write_dref( bs, dinf->dref ); +} + +static int isom_write_pasp( lsmash_bs_t *bs, isom_pasp_t *pasp ) +{ + if( !pasp ) + return 0; + isom_bs_put_box_common( bs, pasp ); + lsmash_bs_put_be32( bs, pasp->hSpacing ); + lsmash_bs_put_be32( bs, pasp->vSpacing ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_clap( lsmash_bs_t *bs, isom_clap_t *clap ) +{ + if( !clap ) + return 0; + isom_bs_put_box_common( bs, clap ); + lsmash_bs_put_be32( bs, clap->cleanApertureWidthN ); + lsmash_bs_put_be32( bs, clap->cleanApertureWidthD ); + lsmash_bs_put_be32( bs, clap->cleanApertureHeightN ); + lsmash_bs_put_be32( bs, clap->cleanApertureHeightD ); + lsmash_bs_put_be32( bs, clap->horizOffN ); + lsmash_bs_put_be32( bs, clap->horizOffD ); + lsmash_bs_put_be32( bs, clap->vertOffN ); + lsmash_bs_put_be32( bs, clap->vertOffD ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_colr( lsmash_bs_t *bs, isom_colr_t *colr ) +{ + if( !colr || colr->color_parameter_type == QT_COLOR_PARAMETER_TYPE_PROF ) + return 0; + isom_bs_put_box_common( bs, colr ); + lsmash_bs_put_be32( bs, colr->color_parameter_type ); + lsmash_bs_put_be16( bs, colr->primaries_index ); + lsmash_bs_put_be16( bs, colr->transfer_function_index ); + lsmash_bs_put_be16( bs, colr->matrix_index ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stsl( lsmash_bs_t *bs, isom_stsl_t *stsl ) +{ + if( !stsl ) + return 0; + isom_bs_put_box_common( bs, stsl ); + lsmash_bs_put_byte( bs, stsl->constraint_flag ); + lsmash_bs_put_byte( bs, stsl->scale_method ); + lsmash_bs_put_be16( bs, stsl->display_center_x ); + lsmash_bs_put_be16( bs, stsl->display_center_y ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_esds( lsmash_bs_t *bs, isom_esds_t *esds ) +{ + if( !esds ) + return 0; + isom_bs_put_box_common( bs, esds ); + return mp4sys_write_ES_Descriptor( bs, esds->ES ); +} + +static int isom_put_ps_entries( lsmash_bs_t *bs, lsmash_entry_list_t *list ) +{ + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + { + isom_avcC_ps_entry_t *data = (isom_avcC_ps_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be16( bs, data->parameterSetLength ); + lsmash_bs_put_bytes( bs, data->parameterSetNALUnit, data->parameterSetLength ); + } + return 0; +} + +static int isom_write_avcC( lsmash_bs_t *bs, isom_avcC_t *avcC ) +{ + if( !avcC ) + return 0; + if( !avcC->sequenceParameterSets || !avcC->pictureParameterSets ) + return -1; + isom_bs_put_box_common( bs, avcC ); + lsmash_bs_put_byte( bs, avcC->configurationVersion ); + lsmash_bs_put_byte( bs, avcC->AVCProfileIndication ); + lsmash_bs_put_byte( bs, avcC->profile_compatibility ); + lsmash_bs_put_byte( bs, avcC->AVCLevelIndication ); + lsmash_bs_put_byte( bs, avcC->lengthSizeMinusOne | 0xfc ); /* upper 6-bits are reserved as 111111b */ + lsmash_bs_put_byte( bs, avcC->numOfSequenceParameterSets | 0xe0 ); /* upper 3-bits are reserved as 111b */ + if( isom_put_ps_entries( bs, avcC->sequenceParameterSets ) ) + return -1; + lsmash_bs_put_byte( bs, avcC->numOfPictureParameterSets ); + if( isom_put_ps_entries( bs, avcC->pictureParameterSets ) ) + return -1; + if( ISOM_REQUIRES_AVCC_EXTENSION( avcC->AVCProfileIndication ) ) + { + lsmash_bs_put_byte( bs, avcC->chroma_format | 0xfc ); /* upper 6-bits are reserved as 111111b */ + lsmash_bs_put_byte( bs, avcC->bit_depth_luma_minus8 | 0xf8 ); /* upper 5-bits are reserved as 11111b */ + lsmash_bs_put_byte( bs, avcC->bit_depth_chroma_minus8 | 0xf8 ); /* upper 5-bits are reserved as 11111b */ + lsmash_bs_put_byte( bs, avcC->numOfSequenceParameterSetExt ); + if( isom_put_ps_entries( bs, avcC->sequenceParameterSetExt ) ) + return -1; + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_btrt( lsmash_bs_t *bs, isom_btrt_t *btrt ) +{ + if( !btrt ) + return 0; + isom_bs_put_box_common( bs, btrt ); + lsmash_bs_put_be32( bs, btrt->bufferSizeDB ); + lsmash_bs_put_be32( bs, btrt->maxBitrate ); + lsmash_bs_put_be32( bs, btrt->avgBitrate ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_visual_extensions( lsmash_bs_t *bs, isom_visual_entry_t *visual ) +{ + if( !visual ) + return 0; + if( isom_write_avcC( bs, visual->avcC ) + || isom_write_btrt( bs, visual->btrt ) + || isom_write_esds( bs, visual->esds ) + || isom_write_colr( bs, visual->colr ) + || isom_write_stsl( bs, visual->stsl ) + || isom_write_clap( bs, visual->clap ) + || isom_write_pasp( bs, visual->pasp ) ) + return -1; + return 0; +} + +static int isom_write_frma( lsmash_bs_t *bs, isom_frma_t *frma ) +{ + if( !frma ) + return -1; + isom_bs_put_box_common( bs, frma ); + lsmash_bs_put_be32( bs, frma->data_format ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_enda( lsmash_bs_t *bs, isom_enda_t *enda ) +{ + if( !enda ) + return 0; + isom_bs_put_box_common( bs, enda ); + lsmash_bs_put_be16( bs, enda->littleEndian ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_mp4a( lsmash_bs_t *bs, isom_mp4a_t *mp4a ) +{ + if( !mp4a ) + return 0; + isom_bs_put_box_common( bs, mp4a ); + lsmash_bs_put_be32( bs, mp4a->unknown ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_terminator( lsmash_bs_t *bs, isom_terminator_t *terminator ) +{ + if( !terminator ) + return -1; + isom_bs_put_box_common( bs, terminator ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_wave( lsmash_bs_t *bs, isom_wave_t *wave ) +{ + if( !wave ) + return 0; + isom_bs_put_box_common( bs, wave ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_frma( bs, wave->frma ) + || isom_write_enda( bs, wave->enda ) + || isom_write_mp4a( bs, wave->mp4a ) ) + return -1; + lsmash_bs_put_bytes( bs, wave->exdata, wave->exdata_length ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_esds( bs, wave->esds ) ) + return -1; + return isom_write_terminator( bs, wave->terminator ); +} + +static int isom_write_chan( lsmash_bs_t *bs, isom_chan_t *chan ) +{ + if( !chan ) + return 0; + isom_bs_put_box_common( bs, chan ); + lsmash_bs_put_be32( bs, chan->channelLayoutTag ); + lsmash_bs_put_be32( bs, chan->channelBitmap ); + lsmash_bs_put_be32( bs, chan->numberChannelDescriptions ); + if( chan->channelDescriptions ) + for( uint32_t i = 0; i < chan->numberChannelDescriptions; i++ ) + { + isom_channel_description_t *channelDescriptions = (isom_channel_description_t *)(&chan->channelDescriptions[i]); + if( !channelDescriptions ) + return -1; + lsmash_bs_put_be32( bs, channelDescriptions->channelLabel ); + lsmash_bs_put_be32( bs, channelDescriptions->channelFlags ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[0] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[1] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[2] ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_audio_extensions( lsmash_bs_t *bs, isom_audio_entry_t *audio ) +{ + if( !audio ) + return 0; + if( isom_write_esds( bs, audio->esds ) + || isom_write_wave( bs, audio->wave ) + || isom_write_chan( bs, audio->chan ) ) + return -1; + return 0; +} + +static int isom_write_visual_entry( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_visual_entry_t *data = (isom_visual_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be16( bs, data->version ); + lsmash_bs_put_be16( bs, data->revision_level ); + lsmash_bs_put_be32( bs, data->vendor ); + lsmash_bs_put_be32( bs, data->temporalQuality ); + lsmash_bs_put_be32( bs, data->spatialQuality ); + lsmash_bs_put_be16( bs, data->width ); + lsmash_bs_put_be16( bs, data->height ); + lsmash_bs_put_be32( bs, data->horizresolution ); + lsmash_bs_put_be32( bs, data->vertresolution ); + lsmash_bs_put_be32( bs, data->dataSize ); + lsmash_bs_put_be16( bs, data->frame_count ); + lsmash_bs_put_bytes( bs, data->compressorname, 32 ); + lsmash_bs_put_be16( bs, data->depth ); + lsmash_bs_put_be16( bs, data->color_table_ID ); + lsmash_bs_put_bytes( bs, data->exdata, data->exdata_length ); + if( lsmash_bs_write_data( bs ) ) + return -1; + return isom_write_visual_extensions( bs, data ); +} + +static int isom_write_audio_entry( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_audio_entry_t *data = (isom_audio_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be16( bs, data->version ); + lsmash_bs_put_be16( bs, data->revision_level ); + lsmash_bs_put_be32( bs, data->vendor ); + lsmash_bs_put_be16( bs, data->channelcount ); + lsmash_bs_put_be16( bs, data->samplesize ); + lsmash_bs_put_be16( bs, data->compression_ID ); + lsmash_bs_put_be16( bs, data->packet_size ); + lsmash_bs_put_be32( bs, data->samplerate ); + if( data->version == 1 ) + { + lsmash_bs_put_be32( bs, data->samplesPerPacket ); + lsmash_bs_put_be32( bs, data->bytesPerPacket ); + lsmash_bs_put_be32( bs, data->bytesPerFrame ); + lsmash_bs_put_be32( bs, data->bytesPerSample ); + } + else if( data->version == 2 ) + { + lsmash_bs_put_be32( bs, data->sizeOfStructOnly ); + lsmash_bs_put_be64( bs, data->audioSampleRate ); + lsmash_bs_put_be32( bs, data->numAudioChannels ); + lsmash_bs_put_be32( bs, data->always7F000000 ); + lsmash_bs_put_be32( bs, data->constBitsPerChannel ); + lsmash_bs_put_be32( bs, data->formatSpecificFlags ); + lsmash_bs_put_be32( bs, data->constBytesPerAudioPacket ); + lsmash_bs_put_be32( bs, data->constLPCMFramesPerAudioPacket ); + } + lsmash_bs_put_bytes( bs, data->exdata, data->exdata_length ); + if( lsmash_bs_write_data( bs ) ) + return -1; + return isom_write_audio_extensions( bs, data ); +} + +#if 0 +static int isom_write_hint_entry( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_hint_entry_t *data = (isom_hint_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + if( data->data && data->data_length ) + lsmash_bs_put_bytes( bs, data->data, data->data_length ); + return lsmash_bs_write_data( bs ); +} + +static int ( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_metadata_entry_t *data = (isom_metadata_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + return lsmash_bs_write_data( bs ); +} +#endif + +static int isom_write_text_entry( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_text_entry_t *data = (isom_text_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be32( bs, data->displayFlags ); + lsmash_bs_put_be32( bs, data->textJustification ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, data->bgColor[i] ); + lsmash_bs_put_be16( bs, data->top ); + lsmash_bs_put_be16( bs, data->left ); + lsmash_bs_put_be16( bs, data->bottom ); + lsmash_bs_put_be16( bs, data->right ); + lsmash_bs_put_be32( bs, data->scrpStartChar ); + lsmash_bs_put_be16( bs, data->scrpHeight ); + lsmash_bs_put_be16( bs, data->scrpAscent ); + lsmash_bs_put_be16( bs, data->scrpFont ); + lsmash_bs_put_be16( bs, data->scrpFace ); + lsmash_bs_put_be16( bs, data->scrpSize ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, data->scrpColor[i] ); + lsmash_bs_put_byte( bs, data->font_name_length ); + if( data->font_name && data->font_name_length ) + lsmash_bs_put_bytes( bs, data->font_name, data->font_name_length ); + return lsmash_bs_write_data( bs ); +} + +static int isom_put_ftab( lsmash_bs_t *bs, isom_ftab_t *ftab ) +{ + if( !ftab || !ftab->list ) + return -1; + isom_bs_put_box_common( bs, ftab ); + lsmash_bs_put_be16( bs, ftab->list->entry_count ); + for( lsmash_entry_t *entry = ftab->list->head; entry; entry = entry->next ) + { + isom_font_record_t *data = (isom_font_record_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be16( bs, data->font_ID ); + lsmash_bs_put_byte( bs, data->font_name_length ); + if( data->font_name && data->font_name_length ) + lsmash_bs_put_bytes( bs, data->font_name, data->font_name_length ); + } + return 0; +} + +static int isom_write_tx3g_entry( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_tx3g_entry_t *data = (isom_tx3g_entry_t *)entry->data; + if( !data ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, data->reserved, 6 ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be32( bs, data->displayFlags ); + lsmash_bs_put_byte( bs, data->horizontal_justification ); + lsmash_bs_put_byte( bs, data->vertical_justification ); + for( uint32_t i = 0; i < 4; i++ ) + lsmash_bs_put_byte( bs, data->background_color_rgba[i] ); + lsmash_bs_put_be16( bs, data->top ); + lsmash_bs_put_be16( bs, data->left ); + lsmash_bs_put_be16( bs, data->bottom ); + lsmash_bs_put_be16( bs, data->right ); + lsmash_bs_put_be16( bs, data->startChar ); + lsmash_bs_put_be16( bs, data->endChar ); + lsmash_bs_put_be16( bs, data->font_ID ); + lsmash_bs_put_byte( bs, data->face_style_flags ); + lsmash_bs_put_byte( bs, data->font_size ); + for( uint32_t i = 0; i < 4; i++ ) + lsmash_bs_put_byte( bs, data->text_color_rgba[i] ); + isom_put_ftab( bs, data->ftab ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stsd( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stsd_t *stsd = trak->mdia->minf->stbl->stsd; + if( !stsd || !stsd->list || !stsd->list->head ) + return -1; + isom_bs_put_box_common( bs, stsd ); + lsmash_bs_put_be32( bs, stsd->list->entry_count ); + int ret = -1; + for( lsmash_entry_t *entry = stsd->list->head; entry; entry = entry->next ) + { + isom_sample_entry_t *sample = (isom_sample_entry_t *)entry->data; + if( !sample ) + return -1; + switch( sample->type ) + { + case ISOM_CODEC_TYPE_AVC1_VIDEO : + case ISOM_CODEC_TYPE_VC_1_VIDEO : + case QT_CODEC_TYPE_APCH_VIDEO : + case QT_CODEC_TYPE_APCN_VIDEO : + case QT_CODEC_TYPE_APCS_VIDEO : + case QT_CODEC_TYPE_APCO_VIDEO : + case QT_CODEC_TYPE_AP4H_VIDEO : +#ifdef LSMASH_DEMUXER_ENABLED + case ISOM_CODEC_TYPE_MP4V_VIDEO : +#endif +#if 0 + case ISOM_CODEC_TYPE_AVC2_VIDEO : + case ISOM_CODEC_TYPE_AVCP_VIDEO : + case ISOM_CODEC_TYPE_SVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC1_VIDEO : + case ISOM_CODEC_TYPE_MVC2_VIDEO : + case ISOM_CODEC_TYPE_DRAC_VIDEO : + case ISOM_CODEC_TYPE_ENCV_VIDEO : + case ISOM_CODEC_TYPE_MJP2_VIDEO : + case ISOM_CODEC_TYPE_S263_VIDEO : +#endif + ret = isom_write_visual_entry( bs, entry ); + break; + case ISOM_CODEC_TYPE_MP4A_AUDIO : + case ISOM_CODEC_TYPE_AC_3_AUDIO : + case ISOM_CODEC_TYPE_ALAC_AUDIO : + case ISOM_CODEC_TYPE_EC_3_AUDIO : + case ISOM_CODEC_TYPE_SAMR_AUDIO : + case ISOM_CODEC_TYPE_SAWB_AUDIO : + case QT_CODEC_TYPE_23NI_AUDIO : + case QT_CODEC_TYPE_NONE_AUDIO : + case QT_CODEC_TYPE_LPCM_AUDIO : + case QT_CODEC_TYPE_RAW_AUDIO : + case QT_CODEC_TYPE_SOWT_AUDIO : + case QT_CODEC_TYPE_TWOS_AUDIO : + case QT_CODEC_TYPE_FL32_AUDIO : + case QT_CODEC_TYPE_FL64_AUDIO : + case QT_CODEC_TYPE_IN24_AUDIO : + case QT_CODEC_TYPE_IN32_AUDIO : + case QT_CODEC_TYPE_NOT_SPECIFIED : +#if 0 + case ISOM_CODEC_TYPE_DRA1_AUDIO : + case ISOM_CODEC_TYPE_DTSC_AUDIO : + case ISOM_CODEC_TYPE_DTSH_AUDIO : + case ISOM_CODEC_TYPE_DTSL_AUDIO : + case ISOM_CODEC_TYPE_ENCA_AUDIO : + case ISOM_CODEC_TYPE_G719_AUDIO : + case ISOM_CODEC_TYPE_G726_AUDIO : + case ISOM_CODEC_TYPE_M4AE_AUDIO : + case ISOM_CODEC_TYPE_MLPA_AUDIO : + case ISOM_CODEC_TYPE_RAW_AUDIO : + case ISOM_CODEC_TYPE_SAWP_AUDIO : + case ISOM_CODEC_TYPE_SEVC_AUDIO : + case ISOM_CODEC_TYPE_SQCP_AUDIO : + case ISOM_CODEC_TYPE_SSMV_AUDIO : + case ISOM_CODEC_TYPE_TWOS_AUDIO : +#endif + ret = isom_write_audio_entry( bs, entry ); + break; +#if 0 + case ISOM_CODEC_TYPE_FDP_HINT : + case ISOM_CODEC_TYPE_M2TS_HINT : + case ISOM_CODEC_TYPE_PM2T_HINT : + case ISOM_CODEC_TYPE_PRTP_HINT : + case ISOM_CODEC_TYPE_RM2T_HINT : + case ISOM_CODEC_TYPE_RRTP_HINT : + case ISOM_CODEC_TYPE_RSRP_HINT : + case ISOM_CODEC_TYPE_RTP_HINT : + case ISOM_CODEC_TYPE_SM2T_HINT : + case ISOM_CODEC_TYPE_SRTP_HINT : + ret = isom_write_hint_entry( bs, entry ); + break; + case ISOM_CODEC_TYPE_IXSE_META : + case ISOM_CODEC_TYPE_METT_META : + case ISOM_CODEC_TYPE_METX_META : + case ISOM_CODEC_TYPE_MLIX_META : + case ISOM_CODEC_TYPE_OKSD_META : + case ISOM_CODEC_TYPE_SVCM_META : + case ISOM_CODEC_TYPE_TEXT_META : + case ISOM_CODEC_TYPE_URIM_META : + case ISOM_CODEC_TYPE_XML_META : + ret = isom_write_metadata_entry( bs, entry ); + break; +#endif + case ISOM_CODEC_TYPE_TX3G_TEXT : + ret = isom_write_tx3g_entry( bs, entry ); + break; + case QT_CODEC_TYPE_TEXT_TEXT : + ret = isom_write_text_entry( bs, entry ); + break; + default : + break; + } + if( ret ) + break; + } + return ret; +} + +static int isom_write_stts( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stts_t *stts = trak->mdia->minf->stbl->stts; + if( !stts || !stts->list ) + return -1; + isom_bs_put_box_common( bs, stts ); + lsmash_bs_put_be32( bs, stts->list->entry_count ); + for( lsmash_entry_t *entry = stts->list->head; entry; entry = entry->next ) + { + isom_stts_entry_t *data = (isom_stts_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->sample_delta ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_ctts( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_ctts_t *ctts = trak->mdia->minf->stbl->ctts; + if( !ctts ) + return 0; + if( !ctts->list ) + return -1; + isom_bs_put_box_common( bs, ctts ); + lsmash_bs_put_be32( bs, ctts->list->entry_count ); + for( lsmash_entry_t *entry = ctts->list->head; entry; entry = entry->next ) + { + isom_ctts_entry_t *data = (isom_ctts_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->sample_offset ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_cslg( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_cslg_t *cslg = trak->mdia->minf->stbl->cslg; + if( !cslg ) + return 0; + isom_bs_put_box_common( bs, cslg ); + lsmash_bs_put_be32( bs, cslg->compositionToDTSShift ); + lsmash_bs_put_be32( bs, cslg->leastDecodeToDisplayDelta ); + lsmash_bs_put_be32( bs, cslg->greatestDecodeToDisplayDelta ); + lsmash_bs_put_be32( bs, cslg->compositionStartTime ); + lsmash_bs_put_be32( bs, cslg->compositionEndTime ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stsz( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stsz_t *stsz = trak->mdia->minf->stbl->stsz; + if( !stsz ) + return -1; + isom_bs_put_box_common( bs, stsz ); + lsmash_bs_put_be32( bs, stsz->sample_size ); + lsmash_bs_put_be32( bs, stsz->sample_count ); + if( stsz->sample_size == 0 && stsz->list ) + for( lsmash_entry_t *entry = stsz->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->entry_size ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stss( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stss_t *stss = trak->mdia->minf->stbl->stss; + if( !stss ) + return 0; /* If the sync sample box is not present, every sample is a random access point. */ + if( !stss->list ) + return -1; + isom_bs_put_box_common( bs, stss ); + lsmash_bs_put_be32( bs, stss->list->entry_count ); + for( lsmash_entry_t *entry = stss->list->head; entry; entry = entry->next ) + { + isom_stss_entry_t *data = (isom_stss_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->sample_number ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stps( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stps_t *stps = trak->mdia->minf->stbl->stps; + if( !stps ) + return 0; + if( !stps->list ) + return -1; + isom_bs_put_box_common( bs, stps ); + lsmash_bs_put_be32( bs, stps->list->entry_count ); + for( lsmash_entry_t *entry = stps->list->head; entry; entry = entry->next ) + { + isom_stps_entry_t *data = (isom_stps_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->sample_number ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_sdtp( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_sdtp_t *sdtp = trak->mdia->minf->stbl->sdtp; + if( !sdtp ) + return 0; + if( !sdtp->list ) + return -1; + isom_bs_put_box_common( bs, sdtp ); + for( lsmash_entry_t *entry = sdtp->list->head; entry; entry = entry->next ) + { + isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)entry->data; + if( !data ) + return -1; + uint8_t temp = (data->is_leading << 6) + | (data->sample_depends_on << 4) + | (data->sample_is_depended_on << 2) + | data->sample_has_redundancy; + lsmash_bs_put_byte( bs, temp ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stsc( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stsc_t *stsc = trak->mdia->minf->stbl->stsc; + if( !stsc || !stsc->list ) + return -1; + isom_bs_put_box_common( bs, stsc ); + lsmash_bs_put_be32( bs, stsc->list->entry_count ); + for( lsmash_entry_t *entry = stsc->list->head; entry; entry = entry->next ) + { + isom_stsc_entry_t *data = (isom_stsc_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->first_chunk ); + lsmash_bs_put_be32( bs, data->samples_per_chunk ); + lsmash_bs_put_be32( bs, data->sample_description_index ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_co64( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stco_t *co64 = trak->mdia->minf->stbl->stco; + if( !co64 || !co64->list ) + return -1; + isom_bs_put_box_common( bs, co64 ); + lsmash_bs_put_be32( bs, co64->list->entry_count ); + for( lsmash_entry_t *entry = co64->list->head; entry; entry = entry->next ) + { + isom_co64_entry_t *data = (isom_co64_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be64( bs, data->chunk_offset ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stco( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stco_t *stco = trak->mdia->minf->stbl->stco; + if( !stco || !stco->list ) + return -1; + if( stco->large_presentation ) + return isom_write_co64( bs, trak ); + isom_bs_put_box_common( bs, stco ); + lsmash_bs_put_be32( bs, stco->list->entry_count ); + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + { + isom_stco_entry_t *data = (isom_stco_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->chunk_offset ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_sgpd( lsmash_bs_t *bs, isom_trak_entry_t *trak, uint32_t grouping_number ) +{ + isom_sgpd_entry_t *sgpd = (isom_sgpd_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->sgpd_list, grouping_number ); + if( !sgpd || !sgpd->list ) + return -1; + isom_bs_put_box_common( bs, sgpd ); + lsmash_bs_put_be32( bs, sgpd->grouping_type ); + if( sgpd->version == 1 ) + lsmash_bs_put_be32( bs, sgpd->default_length ); + lsmash_bs_put_be32( bs, sgpd->list->entry_count ); + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + if( !entry->data ) + return -1; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + isom_rap_entry_t *rap = (isom_rap_entry_t *)entry->data; + uint8_t temp = (rap->num_leading_samples_known << 7) + | rap->num_leading_samples; + lsmash_bs_put_byte( bs, temp ); + break; + } + case ISOM_GROUP_TYPE_ROLL : + lsmash_bs_put_be16( bs, ((isom_roll_entry_t *)entry->data)->roll_distance ); + break; + default : + /* We don't consider other grouping types currently. */ + // if( sgpd->version == 1 && !sgpd->default_length ) + // lsmash_bs_put_be32( bs, ((isom_sgpd_entry_t *)entry->data)->description_length ); + break; + } + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_sbgp( lsmash_bs_t *bs, isom_trak_entry_t *trak, uint32_t grouping_number ) +{ + isom_sbgp_entry_t *sbgp = (isom_sbgp_entry_t *)lsmash_get_entry_data( trak->mdia->minf->stbl->sbgp_list, grouping_number ); + if( !sbgp || !sbgp->list ) + return -1; + isom_bs_put_box_common( bs, sbgp ); + lsmash_bs_put_be32( bs, sbgp->grouping_type ); + if( sbgp->version == 1 ) + lsmash_bs_put_be32( bs, sbgp->grouping_type_parameter ); + lsmash_bs_put_be32( bs, sbgp->list->entry_count ); + for( lsmash_entry_t *entry = sbgp->list->head; entry; entry = entry->next ) + { + isom_group_assignment_entry_t *data = (isom_group_assignment_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->group_description_index ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_stbl( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl ) + return -1; + isom_bs_put_box_common( bs, stbl ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_stsd( bs, trak ) + || isom_write_stts( bs, trak ) + || isom_write_ctts( bs, trak ) + || isom_write_cslg( bs, trak ) + || isom_write_stss( bs, trak ) + || isom_write_stps( bs, trak ) + || isom_write_sdtp( bs, trak ) + || isom_write_stsc( bs, trak ) + || isom_write_stsz( bs, trak ) + || isom_write_stco( bs, trak ) ) + return -1; + if( stbl->sgpd_list ) + for( uint32_t i = 1; i <= stbl->sgpd_list->entry_count; i++ ) + if( isom_write_sgpd( bs, trak, i ) ) + return -1; + if( stbl->sbgp_list ) + for( uint32_t i = 1; i <= stbl->sbgp_list->entry_count; i++ ) + if( isom_write_sbgp( bs, trak, i ) ) + return -1; + return 0; +} + +static int isom_write_minf( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_minf_t *minf = trak->mdia->minf; + if( !minf ) + return -1; + isom_bs_put_box_common( bs, minf ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( (minf->vmhd && isom_write_vmhd( bs, trak )) + || (minf->smhd && isom_write_smhd( bs, trak )) + || (minf->hmhd && isom_write_hmhd( bs, trak )) + || (minf->nmhd && isom_write_nmhd( bs, trak )) + || (minf->gmhd && isom_write_gmhd( bs, trak )) ) + return -1; + if( isom_write_hdlr( bs, minf->hdlr, minf->type ) + || isom_write_dinf( bs, minf->dinf, minf->type ) + || isom_write_stbl( bs, trak ) ) + return -1; + return 0; +} + +static int isom_write_mdia( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + isom_mdia_t *mdia = trak->mdia; + if( !mdia ) + return -1; + isom_bs_put_box_common( bs, mdia ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_mdhd( bs, trak ) + || isom_write_hdlr( bs, mdia->hdlr, mdia->type ) + || isom_write_minf( bs, trak ) ) + return -1; + return 0; +} + +static int isom_write_chpl( lsmash_bs_t *bs, isom_chpl_t *chpl ) +{ + if( !chpl ) + return 0; + if( !chpl->list || chpl->version > 1 ) + return -1; + isom_bs_put_box_common( bs, chpl ); + if( chpl->version == 1 ) + { + lsmash_bs_put_byte( bs, chpl->unknown ); + lsmash_bs_put_be32( bs, chpl->list->entry_count ); + } + else /* chpl->version == 0 */ + lsmash_bs_put_byte( bs, (uint8_t)chpl->list->entry_count ); + for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + if( !data ) + return -1; + lsmash_bs_put_be64( bs, data->start_time ); + lsmash_bs_put_byte( bs, data->chapter_name_length ); + lsmash_bs_put_bytes( bs, data->chapter_name, data->chapter_name_length ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_mean( lsmash_bs_t *bs, isom_mean_t *mean ) +{ + if( !mean ) + return 0; + isom_bs_put_box_common( bs, mean ); + if( mean->meaning_string && mean->meaning_string_length ) + lsmash_bs_put_bytes( bs, mean->meaning_string, mean->meaning_string_length ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_name( lsmash_bs_t *bs, isom_name_t *name ) +{ + if( !name ) + return 0; + isom_bs_put_box_common( bs, name ); + if( name->name && name->name_length ) + lsmash_bs_put_bytes( bs, name->name, name->name_length ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_data( lsmash_bs_t *bs, isom_data_t *data ) +{ + if( !data || data->size < 16 ) + return -1; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_be16( bs, data->reserved ); + lsmash_bs_put_byte( bs, data->type_set_identifier ); + lsmash_bs_put_byte( bs, data->type_code ); + lsmash_bs_put_be32( bs, data->the_locale ); + if( data->value && data->value_length ) + lsmash_bs_put_bytes( bs, data->value, data->value_length ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_metaitem( lsmash_bs_t *bs, isom_metaitem_t *metaitem ) +{ + if( !metaitem ) + return -1; + isom_bs_put_box_common( bs, metaitem ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_mean( bs, metaitem->mean ) + || isom_write_name( bs, metaitem->name ) + || isom_write_data( bs, metaitem->data ) ) + return -1; + return 0; +} + +static int isom_write_ilst( lsmash_bs_t *bs, isom_ilst_t *ilst ) +{ + if( !ilst ) + return 0; + isom_bs_put_box_common( bs, ilst ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( ilst->item_list ) + for( lsmash_entry_t *entry = ilst->item_list->head; entry; entry = entry->next ) + if( isom_write_metaitem( bs, (isom_metaitem_t *)entry->data ) ) + return -1; + return 0; +} + +int isom_write_meta( lsmash_bs_t *bs, isom_meta_t *meta ) +{ + if( !meta ) + return 0; + isom_bs_put_box_common( bs, meta ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_hdlr( bs, meta->hdlr, meta->type ) + || isom_write_dinf( bs, meta->dinf, meta->type ) + || isom_write_ilst( bs, meta->ilst ) ) + return -1; + return 0; +} + +static int isom_write_cprt( lsmash_bs_t *bs, isom_cprt_t *cprt ) +{ + if( !cprt ) + return -1; + isom_bs_put_box_common( bs, cprt ); + lsmash_bs_put_be16( bs, cprt->language ); + lsmash_bs_put_bytes( bs, cprt->notice, cprt->notice_length ); + return lsmash_bs_write_data( bs ); +} + +int isom_write_udta( lsmash_bs_t *bs, isom_moov_t *moov, isom_trak_entry_t *trak ) +{ + /* Setting non-NULL pointer to trak means trak->udta data will be written in stream. + * If trak is set by NULL while moov is set by non-NULL pointer, moov->udta data will be written in stream. */ + isom_udta_t *udta = trak ? trak->udta : moov ? moov->udta : NULL; + if( !udta ) + return 0; + isom_bs_put_box_common( bs, udta ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( moov && isom_write_chpl( bs, udta->chpl ) ) + return -1; + if( isom_write_meta( bs, udta->meta ) ) + return -1; + if( udta->cprt_list ) + for( lsmash_entry_t *entry = udta->cprt_list->head; entry; entry = entry->next ) + if( isom_write_cprt( bs, (isom_cprt_t *)entry->data ) ) + return -1; + return 0; +} + +int isom_write_trak( lsmash_bs_t *bs, isom_trak_entry_t *trak ) +{ + if( !trak ) + return -1; + isom_bs_put_box_common( bs, trak ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_tkhd( bs, trak ) + || isom_write_tapt( bs, trak ) + || isom_write_edts( bs, trak ) + || isom_write_tref( bs, trak ) + || isom_write_mdia( bs, trak ) + || isom_write_udta( bs, NULL, trak ) + || isom_write_meta( bs, trak->meta ) ) + return -1; + return 0; +} + +int isom_write_iods( lsmash_root_t *root ) +{ + if( !root || !root->moov ) + return -1; + if( !root->moov->iods ) + return 0; + isom_iods_t *iods = root->moov->iods; + lsmash_bs_t *bs = root->bs; + isom_bs_put_box_common( bs, iods ); + return mp4sys_write_ObjectDescriptor( bs, iods->OD ); +} + +int isom_write_mvhd( lsmash_root_t *root ) +{ + if( !root || !root->moov || !root->moov->mvhd ) + return -1; + isom_mvhd_t *mvhd = root->moov->mvhd; + lsmash_bs_t *bs = root->bs; + isom_bs_put_box_common( bs, mvhd ); + if( mvhd->version ) + { + lsmash_bs_put_be64( bs, mvhd->creation_time ); + lsmash_bs_put_be64( bs, mvhd->modification_time ); + lsmash_bs_put_be32( bs, mvhd->timescale ); + lsmash_bs_put_be64( bs, mvhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)mvhd->creation_time ); + lsmash_bs_put_be32( bs, (uint32_t)mvhd->modification_time ); + lsmash_bs_put_be32( bs, mvhd->timescale ); + lsmash_bs_put_be32( bs, (uint32_t)mvhd->duration ); + } + lsmash_bs_put_be32( bs, mvhd->rate ); + lsmash_bs_put_be16( bs, mvhd->volume ); + lsmash_bs_put_be16( bs, mvhd->reserved ); + lsmash_bs_put_be32( bs, mvhd->preferredLong[0] ); + lsmash_bs_put_be32( bs, mvhd->preferredLong[1] ); + for( int i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, mvhd->matrix[i] ); + lsmash_bs_put_be32( bs, mvhd->previewTime ); + lsmash_bs_put_be32( bs, mvhd->previewDuration ); + lsmash_bs_put_be32( bs, mvhd->posterTime ); + lsmash_bs_put_be32( bs, mvhd->selectionTime ); + lsmash_bs_put_be32( bs, mvhd->selectionDuration ); + lsmash_bs_put_be32( bs, mvhd->currentTime ); + lsmash_bs_put_be32( bs, mvhd->next_track_ID ); + return lsmash_bs_write_data( bs ); +} + +static void isom_bs_put_sample_flags( lsmash_bs_t *bs, isom_sample_flags_t *flags ) +{ + uint32_t temp = (flags->reserved << 28) + | (flags->is_leading << 26) + | (flags->sample_depends_on << 24) + | (flags->sample_is_depended_on << 22) + | (flags->sample_has_redundancy << 20) + | (flags->sample_padding_value << 17) + | (flags->sample_is_non_sync_sample << 16) + | flags->sample_degradation_priority; + lsmash_bs_put_be32( bs, temp ); +} + +int isom_write_mehd( lsmash_bs_t *bs, isom_mehd_t *mehd ) +{ + if( !mehd ) + return -1; + isom_bs_put_box_common( bs, mehd ); + if( mehd->version == 1 ) + lsmash_bs_put_be64( bs, mehd->fragment_duration ); + else + lsmash_bs_put_be32( bs, (uint32_t)mehd->fragment_duration ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_trex( lsmash_bs_t *bs, isom_trex_entry_t *trex ) +{ + if( !trex ) + return -1; + isom_bs_put_box_common( bs, trex ); + lsmash_bs_put_be32( bs, trex->track_ID ); + lsmash_bs_put_be32( bs, trex->default_sample_description_index ); + lsmash_bs_put_be32( bs, trex->default_sample_duration ); + lsmash_bs_put_be32( bs, trex->default_sample_size ); + isom_bs_put_sample_flags( bs, &trex->default_sample_flags ); + return lsmash_bs_write_data( bs ); +} + +static int isom_bs_write_movie_extends_placeholder( lsmash_bs_t *bs ) +{ + /* The following will be overwritten by Movie Extends Header Box. + * We use version 1 Movie Extends Header Box since it causes extra 4 bytes region + * we cannot replace with empty Free Space Box as we place version 0 one. */ + lsmash_bs_put_be32( bs, ISOM_FULLBOX_COMMON_SIZE + 8 ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_FREE ); + lsmash_bs_put_be32( bs, 0 ); + lsmash_bs_put_be64( bs, 0 ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_mvex( lsmash_bs_t *bs, isom_mvex_t *mvex ) +{ + if( !mvex ) + return 0; + isom_bs_put_box_common( bs, mvex ); + if( lsmash_bs_write_data( bs ) ) + return -1; + /* Movie Extends Header Box is not written immediately. + * It's done after finishing all movie fragments. */ + if( mvex->mehd ) + { + if( isom_write_mehd( bs, mvex->mehd ) ) + return -1; + } + else if( bs->stream != stdout ) + { + /* + [ROOT] + |--[ftyp] + |--[moov] + |--[mvhd] + |--[trak] + * + * + * + |--[mvex] + |--[mehd] <--- mehd->pos == mvex->placeholder_pos + */ + mvex->placeholder_pos = mvex->root->bs->written; + if( isom_bs_write_movie_extends_placeholder( bs ) ) + return -1; + } + if( mvex->trex_list ) + for( lsmash_entry_t *entry = mvex->trex_list->head; entry; entry = entry->next ) + if( isom_write_trex( bs, (isom_trex_entry_t *)entry->data ) ) + return -1; + return 0; +} + +static int isom_write_mfhd( lsmash_bs_t *bs, isom_mfhd_t *mfhd ) +{ + if( !mfhd ) + return -1; + isom_bs_put_box_common( bs, mfhd ); + lsmash_bs_put_be32( bs, mfhd->sequence_number ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_tfhd( lsmash_bs_t *bs, isom_tfhd_t *tfhd ) +{ + if( !tfhd ) + return -1; + isom_bs_put_box_common( bs, tfhd ); + lsmash_bs_put_be32( bs, tfhd->track_ID ); + if( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) lsmash_bs_put_be64( bs, tfhd->base_data_offset ); + if( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) lsmash_bs_put_be32( bs, tfhd->sample_description_index ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) lsmash_bs_put_be32( bs, tfhd->default_sample_duration ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) lsmash_bs_put_be32( bs, tfhd->default_sample_size ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &tfhd->default_sample_flags ); + return lsmash_bs_write_data( bs ); +} + +static int isom_write_trun( lsmash_bs_t *bs, isom_trun_entry_t *trun ) +{ + if( !trun ) + return -1; + isom_bs_put_box_common( bs, trun ); + lsmash_bs_put_be32( bs, trun->sample_count ); + if( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) lsmash_bs_put_be32( bs, trun->data_offset ); + if( trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &trun->first_sample_flags ); + if( trun->optional ) + for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next ) + { + isom_trun_optional_row_t *data = (isom_trun_optional_row_t *)entry->data; + if( !data ) + return -1; + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) lsmash_bs_put_be32( bs, data->sample_duration ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) lsmash_bs_put_be32( bs, data->sample_size ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &data->sample_flags ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ) lsmash_bs_put_be32( bs, data->sample_composition_time_offset ); + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_traf( lsmash_bs_t *bs, isom_traf_entry_t *traf ) +{ + if( !traf ) + return -1; + isom_bs_put_box_common( bs, traf ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_tfhd( bs, traf->tfhd ) ) + return -1; + if( traf->trun_list ) + for( lsmash_entry_t *entry = traf->trun_list->head; entry; entry = entry->next ) + if( isom_write_trun( bs, (isom_trun_entry_t *)entry->data ) ) + return -1; + return 0; +} + +int isom_write_moof( lsmash_bs_t *bs, isom_moof_entry_t *moof ) +{ + if( !moof ) + return -1; + isom_bs_put_box_common( bs, moof ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_mfhd( bs, moof->mfhd ) ) + return -1; + if( moof->traf_list ) + for( lsmash_entry_t *entry = moof->traf_list->head; entry; entry = entry->next ) + if( isom_write_traf( bs, (isom_traf_entry_t *)entry->data ) ) + return -1; + return 0; +} + +static int isom_write_tfra( lsmash_bs_t *bs, isom_tfra_entry_t *tfra ) +{ + if( !tfra ) + return -1; + isom_bs_put_box_common( bs, tfra ); + uint32_t temp = (tfra->reserved << 6) + | (tfra->length_size_of_traf_num << 4) + | (tfra->length_size_of_trun_num << 2) + | tfra->length_size_of_sample_num; + lsmash_bs_put_be32( bs, tfra->track_ID ); + lsmash_bs_put_be32( bs, temp ); + lsmash_bs_put_be32( bs, tfra->number_of_entry ); + if( tfra->list ) + { + void (*bs_put_funcs[5])( lsmash_bs_t *, uint64_t ) = + { + lsmash_bs_put_byte_from_64, + lsmash_bs_put_be16_from_64, + lsmash_bs_put_be24_from_64, + lsmash_bs_put_be32_from_64, + lsmash_bs_put_be64 + }; + void (*bs_put_time) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ 3 + (tfra->version == 1) ]; + void (*bs_put_moof_offset) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ 3 + (tfra->version == 1) ]; + void (*bs_put_traf_number) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_traf_num ]; + void (*bs_put_trun_number) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_trun_num ]; + void (*bs_put_sample_number)( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_sample_num ]; + for( lsmash_entry_t *entry = tfra->list->head; entry; entry = entry->next ) + { + isom_tfra_location_time_entry_t *data = (isom_tfra_location_time_entry_t *)entry->data; + if( !data ) + return -1; + bs_put_time ( bs, data->time ); + bs_put_moof_offset ( bs, data->moof_offset ); + bs_put_traf_number ( bs, data->traf_number ); + bs_put_trun_number ( bs, data->trun_number ); + bs_put_sample_number( bs, data->sample_number ); + } + } + return lsmash_bs_write_data( bs ); +} + +static int isom_write_mfro( lsmash_bs_t *bs, isom_mfro_t *mfro ) +{ + if( !mfro ) + return -1; + isom_bs_put_box_common( bs, mfro ); + lsmash_bs_put_be32( bs, mfro->length ); + return lsmash_bs_write_data( bs ); +} + +int isom_write_mfra( lsmash_bs_t *bs, isom_mfra_t *mfra ) +{ + if( !mfra ) + return -1; + isom_bs_put_box_common( bs, mfra ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( mfra->tfra_list ) + for( lsmash_entry_t *entry = mfra->tfra_list->head; entry; entry = entry->next ) + if( isom_write_tfra( bs, (isom_tfra_entry_t *)entry->data ) ) + return -1; + return isom_write_mfro( bs, mfra->mfro ); +} + +static int isom_bs_write_largesize_placeholder( lsmash_bs_t *bs ) +{ + lsmash_bs_put_be32( bs, ISOM_BASEBOX_COMMON_SIZE ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_FREE ); + return lsmash_bs_write_data( bs ); +} + +int isom_write_mdat_header( lsmash_root_t *root, uint64_t media_size ) +{ + if( !root || !root->bs || !root->mdat ) + return -1; + isom_mdat_t *mdat = root->mdat; + lsmash_bs_t *bs = root->bs; + if( media_size ) + { + mdat->size = ISOM_BASEBOX_COMMON_SIZE + media_size; + if( mdat->size > UINT32_MAX ) + mdat->size += 8; /* large_size */ + isom_bs_put_box_common( bs, mdat ); + return 0; + } + mdat->placeholder_pos = lsmash_ftell( bs->stream ); + if( isom_bs_write_largesize_placeholder( bs ) ) + return -1; + mdat->size = ISOM_BASEBOX_COMMON_SIZE; + isom_bs_put_box_common( bs, mdat ); + return lsmash_bs_write_data( bs ); +} + +int isom_write_mdat_size( lsmash_root_t *root ) +{ + if( !root || !root->bs || !root->bs->stream ) + return -1; + if( !root->mdat ) + return 0; + isom_mdat_t *mdat = root->mdat; + uint8_t large_flag = mdat->size > UINT32_MAX; + lsmash_bs_t *bs = root->bs; + FILE *stream = bs->stream; + uint64_t current_pos = lsmash_ftell( stream ); + if( large_flag ) + { + lsmash_fseek( stream, mdat->placeholder_pos, SEEK_SET ); + lsmash_bs_put_be32( bs, 1 ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_MDAT ); + lsmash_bs_put_be64( bs, mdat->size + ISOM_BASEBOX_COMMON_SIZE ); + } + else + { + lsmash_fseek( stream, mdat->placeholder_pos + ISOM_BASEBOX_COMMON_SIZE, SEEK_SET ); + lsmash_bs_put_be32( bs, mdat->size ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_MDAT ); + } + int ret = lsmash_bs_write_data( bs ); + lsmash_fseek( stream, current_pos, SEEK_SET ); + return ret; +} + +int isom_write_ftyp( lsmash_root_t *root ) +{ + isom_ftyp_t *ftyp = root->ftyp; + if( !ftyp || !ftyp->brand_count ) + return 0; + lsmash_bs_t *bs = root->bs; + isom_bs_put_box_common( bs, ftyp ); + lsmash_bs_put_be32( bs, ftyp->major_brand ); + lsmash_bs_put_be32( bs, ftyp->minor_version ); + for( uint32_t i = 0; i < ftyp->brand_count; i++ ) + lsmash_bs_put_be32( bs, ftyp->compatible_brands[i] ); + if( lsmash_bs_write_data( bs ) ) + return -1; + root->size += ftyp->size; + root->file_type_written = 1; + return 0; +} + +int isom_write_moov( lsmash_root_t *root ) +{ + if( !root || !root->moov ) + return -1; + lsmash_bs_t *bs = root->bs; + isom_moov_t *moov = root->moov; + isom_bs_put_box_common( bs, moov ); + if( lsmash_bs_write_data( bs ) ) + return -1; + if( isom_write_mvhd( root ) + || isom_write_iods( root ) ) + return -1; + if( moov->trak_list ) + for( lsmash_entry_t *entry = moov->trak_list->head; entry; entry = entry->next ) + if( isom_write_trak( bs, (isom_trak_entry_t *)entry->data ) ) + return -1; + if( isom_write_udta( bs, moov, NULL ) + || isom_write_meta( bs, moov->meta ) ) + return -1; + return isom_write_mvex( bs, moov->mvex ); +} + +int lsmash_write_free( lsmash_root_t *root ) +{ + if( !root || !root->bs || !root->free ) + return -1; + isom_free_t *skip = root->free; + lsmash_bs_t *bs = root->bs; + skip->size = 8 + skip->length; + isom_bs_put_box_common( bs, skip ); + if( skip->data && skip->length ) + lsmash_bs_put_bytes( bs, skip->data, skip->length ); + return lsmash_bs_write_data( bs ); +} diff --git a/output/mp4/write.h b/output/mp4/write.h new file mode 100644 index 0000000..c8eee05 --- /dev/null +++ b/output/mp4/write.h @@ -0,0 +1,39 @@ +/***************************************************************************** + * write.h: + ***************************************************************************** + * Copyright (C) 2011 L-SMASH project + * + * Authors: Hiroki Taniura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_WRITE_H +#define LSMASH_WRITE_H + +int isom_write_meta( lsmash_bs_t *bs, isom_meta_t *meta ); +int isom_write_udta( lsmash_bs_t *bs, isom_moov_t *moov, isom_trak_entry_t *trak ); +int isom_write_trak( lsmash_bs_t *bs, isom_trak_entry_t *trak ); +int isom_write_iods( lsmash_root_t *root ); +int isom_write_mvhd( lsmash_root_t *root ); +int isom_write_mehd( lsmash_bs_t *bs, isom_mehd_t *mehd ); +int isom_write_moof( lsmash_bs_t *bs, isom_moof_entry_t *moof ); +int isom_write_mfra( lsmash_bs_t *bs, isom_mfra_t *mfra ); +int isom_write_mdat_header( lsmash_root_t *root, uint64_t media_size ); +int isom_write_mdat_size( lsmash_root_t *root ); +int isom_write_ftyp( lsmash_root_t *root ); +int isom_write_moov( lsmash_root_t *root ); + +#endif diff --git a/x264.c b/x264.c index 54359c6..11e2b25 100644 --- a/x264.c +++ b/x264.c @@ -114,9 +114,7 @@ static const char * const muxer_names[] = "raw", "mkv", "flv", -#if HAVE_GPAC "mp4", -#endif 0 }; @@ -386,7 +384,10 @@ static void help( x264_param_t *defaults, int longhelp ) " .264 -> Raw bytestream\n" " .mkv -> Matroska\n" " .flv -> Flash Video\n" - " .mp4 -> MP4 if compiled with GPAC support (%s)\n" + " .mp4 -> MP4\n" + " .3gp -> MP4 (branded '3gp6')\n" + " .3g2 -> MP4 (branded '3gp6' and '3g2a')\n" + " .mov or .qt -> QuickTime File Format\n" "Output bit depth: %d (configured at compile time)\n" "\n" "Options:\n" @@ -411,11 +412,6 @@ static void help( x264_param_t *defaults, int longhelp ) #else "no", #endif -#if HAVE_GPAC - "yes", -#else - "no", -#endif x264_bit_depth ); H0( "Example usage:\n" ); @@ -1029,9 +1025,9 @@ static int select_output( const char *muxer, char *filename, x264_param_t *param if( !strcmp( filename, "-" ) || strcasecmp( muxer, "auto" ) ) ext = muxer; - if( !strcasecmp( ext, "mp4" ) ) + if( !strcasecmp( ext, "mp4" ) || !strcasecmp( ext, "3gp" ) || !strcasecmp( ext, "3g2" ) || + !strcasecmp( ext, "mov" ) || !strcasecmp( ext, "qt" ) ) { -#if HAVE_GPAC cli_output = mp4_output; param->b_annexb = 0; param->b_repeat_headers = 0; @@ -1040,10 +1036,6 @@ static int select_output( const char *muxer, char *filename, x264_param_t *param x264_cli_log( "x264", X264_LOG_WARNING, "cbr nal-hrd is not compatible with mp4\n" ); param->i_nal_hrd = X264_NAL_HRD_VBR; } -#else - x264_cli_log( "x264", X264_LOG_ERROR, "not compiled with MP4 output support\n" ); - return -1; -#endif } else if( !strcasecmp( ext, "mkv" ) ) { -- 1.7.7.msysgit.1