]> git.sesse.net Git - x264/blobdiff - output/flv.c
configure: Define feature test macros for --std=gnu99
[x264] / output / flv.c
index 30b609b68de7ac352ef8523242a0f02164f6e0ff..54647dd0edc1c13339a9c17fc143d23a776b35ad 100644 (file)
@@ -1,7 +1,9 @@
 /*****************************************************************************
- * flv.c:
+ * flv.c: flv muxer
  *****************************************************************************
- * Copyright (C) 2009 Kieran Kunhya
+ * Copyright (C) 2009-2016 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
  * 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 "muxers.h"
+#include "output.h"
 #include "flv_bytestream.h"
+
 #define CHECK(x)\
 do {\
     if( (x) < 0 )\
@@ -30,53 +36,68 @@ typedef struct
 {
     flv_buffer *c;
 
-    uint8_t b_sps;
-    uint8_t b_pps;
     uint8_t *sei;
     int sei_len;
 
     int64_t i_fps_num;
     int64_t i_fps_den;
-    int64_t i_init_delay;
     int64_t i_framenum;
-    int64_t i_mspf;
 
+    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 )
 {
-    put_tag( c, "FLV" ); // Signature
-    put_byte( c, 1 );    // Version
-    put_byte( c, 1 );    // Video Only
-    put_be32( c, 9 );    // DataOffset
-    put_be32( c, 0 );    // PreviousTagSize0
+    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 )
+static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
 {
-    flv_hnd_t *p_flv = malloc( sizeof(*p_flv) );
-    *p_handle = NULL;
-    if( !p_flv )
-        return -1;
-    memset( p_flv, 0, sizeof(*p_flv) );
-
-    p_flv->c = flv_create_writer( psz_filename );
-    if( !p_flv->c )
-        return -1;
+    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;
+            }
 
-    CHECK( write_header( p_flv->c ) );
-    *p_handle = p_flv;
+            fclose( c->fp );
+            free( c->data );
+            free( c );
+        }
+        free( p_flv );
+    }
 
-    return 0;
+    *p_handle = NULL;
+    return -1;
 }
 
 static int set_param( hnd_t handle, x264_param_t *p_param )
@@ -84,211 +105,249 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
     flv_hnd_t *p_flv = handle;
     flv_buffer *c = p_flv->c;
 
-    put_byte( c, FLV_TAG_TYPE_META ); // Tag Type "script data"
+    flv_put_byte( c, FLV_TAG_TYPE_META ); // Tag Type "script data"
 
     int start = c->d_cur;
-    put_be24( c, 0 ); // data length
-    put_be24( c, 0 ); // timestamp
-    put_be32( c, 0 ); // reserved
+    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" );
 
-    put_byte( c, AMF_DATA_TYPE_STRING );
-    put_amf_string( c, "onMetaData" );
+    flv_put_byte( c, AMF_DATA_TYPE_MIXEDARRAY );
+    flv_put_be32( c, 7 );
 
-    put_byte( c, AMF_DATA_TYPE_MIXEDARRAY );
-    put_be32( c, 7 );
+    flv_put_amf_string( c, "width" );
+    flv_put_amf_double( c, p_param->i_width );
 
-    put_amf_string( c, "width" );
-    put_amf_double( c, p_param->i_width );
+    flv_put_amf_string( c, "height" );
+    flv_put_amf_double( c, p_param->i_height );
 
-    put_amf_string( c, "height" );
-    put_amf_double( c, p_param->i_height );
+    flv_put_amf_string( c, "framerate" );
 
-    put_amf_string( c, "framerate" );
-    put_amf_double( c, p_param->i_fps_num / p_param->i_fps_den );
+    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
+    }
 
-    put_amf_string( c, "videocodecid" );
-    put_amf_double( c, FLV_CODECID_H264 );
+    flv_put_amf_string( c, "videocodecid" );
+    flv_put_amf_double( c, FLV_CODECID_H264 );
 
-    put_amf_string( c, "duration" );
-    p_flv->i_duration_pos = c->d_cur + c->d_total + 1; // + 1 because of the following AMF_DATA_TYPE_NUMBER byte
-    put_amf_double( c, 0 ); // written at end of encoding
+    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
 
-    put_amf_string( c, "filesize" );
+    flv_put_amf_string( c, "filesize" );
     p_flv->i_filesize_pos = c->d_cur + c->d_total + 1;
-    put_amf_double( c, 0 ); // written at end of encoding
+    flv_put_amf_double( c, 0 ); // written at end of encoding
 
-    put_amf_string( c, "videodatarate" );
+    flv_put_amf_string( c, "videodatarate" );
     p_flv->i_bitrate_pos = c->d_cur + c->d_total + 1;
-    put_amf_double( c, 0 ); // written at end of encoding
+    flv_put_amf_double( c, 0 ); // written at end of encoding
 
-    put_amf_string( c, "" );
-    put_byte( c, AMF_END_OF_OBJECT );
+    flv_put_amf_string( c, "" );
+    flv_put_byte( c, AMF_END_OF_OBJECT );
 
     unsigned length = c->d_cur - start;
-    rewrite_amf_be24( c, length - 10, start );
+    flv_rewrite_amf_be24( c, length - 10, start );
 
-    put_be32( c, length + 1 ); // tag length
+    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->i_init_delay = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
-    p_flv->i_mspf = 1000 * p_flv->i_fps_den / p_flv->i_fps_num;
-
-    fprintf( stderr, "flv [info]: initial delay %i frames\n",
-             (int)p_flv->i_init_delay );
+    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_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
+static int write_headers( hnd_t handle, x264_nal_t *p_nal )
 {
     flv_hnd_t *p_flv = handle;
     flv_buffer *c = p_flv->c;
-    uint64_t dts = (uint64_t)p_flv->i_framenum * p_flv->i_mspf;
-    uint64_t pts = (uint64_t)p_picture->i_pts * p_flv->i_mspf / p_flv->i_fps_den;
-    uint64_t timestamp = dts + p_flv->i_init_delay * p_flv->i_mspf;
-    uint64_t offset = p_flv->i_init_delay * p_flv->i_mspf + pts - dts;
-    uint8_t type = p_nalu[4] & 0x1f;
-
-    switch( type )
-    {
-        // sps
-        case 0x07:
-            if( !p_flv->b_sps )
-            {
-                uint8_t *sps = p_nalu + 4;
-
-                put_byte( c, FLV_TAG_TYPE_VIDEO );
-                put_be24( c, 0 ); // rewrite later, pps size unknown
-                put_be24( c, 0 ); // timestamp
-                put_byte( c, 0 ); // timestamp extended
-                put_be24( c, 0 ); // StreamID - Always 0
-                p_flv->start = c->d_cur; // needed for overwriting length
-
-                put_byte( c, 7 | FLV_FRAME_KEY ); // Frametype and CodecID
-                put_byte( c, 0 ); // AVC sequence header
-                put_be24( c, 0 ); // composition time
-
-                put_byte( c, 1 );      // version
-                put_byte( c, sps[1] ); // profile
-                put_byte( c, sps[2] ); // profile
-                put_byte( c, sps[3] ); // level
-                put_byte( c, 0xff );   // 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
-                put_byte( c, 0xe1 );   // 3 bits reserved (111) + 5 bits number of sps (00001)
-
-                put_be16( c, i_size - 4 );
-                flv_append_data( c, sps, i_size - 4 );
-
-                p_flv->b_sps = 1;
-            }
-            break;
 
-        // pps
-        case 0x08:
-            if( !p_flv->b_pps )
-            {
-                put_byte( c, 1 ); // number of pps
-                put_be16( c, i_size - 4 );
-                flv_append_data( c, p_nalu + 4, i_size - 4 );
+    int sps_size = p_nal[0].i_payload;
+    int pps_size = p_nal[1].i_payload;
+    int sei_size = p_nal[2].i_payload;
 
-                // rewrite data length info
-                unsigned length = c->d_cur - p_flv->start;
-                rewrite_amf_be24( c, length, p_flv->start - 10 );
-                put_be32( c, length + 11 ); // Last tag size
+    // 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->b_pps = 1;
-            }
-            break;
+    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, 7 | FLV_FRAME_KEY ); // 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 ) );
 
-        // slice
-        case 0x1:
-        case 0x5:
-            if( !p_flv->b_write_length )
-            {
-                // A new frame - write packet header
-                put_byte( c, FLV_TAG_TYPE_VIDEO );
-                put_be24( c, 0 ); // calculated later
-                put_be24( c, timestamp );
-                put_byte( c, timestamp >> 24 );
-                put_be24( c, 0 );
-
-                p_flv->start = c->d_cur;
-                put_byte( c, p_picture->i_type == X264_TYPE_IDR ? FLV_FRAME_KEY : FLV_FRAME_INTER );
-                put_byte( c, 1 ); // AVC NALU
-                put_be24( c, offset );
-
-                p_flv->b_write_length = 1;
-            }
-            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 );
-            break;
-        // sei
-        case 0x6:
-            /* 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( i_size );
-            if( !p_flv->sei )
-                return -1;
-            p_flv->sei_len = i_size;
-
-            memcpy( p_flv->sei, p_nalu, i_size );
-            break;
-    }
-    return i_size;
+    return sei_size + sps_size + pps_size;
 }
 
-static int set_eop( hnd_t handle, x264_picture_t *p_picture )
+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;
 
-    if( p_flv->b_write_length )
+#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 )
     {
-        unsigned length = c->d_cur - p_flv->start;
-        rewrite_amf_be24( c, length, p_flv->start - 10 );
-        put_be32( c, 11 + length ); // Last tag size
-        CHECK( flv_flush_data( c ) );
-        p_flv->b_write_length = 0;
+        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_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 0;
+    return i_size;
 }
 
-static void rewrite_amf_double( FILE *fp, uint64_t position, double value )
+static int rewrite_amf_double( FILE *fp, uint64_t position, double value )
 {
-    uint64_t x = endian_fix64( dbl2int( value ) );
-    fseek( fp, position, SEEK_SET );
-    fwrite( &x, 8, 1, fp );
+    uint64_t x = endian_fix64( flv_dbl2int( value ) );
+    return !fseek( fp, position, SEEK_SET ) && fwrite( &x, 8, 1, fp ) == 1 ? 0 : -1;
 }
 
-static int close_file( hnd_t handle )
+#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 ) );
 
-    if( x264_is_regular_file( c->fp ) )
+    double total_duration = (2 * largest_pts - second_largest_pts) * p_flv->d_timebase;
+
+    if( x264_is_regular_file( c->fp ) && total_duration > 0 )
     {
-        double duration = p_flv->i_fps_den * p_flv->i_framenum / p_flv->i_fps_num;
+        double framerate;
         uint64_t filesize = ftell( c->fp );
-        rewrite_amf_double( c->fp, p_flv->i_duration_pos, duration );
-        rewrite_amf_double( c->fp, p_flv->i_filesize_pos, filesize );
-        rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8 / ( duration * 1000 ) );
+
+        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 / ( total_duration * 1000 ) ) );
     }
+    ret = 0;
 
+error:
     fclose( c->fp );
-    free( p_flv );
+    free( c->data );
     free( c );
+    free( p_flv );
 
-    return 0;
+    return ret;
 }
 
-cli_output_t flv_output = { open_file, set_param, write_nalu, set_eop, close_file };
+const cli_output_t flv_output = { open_file, set_param, write_headers, write_frame, close_file };