x264 source for verification 2026-05-22

This commit is contained in:
2026-05-22 16:45:04 +08:00
commit 4647f166e5
270 changed files with 166522 additions and 0 deletions

358
output/flv.c Normal file
View File

@@ -0,0 +1,358 @@
/*****************************************************************************
* flv.c: flv muxer
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Kieran Kunhya <kieran@kunhya.com>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include "flv_bytestream.h"
#define CHECK(x)\
do {\
if( (x) < 0 )\
return -1;\
} while( 0 )
typedef struct
{
flv_buffer *c;
uint8_t *sei;
int sei_len;
int64_t i_fps_num;
int64_t i_fps_den;
int64_t i_framenum;
uint64_t i_framerate_pos;
uint64_t i_duration_pos;
uint64_t i_filesize_pos;
uint64_t i_bitrate_pos;
uint8_t b_write_length;
int64_t i_prev_dts;
int64_t i_prev_cts;
int64_t i_delay_time;
int64_t i_init_delta;
int i_delay_frames;
double d_timebase;
int b_vfr_input;
int b_dts_compress;
unsigned start;
} flv_hnd_t;
static int write_header( flv_buffer *c )
{
flv_put_tag( c, "FLV" ); // Signature
flv_put_byte( c, 1 ); // Version
flv_put_byte( c, 1 ); // Video Only
flv_put_be32( c, 9 ); // DataOffset
flv_put_be32( c, 0 ); // PreviousTagSize0
return flv_flush_data( c );
}
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
flv_hnd_t *p_flv = calloc( 1, sizeof(flv_hnd_t) );
if( p_flv )
{
flv_buffer *c = flv_create_writer( psz_filename );
if( c )
{
if( !write_header( c ) )
{
p_flv->c = c;
p_flv->b_dts_compress = opt->use_dts_compress;
*p_handle = p_flv;
return 0;
}
fclose( c->fp );
free( c->data );
free( c );
}
free( p_flv );
}
*p_handle = NULL;
return -1;
}
static int set_param( hnd_t handle, x264_param_t *p_param )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
flv_put_byte( c, FLV_TAG_TYPE_META ); // Tag Type "script data"
int start = c->d_cur;
flv_put_be24( c, 0 ); // data length
flv_put_be24( c, 0 ); // timestamp
flv_put_be32( c, 0 ); // reserved
flv_put_byte( c, AMF_DATA_TYPE_STRING );
flv_put_amf_string( c, "onMetaData" );
flv_put_byte( c, AMF_DATA_TYPE_MIXEDARRAY );
flv_put_be32( c, 7 );
flv_put_amf_string( c, "width" );
flv_put_amf_double( c, p_param->i_width );
flv_put_amf_string( c, "height" );
flv_put_amf_double( c, p_param->i_height );
flv_put_amf_string( c, "framerate" );
if( !p_param->b_vfr_input )
flv_put_amf_double( c, (double)p_param->i_fps_num / p_param->i_fps_den );
else
{
p_flv->i_framerate_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 ); // written at end of encoding
}
flv_put_amf_string( c, "videocodecid" );
flv_put_amf_double( c, FLV_CODECID_H264 );
flv_put_amf_string( c, "duration" );
p_flv->i_duration_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 ); // written at end of encoding
flv_put_amf_string( c, "filesize" );
p_flv->i_filesize_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 ); // written at end of encoding
flv_put_amf_string( c, "videodatarate" );
p_flv->i_bitrate_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 ); // written at end of encoding
flv_put_amf_string( c, "" );
flv_put_byte( c, AMF_END_OF_OBJECT );
unsigned length = c->d_cur - start;
flv_rewrite_amf_be24( c, length - 10, start );
flv_put_be32( c, length + 1 ); // tag length
p_flv->i_fps_num = p_param->i_fps_num;
p_flv->i_fps_den = p_param->i_fps_den;
p_flv->d_timebase = (double)p_param->i_timebase_num / p_param->i_timebase_den;
p_flv->b_vfr_input = p_param->b_vfr_input;
p_flv->i_delay_frames = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
return 0;
}
static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
int sps_size = p_nal[0].i_payload;
int pps_size = p_nal[1].i_payload;
int sei_size = p_nal[2].i_payload;
// SEI
/* It is within the spec to write this as-is but for
* mplayer/ffmpeg playback this is deferred until before the first frame */
p_flv->sei = malloc( sei_size );
if( !p_flv->sei )
return -1;
p_flv->sei_len = sei_size;
memcpy( p_flv->sei, p_nal[2].p_payload, sei_size );
// SPS
uint8_t *sps = p_nal[0].p_payload + 4;
flv_put_byte( c, FLV_TAG_TYPE_VIDEO );
flv_put_be24( c, 0 ); // rewrite later
flv_put_be24( c, 0 ); // timestamp
flv_put_byte( c, 0 ); // timestamp extended
flv_put_be24( c, 0 ); // StreamID - Always 0
p_flv->start = c->d_cur; // needed for overwriting length
flv_put_byte( c, FLV_FRAME_KEY | FLV_CODECID_H264 ); // FrameType and CodecID
flv_put_byte( c, 0 ); // AVC sequence header
flv_put_be24( c, 0 ); // composition time
flv_put_byte( c, 1 ); // version
flv_put_byte( c, sps[1] ); // profile
flv_put_byte( c, sps[2] ); // profile
flv_put_byte( c, sps[3] ); // level
flv_put_byte( c, 0xff ); // 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
flv_put_byte( c, 0xe1 ); // 3 bits reserved (111) + 5 bits number of sps (00001)
flv_put_be16( c, sps_size - 4 );
flv_append_data( c, sps, sps_size - 4 );
// PPS
flv_put_byte( c, 1 ); // number of pps
flv_put_be16( c, pps_size - 4 );
flv_append_data( c, p_nal[1].p_payload + 4, pps_size - 4 );
// rewrite data length info
unsigned length = c->d_cur - p_flv->start;
flv_rewrite_amf_be24( c, length, p_flv->start - 10 );
flv_put_be32( c, length + 11 ); // Last tag size
CHECK( flv_flush_data( c ) );
return sei_size + sps_size + pps_size;
}
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
#define convert_timebase_ms( timestamp, timebase ) (int64_t)((timestamp) * (timebase) * 1000 + 0.5)
if( !p_flv->i_framenum )
{
p_flv->i_delay_time = p_picture->i_dts * -1;
if( !p_flv->b_dts_compress && p_flv->i_delay_time )
x264_cli_log( "flv", X264_LOG_INFO, "initial delay %"PRId64" ms\n",
convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase ) );
}
int64_t dts;
int64_t cts;
int64_t offset;
if( p_flv->b_dts_compress )
{
if( p_flv->i_framenum == 1 )
p_flv->i_init_delta = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase );
dts = p_flv->i_framenum > p_flv->i_delay_frames
? convert_timebase_ms( p_picture->i_dts, p_flv->d_timebase )
: p_flv->i_framenum * p_flv->i_init_delta / (p_flv->i_delay_frames + 1);
cts = convert_timebase_ms( p_picture->i_pts, p_flv->d_timebase );
}
else
{
dts = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase );
cts = convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase );
}
offset = cts - dts;
if( p_flv->i_framenum )
{
if( p_flv->i_prev_dts == dts )
x264_cli_log( "flv", X264_LOG_WARNING, "duplicate DTS %"PRId64" generated by rounding\n"
" decoding framerate cannot exceed 1000fps\n", dts );
if( p_flv->i_prev_cts == cts )
x264_cli_log( "flv", X264_LOG_WARNING, "duplicate CTS %"PRId64" generated by rounding\n"
" composition framerate cannot exceed 1000fps\n", cts );
}
p_flv->i_prev_dts = dts;
p_flv->i_prev_cts = cts;
// A new frame - write packet header
flv_put_byte( c, FLV_TAG_TYPE_VIDEO );
flv_put_be24( c, 0 ); // calculated later
flv_put_be24( c, dts );
flv_put_byte( c, dts >> 24 );
flv_put_be24( c, 0 );
p_flv->start = c->d_cur;
flv_put_byte( c, (p_picture->b_keyframe ? FLV_FRAME_KEY : FLV_FRAME_INTER) | FLV_CODECID_H264 );
flv_put_byte( c, 1 ); // AVC NALU
flv_put_be24( c, offset );
if( p_flv->sei )
{
flv_append_data( c, p_flv->sei, p_flv->sei_len );
free( p_flv->sei );
p_flv->sei = NULL;
}
flv_append_data( c, p_nalu, i_size );
unsigned length = c->d_cur - p_flv->start;
flv_rewrite_amf_be24( c, length, p_flv->start - 10 );
flv_put_be32( c, 11 + length ); // Last tag size
CHECK( flv_flush_data( c ) );
p_flv->i_framenum++;
return i_size;
}
static int rewrite_amf_double( FILE *fp, uint64_t position, double value )
{
uint64_t x = endian_fix64( flv_dbl2int( value ) );
return !fseek( fp, position, SEEK_SET ) && fwrite( &x, 8, 1, fp ) == 1 ? 0 : -1;
}
#undef CHECK
#define CHECK(x)\
do {\
if( (x) < 0 )\
goto error;\
} while( 0 )
static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
int ret = -1;
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
CHECK( flv_flush_data( c ) );
double total_duration;
/* duration algorithm fails with one frame */
if( p_flv->i_framenum == 1 )
total_duration = p_flv->i_fps_num ? (double)p_flv->i_fps_den / p_flv->i_fps_num : 0;
else
total_duration = (2 * largest_pts - second_largest_pts) * p_flv->d_timebase;
if( x264_is_regular_file( c->fp ) && total_duration > 0 )
{
double framerate;
int64_t filesize = ftell( c->fp );
if( p_flv->i_framerate_pos )
{
framerate = (double)p_flv->i_framenum / total_duration;
CHECK( rewrite_amf_double( c->fp, p_flv->i_framerate_pos, framerate ) );
}
CHECK( rewrite_amf_double( c->fp, p_flv->i_duration_pos, total_duration ) );
CHECK( rewrite_amf_double( c->fp, p_flv->i_filesize_pos, filesize ) );
CHECK( rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8.0 / ( total_duration * 1000 ) ) );
}
ret = 0;
error:
fclose( c->fp );
free( c->data );
free( c );
free( p_flv );
return ret;
}
const cli_output_t flv_output = { open_file, set_param, write_headers, write_frame, close_file };

153
output/flv_bytestream.c Normal file
View File

@@ -0,0 +1,153 @@
/*****************************************************************************
* flv_bytestream.c: flv muxer utilities
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Kieran Kunhya <kieran@kunhya.com>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include "flv_bytestream.h"
uint64_t flv_dbl2int( double value )
{
return (union {double f; uint64_t i;}){value}.i;
}
/* Put functions */
void flv_put_byte( flv_buffer *c, uint8_t b )
{
flv_append_data( c, &b, 1 );
}
void flv_put_be32( flv_buffer *c, uint32_t val )
{
flv_put_byte( c, val >> 24 );
flv_put_byte( c, val >> 16 );
flv_put_byte( c, val >> 8 );
flv_put_byte( c, val );
}
void flv_put_be64( flv_buffer *c, uint64_t val )
{
flv_put_be32( c, val >> 32 );
flv_put_be32( c, val );
}
void flv_put_be16( flv_buffer *c, uint16_t val )
{
flv_put_byte( c, val >> 8 );
flv_put_byte( c, val );
}
void flv_put_be24( flv_buffer *c, uint32_t val )
{
flv_put_be16( c, val >> 8 );
flv_put_byte( c, val );
}
void flv_put_tag( flv_buffer *c, const char *tag )
{
while( *tag )
flv_put_byte( c, *tag++ );
}
void flv_put_amf_string( flv_buffer *c, const char *str )
{
uint16_t len = strlen( str );
flv_put_be16( c, len );
flv_append_data( c, (uint8_t*)str, len );
}
void flv_put_amf_double( flv_buffer *c, double d )
{
flv_put_byte( c, AMF_DATA_TYPE_NUMBER );
flv_put_be64( c, flv_dbl2int( d ) );
}
/* flv writing functions */
flv_buffer *flv_create_writer( const char *filename )
{
flv_buffer *c = calloc( 1, sizeof(flv_buffer) );
if( !c )
return NULL;
if( !strcmp( filename, "-" ) )
c->fp = stdout;
else
c->fp = x264_fopen( filename, "wb" );
if( !c->fp )
{
free( c );
return NULL;
}
return c;
}
int flv_append_data( flv_buffer *c, uint8_t *data, unsigned size )
{
unsigned ns = c->d_cur + size;
if( ns > c->d_max )
{
void *dp;
unsigned dn = 16;
while( ns > dn )
dn <<= 1;
dp = realloc( c->data, dn );
if( !dp )
return -1;
c->data = dp;
c->d_max = dn;
}
memcpy( c->data + c->d_cur, data, size );
c->d_cur = ns;
return 0;
}
void flv_rewrite_amf_be24( flv_buffer *c, unsigned length, unsigned start )
{
*(c->data + start + 0) = length >> 16;
*(c->data + start + 1) = length >> 8;
*(c->data + start + 2) = length >> 0;
}
int flv_flush_data( flv_buffer *c )
{
if( !c->d_cur )
return 0;
if( fwrite( c->data, c->d_cur, 1, c->fp ) != 1 )
return -1;
c->d_total += c->d_cur;
c->d_cur = 0;
return 0;
}

139
output/flv_bytestream.h Normal file
View File

@@ -0,0 +1,139 @@
/*****************************************************************************
* flv_bytestream.h: flv muxer utilities
*****************************************************************************
* Copyright (C) 2009-2025 x264 project
*
* Authors: Kieran Kunhya <kieran@kunhya.com>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_FLV_BYTESTREAM_H
#define X264_FLV_BYTESTREAM_H
/* offsets for packed values */
#define FLV_AUDIO_SAMPLESSIZE_OFFSET 1
#define FLV_AUDIO_SAMPLERATE_OFFSET 2
#define FLV_AUDIO_CODECID_OFFSET 4
#define FLV_VIDEO_FRAMETYPE_OFFSET 4
/* bitmasks to isolate specific values */
#define FLV_AUDIO_CHANNEL_MASK 0x01
#define FLV_AUDIO_SAMPLESIZE_MASK 0x02
#define FLV_AUDIO_SAMPLERATE_MASK 0x0c
#define FLV_AUDIO_CODECID_MASK 0xf0
#define FLV_VIDEO_CODECID_MASK 0x0f
#define FLV_VIDEO_FRAMETYPE_MASK 0xf0
#define AMF_END_OF_OBJECT 0x09
enum
{
FLV_HEADER_FLAG_HASVIDEO = 1,
FLV_HEADER_FLAG_HASAUDIO = 4,
};
enum
{
FLV_TAG_TYPE_AUDIO = 0x08,
FLV_TAG_TYPE_VIDEO = 0x09,
FLV_TAG_TYPE_META = 0x12,
};
enum
{
FLV_MONO = 0,
FLV_STEREO = 1,
};
enum
{
FLV_SAMPLESSIZE_8BIT = 0,
FLV_SAMPLESSIZE_16BIT = 1 << FLV_AUDIO_SAMPLESSIZE_OFFSET,
};
enum
{
FLV_SAMPLERATE_SPECIAL = 0, /**< signifies 5512Hz and 8000Hz in the case of NELLYMOSER */
FLV_SAMPLERATE_11025HZ = 1 << FLV_AUDIO_SAMPLERATE_OFFSET,
FLV_SAMPLERATE_22050HZ = 2 << FLV_AUDIO_SAMPLERATE_OFFSET,
FLV_SAMPLERATE_44100HZ = 3 << FLV_AUDIO_SAMPLERATE_OFFSET,
};
enum
{
FLV_CODECID_MP3 = 2 << FLV_AUDIO_CODECID_OFFSET,
FLV_CODECID_AAC = 10<< FLV_AUDIO_CODECID_OFFSET,
};
enum
{
FLV_CODECID_H264 = 7,
};
enum
{
FLV_FRAME_KEY = 1 << FLV_VIDEO_FRAMETYPE_OFFSET,
FLV_FRAME_INTER = 2 << FLV_VIDEO_FRAMETYPE_OFFSET,
};
typedef enum
{
AMF_DATA_TYPE_NUMBER = 0x00,
AMF_DATA_TYPE_BOOL = 0x01,
AMF_DATA_TYPE_STRING = 0x02,
AMF_DATA_TYPE_OBJECT = 0x03,
AMF_DATA_TYPE_NULL = 0x05,
AMF_DATA_TYPE_UNDEFINED = 0x06,
AMF_DATA_TYPE_REFERENCE = 0x07,
AMF_DATA_TYPE_MIXEDARRAY = 0x08,
AMF_DATA_TYPE_OBJECT_END = 0x09,
AMF_DATA_TYPE_ARRAY = 0x0a,
AMF_DATA_TYPE_DATE = 0x0b,
AMF_DATA_TYPE_LONG_STRING = 0x0c,
AMF_DATA_TYPE_UNSUPPORTED = 0x0d,
} AMFDataType;
typedef struct flv_buffer
{
uint8_t *data;
unsigned d_cur;
unsigned d_max;
FILE *fp;
uint64_t d_total;
} flv_buffer;
flv_buffer *flv_create_writer( const char *filename );
int flv_append_data( flv_buffer *c, uint8_t *data, unsigned size );
int flv_write_byte( flv_buffer *c, uint8_t *byte );
int flv_flush_data( flv_buffer *c );
void flv_rewrite_amf_be24( flv_buffer *c, unsigned length, unsigned start );
uint64_t flv_dbl2int( double value );
void flv_put_byte( flv_buffer *c, uint8_t b );
void flv_put_be32( flv_buffer *c, uint32_t val );
void flv_put_be64( flv_buffer *c, uint64_t val );
void flv_put_be16( flv_buffer *c, uint16_t val );
void flv_put_be24( flv_buffer *c, uint32_t val );
void flv_put_tag( flv_buffer *c, const char *tag );
void flv_put_amf_string( flv_buffer *c, const char *str );
void flv_put_amf_double( flv_buffer *c, double d );
#endif

221
output/matroska.c Normal file
View File

@@ -0,0 +1,221 @@
/*****************************************************************************
* matroska.c: matroska muxer
*****************************************************************************
* Copyright (C) 2005-2025 x264 project
*
* Authors: Mike Matsnev <mike@haali.su>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include "matroska_ebml.h"
typedef struct
{
mk_writer *w;
int width, height, d_width, d_height;
int display_size_units;
int stereo_mode;
int64_t frame_duration;
char b_writing_frame;
uint32_t i_timebase_num;
uint32_t i_timebase_den;
} mkv_hnd_t;
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
*p_handle = NULL;
mkv_hnd_t *p_mkv = calloc( 1, sizeof(mkv_hnd_t) );
if( !p_mkv )
return -1;
p_mkv->w = mk_create_writer( psz_filename );
if( !p_mkv->w )
{
free( p_mkv );
return -1;
}
*p_handle = p_mkv;
return 0;
}
#define STEREO_COUNT 7
static const uint8_t stereo_modes[STEREO_COUNT] = {5,9,7,1,3,13,0};
static const uint8_t stereo_w_div[STEREO_COUNT] = {1,2,1,2,1,1,1};
static const uint8_t stereo_h_div[STEREO_COUNT] = {1,1,2,1,2,1,1};
static int set_param( hnd_t handle, x264_param_t *p_param )
{
mkv_hnd_t *p_mkv = handle;
int64_t dw, dh;
if( p_param->i_fps_num > 0 && !p_param->b_vfr_input )
{
p_mkv->frame_duration = (int64_t)p_param->i_fps_den *
(int64_t)1000000000 / p_param->i_fps_num;
}
else
{
p_mkv->frame_duration = 0;
}
dw = p_mkv->width = p_param->i_width;
dh = p_mkv->height = p_param->i_height;
p_mkv->display_size_units = DS_PIXELS;
p_mkv->stereo_mode = -1;
if( p_param->i_frame_packing >= 0 && p_param->i_frame_packing < STEREO_COUNT )
{
p_mkv->stereo_mode = stereo_modes[p_param->i_frame_packing];
dw /= stereo_w_div[p_param->i_frame_packing];
dh /= stereo_h_div[p_param->i_frame_packing];
}
if( p_param->vui.i_sar_width && p_param->vui.i_sar_height
&& p_param->vui.i_sar_width != p_param->vui.i_sar_height )
{
if( p_param->vui.i_sar_width > p_param->vui.i_sar_height )
{
dw = dw * p_param->vui.i_sar_width / p_param->vui.i_sar_height;
}
else
{
dh = dh * p_param->vui.i_sar_height / p_param->vui.i_sar_width;
}
}
p_mkv->d_width = (int)dw;
p_mkv->d_height = (int)dh;
p_mkv->i_timebase_num = p_param->i_timebase_num;
p_mkv->i_timebase_den = p_param->i_timebase_den;
return 0;
}
static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
mkv_hnd_t *p_mkv = handle;
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;
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;
int ret;
uint8_t *avcC;
int avcC_len;
if( !p_mkv->width || !p_mkv->height ||
!p_mkv->d_width || !p_mkv->d_height )
return -1;
avcC_len = 5 + 1 + 2 + sps_size + 1 + 2 + pps_size;
avcC = malloc( avcC_len );
if( !avcC )
return -1;
avcC[0] = 1;
avcC[1] = sps[1];
avcC[2] = sps[2];
avcC[3] = sps[3];
avcC[4] = 0xff; // nalu size length is four bytes
avcC[5] = 0xe1; // one sps
avcC[6] = sps_size >> 8;
avcC[7] = sps_size;
memcpy( avcC+8, sps, sps_size );
avcC[8+sps_size] = 1; // one pps
avcC[9+sps_size] = pps_size >> 8;
avcC[10+sps_size] = pps_size;
memcpy( avcC+11+sps_size, pps, pps_size );
ret = mk_write_header( p_mkv->w, "x264" X264_VERSION, "V_MPEG4/ISO/AVC",
avcC, avcC_len, p_mkv->frame_duration, 50000,
p_mkv->width, p_mkv->height,
p_mkv->d_width, p_mkv->d_height, p_mkv->display_size_units, p_mkv->stereo_mode );
free( avcC );
if( ret < 0 )
return ret;
// SEI
if( !p_mkv->b_writing_frame )
{
if( mk_start_frame( p_mkv->w ) < 0 )
return -1;
p_mkv->b_writing_frame = 1;
}
if( mk_add_frame_data( p_mkv->w, sei, sei_size ) < 0 )
return -1;
return sei_size + sps_size + pps_size;
}
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
mkv_hnd_t *p_mkv = handle;
if( !p_mkv->b_writing_frame )
{
if( mk_start_frame( p_mkv->w ) < 0 )
return -1;
p_mkv->b_writing_frame = 1;
}
if( mk_add_frame_data( p_mkv->w, p_nalu, i_size ) < 0 )
return -1;
int64_t i_stamp = (int64_t)((p_picture->i_pts * 1e9 * p_mkv->i_timebase_num / p_mkv->i_timebase_den) + 0.5);
p_mkv->b_writing_frame = 0;
if( mk_set_frame_flags( p_mkv->w, i_stamp, p_picture->b_keyframe, p_picture->i_type == X264_TYPE_B ) < 0 )
return -1;
return i_size;
}
static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
mkv_hnd_t *p_mkv = handle;
int ret;
int64_t i_last_delta;
i_last_delta = p_mkv->i_timebase_den ? (int64_t)(((largest_pts - second_largest_pts) * p_mkv->i_timebase_num / p_mkv->i_timebase_den) + 0.5) : 0;
ret = mk_close( p_mkv->w, i_last_delta );
free( p_mkv );
return ret;
}
const cli_output_t mkv_output = { open_file, set_param, write_headers, write_frame, close_file };

513
output/matroska_ebml.c Normal file
View File

@@ -0,0 +1,513 @@
/*****************************************************************************
* matroska_ebml.c: matroska muxer utilities
*****************************************************************************
* Copyright (C) 2005-2025 x264 project
*
* Authors: Mike Matsnev <mike@haali.su>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include "matroska_ebml.h"
#define CLSIZE 1048576
#define CHECK(x)\
do {\
if( (x) < 0 )\
return -1;\
} while( 0 )
struct mk_context
{
struct mk_context *next, **prev, *parent;
mk_writer *owner;
unsigned id;
void *data;
unsigned d_cur, d_max;
};
typedef struct mk_context mk_context;
struct mk_writer
{
FILE *fp;
unsigned duration_ptr;
mk_context *root, *cluster, *frame;
mk_context *freelist;
mk_context *actlist;
int64_t def_duration;
int64_t timescale;
int64_t cluster_tc_scaled;
int64_t frame_tc, max_frame_tc;
int8_t wrote_header, in_frame, keyframe, skippable;
};
static mk_context *mk_create_context( mk_writer *w, mk_context *parent, unsigned id )
{
mk_context *c;
if( w->freelist )
{
c = w->freelist;
w->freelist = w->freelist->next;
}
else
{
c = calloc( 1, sizeof(mk_context) );
if( !c )
return NULL;
}
c->parent = parent;
c->owner = w;
c->id = id;
if( c->owner->actlist )
c->owner->actlist->prev = &c->next;
c->next = c->owner->actlist;
c->prev = &c->owner->actlist;
c->owner->actlist = c;
return c;
}
static int mk_append_context_data( mk_context *c, const void *data, unsigned size )
{
unsigned ns = c->d_cur + size;
if( ns > c->d_max )
{
void *dp;
unsigned dn = c->d_max ? c->d_max << 1 : 16;
while( ns > dn )
dn <<= 1;
dp = realloc( c->data, dn );
if( !dp )
return -1;
c->data = dp;
c->d_max = dn;
}
memcpy( (uint8_t*)c->data + c->d_cur, data, size );
c->d_cur = ns;
return 0;
}
static int mk_write_id( mk_context *c, unsigned id )
{
uint8_t c_id[4] = { id >> 24, id >> 16, id >> 8, id };
if( c_id[0] )
return mk_append_context_data( c, c_id, 4 );
if( c_id[1] )
return mk_append_context_data( c, c_id+1, 3 );
if( c_id[2] )
return mk_append_context_data( c, c_id+2, 2 );
return mk_append_context_data( c, c_id+3, 1 );
}
static int mk_write_size( mk_context *c, unsigned size )
{
uint8_t c_size[5] = { 0x08, size >> 24, size >> 16, size >> 8, size };
if( size < 0x7f )
{
c_size[4] |= 0x80;
return mk_append_context_data( c, c_size+4, 1 );
}
if( size < 0x3fff )
{
c_size[3] |= 0x40;
return mk_append_context_data( c, c_size+3, 2 );
}
if( size < 0x1fffff )
{
c_size[2] |= 0x20;
return mk_append_context_data( c, c_size+2, 3 );
}
if( size < 0x0fffffff )
{
c_size[1] |= 0x10;
return mk_append_context_data( c, c_size+1, 4 );
}
return mk_append_context_data( c, c_size, 5 );
}
static int mk_flush_context_id( mk_context *c )
{
uint8_t ff = 0xff;
if( !c->id )
return 0;
CHECK( mk_write_id( c->parent, c->id ) );
CHECK( mk_append_context_data( c->parent, &ff, 1 ) );
c->id = 0;
return 0;
}
static int mk_flush_context_data( mk_context *c )
{
if( !c->d_cur )
return 0;
if( c->parent )
CHECK( mk_append_context_data( c->parent, c->data, c->d_cur ) );
else if( fwrite( c->data, c->d_cur, 1, c->owner->fp ) != 1 )
return -1;
c->d_cur = 0;
return 0;
}
static int mk_close_context( mk_context *c, unsigned *off )
{
if( c->id )
{
CHECK( mk_write_id( c->parent, c->id ) );
CHECK( mk_write_size( c->parent, c->d_cur ) );
}
if( c->parent && off )
*off += c->parent->d_cur;
CHECK( mk_flush_context_data( c ) );
if( c->next )
c->next->prev = c->prev;
*(c->prev) = c->next;
c->next = c->owner->freelist;
c->owner->freelist = c;
return 0;
}
static void mk_destroy_contexts( mk_writer *w )
{
mk_context *next;
for( mk_context *cur = w->freelist; cur; cur = next )
{
next = cur->next;
free( cur->data );
free( cur );
}
for( mk_context *cur = w->actlist; cur; cur = next )
{
next = cur->next;
free( cur->data );
free( cur );
}
w->freelist = w->actlist = w->root = NULL;
}
static int mk_write_string( mk_context *c, unsigned id, const char *str )
{
size_t len = strlen( str );
CHECK( mk_write_id( c, id ) );
CHECK( mk_write_size( c, len ) );
CHECK( mk_append_context_data( c, str, len ) );
return 0;
}
static int mk_write_bin( mk_context *c, unsigned id, const void *data, unsigned size )
{
CHECK( mk_write_id( c, id ) );
CHECK( mk_write_size( c, size ) );
CHECK( mk_append_context_data( c, data, size ) );
return 0;
}
static int mk_write_uint( mk_context *c, unsigned id, uint64_t ui )
{
uint8_t c_ui[8] = { ui >> 56, ui >> 48, ui >> 40, ui >> 32, ui >> 24, ui >> 16, ui >> 8, ui };
unsigned i = 0;
CHECK( mk_write_id( c, id ) );
while( i < 7 && !c_ui[i] )
++i;
CHECK( mk_write_size( c, 8 - i ) );
CHECK( mk_append_context_data( c, c_ui+i, 8 - i ) );
return 0;
}
static int mk_write_float_raw( mk_context *c, float f )
{
union
{
float f;
uint32_t u;
} u;
uint8_t c_f[4];
u.f = f;
c_f[0] = u.u >> 24;
c_f[1] = u.u >> 16;
c_f[2] = u.u >> 8;
c_f[3] = u.u;
return mk_append_context_data( c, c_f, 4 );
}
static int mk_write_float( mk_context *c, unsigned id, float f )
{
CHECK( mk_write_id( c, id ) );
CHECK( mk_write_size( c, 4 ) );
CHECK( mk_write_float_raw( c, f ) );
return 0;
}
mk_writer *mk_create_writer( const char *filename )
{
mk_writer *w = calloc( 1, sizeof(mk_writer) );
if( !w )
return NULL;
w->root = mk_create_context( w, NULL, 0 );
if( !w->root )
{
free( w );
return NULL;
}
if( !strcmp( filename, "-" ) )
w->fp = stdout;
else
w->fp = x264_fopen( filename, "wb" );
if( !w->fp )
{
mk_destroy_contexts( w );
free( w );
return NULL;
}
w->timescale = 1000000;
return w;
}
int mk_write_header( mk_writer *w, const char *writing_app,
const char *codec_id,
const void *codec_private, unsigned codec_private_size,
int64_t default_frame_duration,
int64_t timescale,
unsigned width, unsigned height,
unsigned d_width, unsigned d_height, int display_size_units, int stereo_mode )
{
mk_context *c, *ti, *v;
if( w->wrote_header )
return -1;
w->timescale = timescale;
w->def_duration = default_frame_duration;
if( !(c = mk_create_context( w, w->root, 0x1a45dfa3 )) ) // EBML
return -1;
CHECK( mk_write_uint( c, 0x4286, 1 ) ); // EBMLVersion
CHECK( mk_write_uint( c, 0x42f7, 1 ) ); // EBMLReadVersion
CHECK( mk_write_uint( c, 0x42f2, 4 ) ); // EBMLMaxIDLength
CHECK( mk_write_uint( c, 0x42f3, 8 ) ); // EBMLMaxSizeLength
CHECK( mk_write_string( c, 0x4282, "matroska") ); // DocType
CHECK( mk_write_uint( c, 0x4287, stereo_mode >= 0 ? 3 : 2 ) ); // DocTypeVersion
CHECK( mk_write_uint( c, 0x4285, 2 ) ); // DocTypeReadVersion
CHECK( mk_close_context( c, 0 ) );
if( !(c = mk_create_context( w, w->root, 0x18538067 )) ) // Segment
return -1;
CHECK( mk_flush_context_id( c ) );
CHECK( mk_close_context( c, 0 ) );
if( !(c = mk_create_context( w, w->root, 0x1549a966 )) ) // SegmentInfo
return -1;
CHECK( mk_write_string( c, 0x4d80, "Haali Matroska Writer b0" ) ); // MuxingApp
CHECK( mk_write_string( c, 0x5741, writing_app ) ); // WritingApp
CHECK( mk_write_uint( c, 0x2ad7b1, w->timescale ) ); // TimecodeScale
CHECK( mk_write_float( c, 0x4489, 0) ); // Duration
w->duration_ptr = c->d_cur - 4;
CHECK( mk_close_context( c, &w->duration_ptr ) );
if( !(c = mk_create_context( w, w->root, 0x1654ae6b )) ) // Tracks
return -1;
if( !(ti = mk_create_context( w, c, 0xae )) ) // TrackEntry
return -1;
CHECK( mk_write_uint( ti, 0xd7, 1 ) ); // TrackNumber
CHECK( mk_write_uint( ti, 0x73c5, 1 ) ); // TrackUID
CHECK( mk_write_uint( ti, 0x83, 1 ) ); // TrackType
CHECK( mk_write_uint( ti, 0x9c, 0 ) ); // FlagLacing
CHECK( mk_write_string( ti, 0x86, codec_id ) ); // CodecID
if( codec_private_size )
CHECK( mk_write_bin( ti, 0x63a2, codec_private, codec_private_size ) ); // CodecPrivate
if( default_frame_duration )
CHECK( mk_write_uint( ti, 0x23e383, default_frame_duration ) ); // DefaultDuration
if( !(v = mk_create_context( w, ti, 0xe0 ) ) ) // Video
return -1;
CHECK( mk_write_uint( v, 0xb0, width ) ); // PixelWidth
CHECK( mk_write_uint( v, 0xba, height ) ); // PixelHeight
CHECK( mk_write_uint( v, 0x54b2, display_size_units ) ); // DisplayUnit
CHECK( mk_write_uint( v, 0x54b0, d_width ) ); // DisplayWidth
CHECK( mk_write_uint( v, 0x54ba, d_height ) ); // DisplayHeight
if( stereo_mode >= 0 )
CHECK( mk_write_uint( v, 0x53b8, stereo_mode ) ); // StereoMode
CHECK( mk_close_context( v, 0 ) );
CHECK( mk_close_context( ti, 0 ) );
CHECK( mk_close_context( c, 0 ) );
CHECK( mk_flush_context_data( w->root ) );
w->wrote_header = 1;
return 0;
}
static int mk_close_cluster( mk_writer *w )
{
if( w->cluster == NULL )
return 0;
CHECK( mk_close_context( w->cluster, 0 ) );
w->cluster = NULL;
CHECK( mk_flush_context_data( w->root ) );
return 0;
}
static int mk_flush_frame( mk_writer *w )
{
int64_t delta;
unsigned fsize;
uint8_t c_delta_flags[3];
if( !w->in_frame )
return 0;
delta = w->frame_tc/w->timescale - w->cluster_tc_scaled;
if( delta > 32767ll || delta < -32768ll )
CHECK( mk_close_cluster( w ) );
if( !w->cluster )
{
w->cluster_tc_scaled = w->frame_tc / w->timescale;
w->cluster = mk_create_context( w, w->root, 0x1f43b675 ); // Cluster
if( !w->cluster )
return -1;
CHECK( mk_write_uint( w->cluster, 0xe7, w->cluster_tc_scaled ) ); // Timecode
delta = 0;
}
fsize = w->frame ? w->frame->d_cur : 0;
CHECK( mk_write_id( w->cluster, 0xa3 ) ); // SimpleBlock
CHECK( mk_write_size( w->cluster, fsize + 4 ) ); // Size
CHECK( mk_write_size( w->cluster, 1 ) ); // TrackNumber
c_delta_flags[0] = (uint8_t)(delta >> 8);
c_delta_flags[1] = (uint8_t)delta;
c_delta_flags[2] = (w->keyframe << 7) | w->skippable;
CHECK( mk_append_context_data( w->cluster, c_delta_flags, 3 ) ); // Timecode, Flags
if( w->frame )
{
CHECK( mk_append_context_data( w->cluster, w->frame->data, w->frame->d_cur ) ); // Data
w->frame->d_cur = 0;
}
w->in_frame = 0;
if( w->cluster->d_cur > CLSIZE )
CHECK( mk_close_cluster( w ) );
return 0;
}
int mk_start_frame( mk_writer *w )
{
if( mk_flush_frame( w ) < 0 )
return -1;
w->in_frame = 1;
w->keyframe = 0;
w->skippable = 0;
return 0;
}
int mk_set_frame_flags( mk_writer *w, int64_t timestamp, int keyframe, int skippable )
{
if( !w->in_frame )
return -1;
w->frame_tc = timestamp;
w->keyframe = keyframe != 0;
w->skippable = skippable != 0;
if( w->max_frame_tc < timestamp )
w->max_frame_tc = timestamp;
return 0;
}
int mk_add_frame_data( mk_writer *w, const void *data, unsigned size )
{
if( !w->in_frame )
return -1;
if( !w->frame )
if( !(w->frame = mk_create_context( w, NULL, 0 )) )
return -1;
return mk_append_context_data( w->frame, data, size );
}
int mk_close( mk_writer *w, int64_t last_delta )
{
int ret = 0;
if( mk_flush_frame( w ) < 0 || mk_close_cluster( w ) < 0 )
ret = -1;
if( w->wrote_header && x264_is_regular_file( w->fp ) )
{
int64_t last_frametime = w->def_duration ? w->def_duration : last_delta;
int64_t total_duration = w->max_frame_tc + last_frametime;
if( fseek( w->fp, w->duration_ptr, SEEK_SET ) ||
mk_write_float_raw( w->root, (float)((double)total_duration / w->timescale) ) < 0 ||
mk_flush_context_data( w->root ) < 0 )
ret = -1;
}
mk_destroy_contexts( w );
fclose( w->fp );
free( w );
return ret;
}

52
output/matroska_ebml.h Normal file
View File

@@ -0,0 +1,52 @@
/*****************************************************************************
* matroska_ebml.h: matroska muxer utilities
*****************************************************************************
* Copyright (C) 2005-2025 x264 project
*
* Authors: Mike Matsnev <mike@haali.su>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_MATROSKA_EBML_H
#define X264_MATROSKA_EBML_H
/* Matroska display size units from the spec */
#define DS_PIXELS 0
#define DS_CM 1
#define DS_INCHES 2
#define DS_ASPECT_RATIO 3
typedef struct mk_writer mk_writer;
mk_writer *mk_create_writer( const char *filename );
int mk_write_header( mk_writer *w, const char *writing_app,
const char *codec_id,
const void *codec_private, unsigned codec_private_size,
int64_t default_frame_duration,
int64_t timescale,
unsigned width, unsigned height,
unsigned d_width, unsigned d_height, int display_size_units, int stereo_mode );
int mk_start_frame( mk_writer *w );
int mk_add_frame_data( mk_writer *w, const void *data, unsigned size );
int mk_set_frame_flags( mk_writer *w, int64_t timestamp, int keyframe, int skippable );
int mk_close( mk_writer *w, int64_t last_delta );
#endif

348
output/mp4.c Normal file
View File

@@ -0,0 +1,348 @@
/*****************************************************************************
* mp4.c: mp4 muxer
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include <gpac/isomedia.h>
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;
int i_numframe;
int i_delay_frames;
int b_dts_compress;
int i_dts_compress_multiplier;
int i_data_size;
} 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 )
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++ )
{
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 );
}
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 );
}
static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
mp4_hnd_t *p_mp4 = handle;
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->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 );
}
/* 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 )
{
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 ) );
#if GPAC_VERSION_MAJOR > 8
gf_isom_append_edit( p_mp4->p_file, p_mp4->i_track, tkhd_duration, sample->CTS_Offset, GF_ISOM_EDIT_NORMAL );
#else
gf_isom_append_edit_segment( p_mp4->p_file, p_mp4->i_track, tkhd_duration, sample->CTS_Offset, GF_ISOM_EDIT_NORMAL );
#endif
}
gf_isom_sample_del( &sample );
recompute_bitrate_mp4( p_mp4->p_file, p_mp4->i_track );
}
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 );
}
free( p_mp4 );
return 0;
}
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
*p_handle = NULL;
FILE *fh = x264_fopen( psz_filename, "w" );
if( !fh )
return -1;
int b_regular = x264_is_regular_file( fh );
fclose( fh );
FAIL_IF_ERR( !b_regular, "mp4", "MP4 output is incompatible with non-regular file `%s'\n", psz_filename );
mp4_hnd_t *p_mp4 = calloc( 1, sizeof(mp4_hnd_t) );
if( !p_mp4 )
return -1;
p_mp4->p_file = gf_isom_open( psz_filename, GF_ISOM_OPEN_WRITE, NULL );
p_mp4->b_dts_compress = opt->use_dts_compress;
if( !(p_mp4->p_sample = gf_isom_sample_new()) )
{
close_file( p_mp4, 0, 0 );
return -1;
}
gf_isom_set_brand_info( p_mp4->p_file, GF_ISOM_BRAND_AVC1, 0 );
*p_handle = p_mp4;
return 0;
}
static int set_param( hnd_t handle, x264_param_t *p_param )
{
mp4_hnd_t *p_mp4 = handle;
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 = (uint64_t)p_param->i_timebase_den * p_mp4->i_dts_compress_multiplier;
p_mp4->i_time_inc = (uint64_t)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 );
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 );
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;
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, 0 );
gf_isom_set_track_layout_info( p_mp4->p_file, p_mp4->i_track, dw, dh, 0, 0, 0 );
}
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 )
{
p_mp4->i_data_size = 0;
return -1;
}
return 0;
}
static int check_buffer( mp4_hnd_t *p_mp4, int needed_size )
{
if( needed_size > p_mp4->i_data_size )
{
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;
}
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;
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;
return sei_size + sps_size + pps_size;
}
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;
if( !p_mp4->i_numframe )
p_mp4->i_delay_time = p_picture->i_dts * -1;
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;
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);
cts = p_picture->i_pts * p_mp4->i_time_inc;
}
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;
}
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 );
p_mp4->p_sample->dataLength = 0;
p_mp4->i_numframe++;
return i_size;
}
const cli_output_t mp4_output = { open_file, set_param, write_headers, write_frame, close_file };

429
output/mp4_lsmash.c Normal file
View File

@@ -0,0 +1,429 @@
/*****************************************************************************
* mp4_lsmash.c: mp4 muxer using L-SMASH
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
* Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
* Takashi Hirata <silverfilain@gmail.com>
* golgol7777 <golgol7777@gmail.com>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
#include <lsmash.h>
#define H264_NALU_LENGTH_SIZE 4
/*******************/
#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_FAIL_IF_ERR( cond, ... ) FAIL_IF_ERR( cond, "mp4", __VA_ARGS__ )
/* For close_file() */
#define MP4_LOG_IF_ERR( cond, ... )\
do\
{\
if( cond )\
{\
MP4_LOG_ERROR( __VA_ARGS__ );\
}\
} while( 0 )
/* For open_file() */
#define MP4_FAIL_IF_ERR_EX( cond, ... )\
do\
{\
if( cond )\
{\
remove_mp4_hnd( p_mp4 );\
MP4_LOG_ERROR( __VA_ARGS__ );\
return -1;\
}\
} while( 0 )
/*******************/
typedef struct
{
lsmash_root_t *p_root;
lsmash_video_summary_t *summary;
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 b_use_recovery;
int b_fragments;
lsmash_file_parameters_t file_param;
} mp4_hnd_t;
/*******************/
static void remove_mp4_hnd( hnd_t handle )
{
mp4_hnd_t *p_mp4 = handle;
if( !p_mp4 )
return;
lsmash_cleanup_summary( (lsmash_summary_t *)p_mp4->summary );
lsmash_close_file( &p_mp4->file_param );
lsmash_destroy_root( p_mp4->p_root );
free( p_mp4->p_sei_buffer );
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;
if( !p_mp4 )
return 0;
if( p_mp4->p_root )
{
double actual_duration = 0;
if( p_mp4->i_track )
{
/* 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" );
/*
* 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.
*/
lsmash_edit_t edit;
edit.duration = actual_duration;
edit.start_time = p_mp4->i_first_cts;
edit.rate = ISOM_EDIT_MODE_NORMAL;
if( !p_mp4->b_fragments )
{
MP4_LOG_IF_ERR( lsmash_create_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, edit ),
"failed to set timeline map for video.\n" );
}
else if( !p_mp4->b_stdout )
MP4_LOG_IF_ERR( lsmash_modify_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, 1, edit ),
"failed to update timeline map for video.\n" );
}
MP4_LOG_IF_ERR( lsmash_finish_movie( p_mp4->p_root, NULL ), "failed to finish movie.\n" );
}
remove_mp4_hnd( p_mp4 ); /* including lsmash_destroy_root( p_mp4->p_root ); */
return 0;
}
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
*p_handle = NULL;
int b_regular = strcmp( psz_filename, "-" );
b_regular = b_regular && x264_is_regular_file_path( psz_filename );
if( b_regular )
{
FILE *fh = x264_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 );
}
mp4_hnd_t *p_mp4 = calloc( 1, sizeof(mp4_hnd_t) );
MP4_FAIL_IF_ERR( !p_mp4, "failed to allocate memory for muxer information.\n" );
p_mp4->b_dts_compress = opt->use_dts_compress;
p_mp4->b_use_recovery = 0; // we don't really support recovery
p_mp4->b_fragments = !b_regular;
p_mp4->b_stdout = !strcmp( psz_filename, "-" );
p_mp4->p_root = lsmash_create_root();
MP4_FAIL_IF_ERR_EX( !p_mp4->p_root, "failed to create root.\n" );
MP4_FAIL_IF_ERR_EX( lsmash_open_file( psz_filename, 0, &p_mp4->file_param ) < 0, "failed to open an output file.\n" );
if( p_mp4->b_fragments )
p_mp4->file_param.mode |= LSMASH_FILE_MODE_FRAGMENTED;
p_mp4->summary = (lsmash_video_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_VIDEO );
MP4_FAIL_IF_ERR_EX( !p_mp4->summary,
"failed to allocate memory for summary information of video.\n" );
p_mp4->summary->sample_type = ISOM_CODEC_TYPE_AVC1_VIDEO;
*p_handle = p_mp4;
return 0;
}
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;
i_media_timescale = (uint64_t)p_param->i_timebase_den * p_mp4->i_dts_compress_multiplier;
p_mp4->i_time_inc = (uint64_t)p_param->i_timebase_num * p_mp4->i_dts_compress_multiplier;
MP4_FAIL_IF_ERR( i_media_timescale > UINT32_MAX, "MP4 media timescale %"PRIu64" exceeds maximum\n", i_media_timescale );
/* Select brands. */
lsmash_brand_type brands[6] = { 0 };
uint32_t brand_count = 0;
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 */
}
/* Set file */
lsmash_file_parameters_t *file_param = &p_mp4->file_param;
file_param->major_brand = brands[0];
file_param->brands = brands;
file_param->brand_count = brand_count;
file_param->minor_version = 0;
MP4_FAIL_IF_ERR( !lsmash_set_file( p_mp4->p_root, file_param ), "failed to add an output file into a ROOT.\n" );
/* Set movie parameters. */
lsmash_movie_parameters_t movie_param;
lsmash_initialize_movie_parameters( &movie_param );
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" );
p_mp4->summary->width = p_param->i_width;
p_mp4->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 )
{
double sar = (double)p_param->vui.i_sar_width / p_param->vui.i_sar_height;
if( sar > 1.0 )
i_display_width *= sar;
else
i_display_height /= sar;
p_mp4->summary->par_h = p_param->vui.i_sar_width;
p_mp4->summary->par_v = p_param->vui.i_sar_height;
}
p_mp4->summary->color.primaries_index = p_param->vui.i_colorprim;
p_mp4->summary->color.transfer_index = p_param->vui.i_transfer;
p_mp4->summary->color.matrix_index = p_param->vui.i_colmatrix >= 0 ? p_param->vui.i_colmatrix : ISOM_MATRIX_INDEX_UNSPECIFIED;
p_mp4->summary->color.full_range = p_param->vui.b_fullrange >= 0 ? p_param->vui.b_fullrange : 0;
/* 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;
track_param.mode = track_mode;
track_param.display_width = i_display_width;
track_param.display_height = i_display_height;
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_use_recovery )
{
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" );
return 0;
}
static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
mp4_hnd_t *p_mp4 = handle;
uint32_t sps_size = p_nal[0].i_payload - H264_NALU_LENGTH_SIZE;
uint32_t pps_size = p_nal[1].i_payload - H264_NALU_LENGTH_SIZE;
uint32_t sei_size = p_nal[2].i_payload;
uint8_t *sps = p_nal[0].p_payload + H264_NALU_LENGTH_SIZE;
uint8_t *pps = p_nal[1].p_payload + H264_NALU_LENGTH_SIZE;
uint8_t *sei = p_nal[2].p_payload;
lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264,
LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED );
lsmash_h264_specific_parameters_t *param = (lsmash_h264_specific_parameters_t *)cs->data.structured;
param->lengthSizeMinusOne = H264_NALU_LENGTH_SIZE - 1;
/* SPS
* The remaining parameters are automatically set by SPS. */
if( lsmash_append_h264_parameter_set( param, H264_PARAMETER_SET_TYPE_SPS, sps, sps_size ) )
{
MP4_LOG_ERROR( "failed to append SPS.\n" );
return -1;
}
/* PPS */
if( lsmash_append_h264_parameter_set( param, H264_PARAMETER_SET_TYPE_PPS, pps, pps_size ) )
{
MP4_LOG_ERROR( "failed to append PPS.\n" );
return -1;
}
if( lsmash_add_codec_specific_data( (lsmash_summary_t *)p_mp4->summary, cs ) )
{
MP4_LOG_ERROR( "failed to add H.264 specific info.\n" );
return -1;
}
lsmash_destroy_codec_specific_data( cs );
/* Additional extensions */
/* Bitrate info */
cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE,
LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED );
if( cs )
lsmash_add_codec_specific_data( (lsmash_summary_t *)p_mp4->summary, cs );
lsmash_destroy_codec_specific_data( cs );
p_mp4->i_sample_entry = lsmash_add_sample_entry( p_mp4->p_root, p_mp4->i_track, p_mp4->summary );
MP4_FAIL_IF_ERR( !p_mp4->i_sample_entry,
"failed to add sample entry for video.\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;
}
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;
uint64_t dts, cts;
if( !p_mp4->i_numframe )
{
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 )
{
lsmash_edit_t edit;
edit.duration = ISOM_EDIT_DURATION_UNKNOWN32; /* QuickTime doesn't support 64bit duration. */
edit.start_time = p_mp4->i_first_cts;
edit.rate = ISOM_EDIT_MODE_NORMAL;
MP4_LOG_IF_ERR( lsmash_create_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, edit ),
"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_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);
cts = p_picture->i_pts * p_mp4->i_time_inc;
}
else
{
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.ra_flags = p_picture->b_keyframe ? ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC : ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE;
if( p_mp4->b_fragments && p_mp4->i_numframe && p_sample->prop.ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE )
{
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" );
}
/* 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->i_prev_dts = dts;
p_mp4->i_numframe++;
return i_size;
}
const cli_output_t mp4_output = { open_file, set_param, write_headers, write_frame, close_file };

51
output/output.h Normal file
View File

@@ -0,0 +1,51 @@
/*****************************************************************************
* output.h: x264 file output modules
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_OUTPUT_H
#define X264_OUTPUT_H
#include "x264cli.h"
typedef struct
{
int use_dts_compress;
} cli_output_opt_t;
typedef struct
{
int (*open_file)( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt );
int (*set_param)( hnd_t handle, x264_param_t *p_param );
int (*write_headers)( hnd_t handle, x264_nal_t *p_nal );
int (*write_frame)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture );
int (*close_file)( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts );
} cli_output_t;
extern const cli_output_t raw_output;
extern const cli_output_t mkv_output;
extern const cli_output_t mp4_output;
extern const cli_output_t flv_output;
#endif

69
output/raw.c Normal file
View File

@@ -0,0 +1,69 @@
/*****************************************************************************
* raw.c: raw muxer
*****************************************************************************
* Copyright (C) 2003-2025 x264 project
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Loren Merritt <lorenm@u.washington.edu>
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "output.h"
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
if( !strcmp( psz_filename, "-" ) )
*p_handle = stdout;
else if( !(*p_handle = x264_fopen( psz_filename, "w+b" )) )
return -1;
return 0;
}
static int set_param( hnd_t handle, x264_param_t *p_param )
{
return 0;
}
static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
int size = p_nal[0].i_payload + p_nal[1].i_payload + p_nal[2].i_payload;
if( fwrite( p_nal[0].p_payload, size, 1, (FILE*)handle ) )
return size;
return -1;
}
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) )
return i_size;
return -1;
}
static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
if( !handle || handle == stdout )
return 0;
return fclose( (FILE*)handle );
}
const cli_output_t raw_output = { open_file, set_param, write_headers, write_frame, close_file };