diff -uNrp src/Makefile src.new/Makefile --- src/Makefile 2014-11-13 21:05:08 +0300 +++ src.new/Makefile 2014-11-13 21:05:56 +0300 @@ -22,7 +22,7 @@ SRCS = common/mc.c common/predict.c comm encoder/set.c encoder/macroblock.c encoder/cabac.c \ encoder/cavlc.c encoder/encoder.c encoder/lookahead.c -SRCCLI = x264.c input/input.c input/timecode.c input/raw.c input/y4m.c \ +SRCCLI = x264.c input/input.c input/timecode.c input/raw.c input/scc.c input/y4m.c \ output/raw.c output/matroska.c output/matroska_ebml.c \ output/flv.c output/flv_bytestream.c filters/filters.c \ filters/video/video.c filters/video/source.c filters/video/internal.c \ diff -uNrp src/input/input.c src.new/input/input.c --- src/input/input.c 2014-11-13 21:00:14 +0300 +++ src.new/input/input.c 2014-11-13 21:06:45 +0300 @@ -124,3 +124,23 @@ const x264_cli_csp_t *x264_cli_get_csp( return NULL; return x264_cli_csps + (csp&X264_CSP_MASK); } + +void increase_tc( cli_opt_t *opt, cli_timecode_t *timecode ) +{ + timecode->frame = (timecode->frame + 1) % opt->timecode_ctx->mod; + if( !timecode->frame ) + { + timecode->sec = (timecode->sec + 1) % 60; + if( !timecode->sec ) + { + timecode->min = (timecode->min + 1) % 60; + if( !timecode->min ) + timecode->hour = (timecode->hour + 1) % 24; + } + } + + /* 29.97 and 59.94 Drop Frame - SMPTE 12M-2008 */ + if( opt->drop_frame && !( timecode->min % 10 ) && timecode->sec == 0 && timecode->frame == 0 ) + timecode->frame = 2; + +} diff -uNrp src/input/input.h src.new/input/input.h --- src/input/input.h 2014-11-13 21:05:07 +0300 +++ src.new/input/input.h 2014-11-13 21:06:19 +0300 @@ -131,4 +131,9 @@ uint64_t x264_cli_pic_plane_size( int cs uint64_t x264_cli_pic_size( int csp, int width, int height ); const x264_cli_csp_t *x264_cli_get_csp( int csp ); +void increase_tc( cli_opt_t *opt, cli_timecode_t *timecode ); + +int open_scc( cli_opt_t *opt, scc_opt_t *scc ); +int write_cc( cli_opt_t *opt, x264_sei_t *sei, int odd ); + #endif diff -uNrp src/input/scc.c src.new/input/scc.c --- src/input/scc.c 1970-01-01 03:00:00 +0300 +++ src.new/input/scc.c 2014-11-13 21:07:17 +0300 @@ -0,0 +1,239 @@ +/***************************************************************************** + * scc.c: scc captions input functions + ***************************************************************************** + * Copyright (C) 2010-2011 Open Broadcast Encoder + * + * Authors: Kieran Kunhya + * + * 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 "input.h" + +#define SCC_HEADER "Scenarist_SCC V1.0" + +static void write_bytes( bs_t *s, uint8_t *bytes, int length ) +{ + bs_flush( s ); + uint8_t *p_start = s->p_start; + + memcpy( s->p, bytes, length ); + s->p += length; + + bs_init( s, s->p, s->p_end - s->p ); + s->p_start = p_start; +} + +static void write_invalid( bs_t *s ) +{ + bs_write1( s, 0 ); // cc_valid + bs_write( s, 2, 2 ); // cc_type + bs_write( s, 8, 0 ); // c_data_1 + bs_write( s, 8, 0 ); // c_data_2 +} + +static void write_null( bs_t *s ) +{ + bs_write1( s, 1 ); // cc_valid + bs_write( s, 2, 1 ); // cc_type + bs_write( s, 8, 128 ); // c_data_1 + bs_write( s, 8, 128 ); // c_data_2 +} + +static void write_payload( bs_t *s, cli_opt_t *opt ) +{ + char cc_data1[3]; + char cc_data2[3]; + char spacer[1]; + char tc_separator[1]; + int ret; + uint8_t out1, out2; + + bs_write1( s, 1 ); // cc_valid + bs_write( s, 2, 0 ); // cc_type + + if( !memcmp( &opt->timecode, &opt->scc_opts[0].timecode, sizeof(cli_timecode_t) ) ) + { + char *end; + out1 = out2 = 128; + ret = fscanf( opt->scc_opts[0].scc_file, "%2s", cc_data1 ); + ret = fscanf( opt->scc_opts[0].scc_file, "%2s", cc_data2 ); + + if( ret > 0 ) + { + cc_data1[2] = cc_data2[2] = 0; + out1 = strtol( cc_data1, &end, 16 ); + if( end == cc_data1 || *end != '\0' ) + out1 = 128; + out2 = strtol( cc_data2, &end, 16 ); + if( end == cc_data2 || *end != '\0' ) + out2 = 128; + } + + bs_write( s, 8, out1 ); // c_data_1 + bs_write( s, 8, out2 ); // c_data_2 + + ret = fread( spacer, 1, 1, opt->scc_opts[0].scc_file ); + + if( ret < 1 ) + opt->scc_opts[0].timecode.frame = -1; /* will never match */ + else if( spacer[0] == ' ' ) + increase_tc( opt, &opt->scc_opts[0].timecode ); + else if( spacer[0] == '\r' || spacer[0] == '\n' ) + { + ret = fscanf( opt->scc_opts[0].scc_file, "%2u:%2u:%2u%[;:]%2u\t", &opt->scc_opts[0].timecode.hour, + &opt->scc_opts[0].timecode.min, &opt->scc_opts[0].timecode.sec, tc_separator, &opt->scc_opts[0].timecode.frame ); + } + } + else + { + bs_write( s, 8, 128 ); // c_data_1 + bs_write( s, 8, 128 ); // c_data_2 + } +} + +int open_scc( cli_opt_t *opt, scc_opt_t *scc ) +{ + char temp[19]; + char tc_separator[1]; + int ret; + + ret = fread( temp, 18, 1, scc->scc_file ); + if( ret < 1 ) + goto fail; + + temp[18] = 0; + + if( strcmp( temp, SCC_HEADER ) ) + { + x264_cli_log( "caption", X264_LOG_ERROR, "could not find scc header\n" ); + return -1; + } + + ret = fscanf( scc->scc_file, "%2u:%2u:%2u%[;:]%2u\t", &scc->timecode.hour, &scc->timecode.min, &scc->timecode.sec, tc_separator, &scc->timecode.frame ); + if( ret < 0 ) + { + x264_cli_log( "caption", X264_LOG_ERROR, "could not find timecode in scc file\n" ); + return -1; + } + + if( scc->timecode.hour != opt->timecode.hour ) + x264_cli_log( "caption", X264_LOG_WARNING, "scc timecode hour does not match stream timecode hour\n" ); + + opt->drop_frame = tc_separator[0] == ';'; + + return 0; + +fail: + fprintf( stderr, "[caption] could not read scc file\n" ); + return -1; +} + +int write_cc( cli_opt_t *opt, x264_sei_t *sei, int odd ) +{ + bs_t q, r; + uint8_t temp[1000]; + uint8_t temp2[1000]; + int country_code = 0xb5; + int provider_code = 0x31; + char *user_identifier = "GA94"; + int data_type_code = 0x03; + + sei->num_payloads = 1; + sei->sei_free = free; + + sei->payloads = calloc( 1, sizeof(*sei->payloads ) ); + if( !sei->payloads ) + { + fprintf( stderr, "Malloc failed\n" ); + return -1; + } + + bs_init( &r, temp, 1000 ); + + bs_write( &r, 8, country_code ); // itu_t_t35_country_code + bs_write( &r, 16, provider_code ); // itu_t_t35_provider_code + + if( !opt->echostar_captions ) + { + for( int i = 0; i < 4; i++ ) + bs_write( &r, 8, user_identifier[i] ); // user_identifier + } + + bs_write( &r, 8, data_type_code ); // user_data_type_code + + bs_init( &q, temp2, 1000 ); + + // user_data_type_structure (echostar) + // cc_data + bs_write1( &q, 1 ); // reserved + bs_write1( &q, 1 ); // process_cc_data_flag + bs_write1( &q, 0 ); // zero_bit / additional_data_flag + bs_write( &q, 5, opt->timecode_ctx->cc_count ); // cc_count + bs_write( &q, 8, 0xff ); // reserved + + for( int i = 0; i < opt->timecode_ctx->cc_count; i++ ) + { + bs_write1( &q, 1 ); // one_bit + bs_write( &q, 4, 0xf ); // reserved + + if( i > 1 || (opt->timecode_ctx->frame_doubling && i == 1) ) /* nothing to write so maintain a constant bitrate */ + write_invalid( &q ); + /* Each field is written on a separate frame in frame doubling mode */ + else if( opt->timecode_ctx->frame_doubling ) + { + if( odd != opt->scc_opts[0].sff ) + write_null( &q ); + else + write_payload( &q, opt ); + } + /* For some reason certain applications write second field first */ + else if( (i == 1) != opt->scc_opts[0].sff ) + write_null( &q ); + else + write_payload( &q, opt ); + } + + bs_write( &q, 8, 0xff ); // marker_bits + bs_flush( &q ); + + if( opt->echostar_captions ) + { + // ATSC1_data + bs_write( &r, 8, bs_pos( &q ) >> 3 ); // user_data_code_length + } + + write_bytes( &r, temp2, bs_pos( &q ) >> 3 ); + + bs_flush( &r ); + + sei->payloads[0].payload_type = 4; // registered itu_t_35 + sei->payloads[0].payload_size = bs_pos( &r ) >> 3; + + sei->payloads[0].payload = malloc( sei->payloads[0].payload_size ); + if( !sei->payloads[0].payload ) + { + free( sei->payloads ); + fprintf( stderr, "Malloc failed\n" ); + return -1; + } + + memcpy( sei->payloads[0].payload, temp, sei->payloads[0].payload_size ); + + return 0; +} diff -uNrp src/x264.c src.new/x264.c --- src/x264.c 2014-11-13 21:05:09 +0300 +++ src.new/x264.c 2014-11-13 21:12:40 +0300 @@ -147,17 +147,6 @@ static void sigint_handler( int a ) b_ctrl_c = 1; } -typedef struct { - int b_progress; - int i_seek; - hnd_t hin; - hnd_t hout; - FILE *qpfile; - FILE *tcfile_out; - double timebase_convert_multiplier; - int i_pulldown; -} cli_opt_t; - /* file i/o operation structs */ cli_input_t cli_input; static cli_output_t cli_output; @@ -256,6 +245,21 @@ static const cli_pulldown_t pulldown_val // indexed by pic_struct enum static const float pulldown_frame_duration[10] = { 0.0, 1, 0.5, 0.5, 1, 1, 1.5, 1.5, 2, 3 }; +/* Is there a spec with these in them? + * PAL framerates with closed captions are unlikely */ +cli_timecode_ctx_t timecode_ctx[] = +{ + { 24, 1, 24, 25, 0 }, + { 24000, 1001, 24, 25, 0 }, + { 25, 1, 25, 24, 0 }, + { 30000, 1001, 30, 20, 0 }, + { 30, 1, 30, 20, 0 }, + { 50, 1, 25, 12, 1 }, + { 60000, 1001, 30, 10, 1 }, + { 60, 1, 30, 10, 1 }, + { 0, 0, 0, 0 } +}; + static void help( x264_param_t *defaults, int longhelp ); static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ); static int encode( x264_param_t *param, cli_opt_t *opt ); @@ -1012,6 +1016,30 @@ static void help( x264_param_t *defaults x264_register_vid_filters(); x264_vid_filter_help( longhelp ); H0( "\n" ); + H0( "\n" ); + H0( "SMPTE Timecodes:\n" ); + H0( "\n" ); + H0( " --timecode Start SMPTE timecode\n" ); + H0( "\n" ); + H0( " It is important to put quotes around the timecode, e.g.\n" ); + H0( "\n" ); + H0( " --timecode \"01:00:00:00\" - non-drop frame\n" ); + H0( " --timecode \"01:00:00;00\" - drop frame\n" ); + H0( "\n" ); + H0( "Closed Captions:\n" ); + H0( "Only single SCC files are currently supported.\n" ); + H0( "\n" ); + H0( " --captions | SCC caption file input\n" ); + H0( " --captions-echostar Write Echostar captions\n"); + H0( "\n" ); + H0( " Format of :\n" ); + H0( "\n" ); + H0( " scc:filename=in.scc,sff=1\n" ); + H0( "\n" ); + H0( " SCC Options:\n" ); + H0( "\n" ); + H0( " filename Mandatory. File name\n" ); + H0( " sff 0/1 Optional. Write second field first in captions\n" ); } typedef enum @@ -1049,7 +1077,10 @@ typedef enum OPT_DTS_COMPRESSION, OPT_OUTPUT_CSP, OPT_INPUT_RANGE, - OPT_RANGE + OPT_RANGE, + OPT_TIMECODE, + OPT_CAPTIONS, + OPT_CAPTIONS_ECHOSTAR } OptionsOPT; static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw"; @@ -1224,6 +1255,9 @@ static struct option long_options[] = { "input-range", required_argument, NULL, OPT_INPUT_RANGE }, { "stitchable", no_argument, NULL, 0 }, { "filler", no_argument, NULL, 0 }, + { "timecode", required_argument, NULL, OPT_TIMECODE }, + { "captions", required_argument, NULL, OPT_CAPTIONS }, + { "captions-echostar", no_argument, NULL, OPT_CAPTIONS_ECHOSTAR }, {0, 0, 0, 0} }; @@ -1452,6 +1486,7 @@ static int parse( int argc, char **argv, x264_param_t defaults; char *profile = NULL; char *vid_filters = NULL; + char *scc_extra = NULL; int b_thread_input = 0; int b_turbo = 1; int b_user_ref = 0; @@ -1461,6 +1496,8 @@ static int parse( int argc, char **argv, cli_output_opt_t output_opt; char *preset = NULL; char *tune = NULL; + int j; + char tc_separator[2]; x264_param_default( &defaults ); cli_log_level = defaults.i_log_level; @@ -1647,6 +1684,17 @@ static int parse( int argc, char **argv, FAIL_IF_ERROR( parse_enum_value( optarg, range_names, ¶m->vui.b_fullrange ), "Unknown range `%s'\n", optarg ); input_opt.output_range = param->vui.b_fullrange += RANGE_AUTO; break; + case OPT_TIMECODE: + /* don't fail if the user is a bit lazy and doesn't include leading zeros */ + sscanf( optarg, "%u:%u:%u%[;:]%u", &opt->timecode.hour, &opt->timecode.min, &opt->timecode.sec, tc_separator, &opt->timecode.frame ); + opt->drop_frame = !!strcmp( tc_separator, ";" ); + break; + case OPT_CAPTIONS: + scc_extra = optarg; + break; + case OPT_CAPTIONS_ECHOSTAR: + opt->echostar_captions = 1; + break; default: generic_option: { @@ -1761,6 +1809,67 @@ generic_option: info.fps_num = param->i_fps_num; info.fps_den = param->i_fps_den; } + + for( j = 0; timecode_ctx[j].fps_num != 0; j++ ) + { + if( timecode_ctx[j].fps_num == info.fps_num && timecode_ctx[j].fps_den == info.fps_den ) + { + opt->timecode_ctx = &timecode_ctx[j]; + opt->write_timecode = 1; + break; + } + } + + if( scc_extra ) + { + FAIL_IF_ERROR( !opt->write_timecode, "unsupported framerate for scc file\n" ); + FAIL_IF_ERROR( opt->i_pulldown, "pulldown is currently not compatible with scc input\n" ); + FAIL_IF_ERROR( opt->i_seek, "seek is currently not compatible with scc input\n" ); + + j = 0; + for( char *p = scc_extra; p && *p; ) + { + int tok_len = strcspn( p, "|" ); + int p_len = strlen( p ); + p[tok_len] = 0; + int name_len = strcspn( p, ":" ); + p[name_len] = 0; + name_len += name_len != tok_len; + if( j == MAX_SCC ) + { + x264_cli_log( "x264", X264_LOG_WARNING, "only %d scc files supported\n", MAX_SCC ); + break; + } + + if( !strcasecmp( p, "scc" ) ) + { + char *params = p + name_len; + static const char *optlist[] = { "filename", "sff", NULL }; + char **opts = x264_split_options( params, optlist ); + if( !opts && params ) + break; + + char *filename = x264_get_option( optlist[0], opts ); + char *sff = x264_get_option( optlist[1], opts ); + + FAIL_IF_ERROR( !filename, "Filename missing in caption file %i\n", j+1 ); + + opt->scc_opts[j].scc_file = fopen( filename, "rb" ); + FAIL_IF_ERROR( !opt->scc_opts[j].scc_file, "Could not open caption file %s\n", filename ); + opt->scc_opts[j].sff = x264_otoi( sff, 0 ); + + FAIL_IF_ERROR( open_scc( opt, &opt->scc_opts[j] ) < 0, "Could not read caption file %s\n", filename ); + + opt->num_scc++; + j++; + } + else + x264_cli_log( "x264", X264_LOG_WARNING, "%s is not a valid caption type\n", p ); + + p += X264_MIN( tok_len+1, p_len ); + } + } + if( !info.vfr ) { info.timebase_num = info.fps_den; @@ -2075,6 +2184,8 @@ static int encode( x264_param_t *param, if( opt->qpfile ) parse_qpfile( opt, &pic, i_frame + opt->i_seek ); + if( opt->num_scc ) + write_cc( opt, &pic.extra_sei, i_frame & 1 ); prev_dts = last_dts; i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts ); @@ -2097,6 +2208,9 @@ static int encode( x264_param_t *param, /* update status line (up to 1000 times per input file) */ if( opt->b_progress && i_frame_output ) i_previous = print_status( i_start, i_previous, i_frame_output, param->i_frame_total, i_file, param, 2 * last_dts - prev_dts - first_dts ); + + if( opt->write_timecode && ( ( opt->timecode_ctx->frame_doubling && (i_frame & 1) ) || !opt->timecode_ctx->frame_doubling ) ) + increase_tc( opt, &opt->timecode ); } /* Flush delayed frames */ while( !b_ctrl_c && x264_encoder_delayed_frames( h ) ) @@ -2158,5 +2272,8 @@ fail: secs/3600, (secs/60)%60, secs%60, (int)((i_end - i_start)%1000000/10000) ); } + for( int i = 0; i < opt->num_scc; i++ ) + fclose( opt->scc_opts[i].scc_file ); + return retval; } diff -uNrp src/x264cli.h src.new/x264cli.h --- src/x264cli.h 2014-11-13 21:05:08 +0300 +++ src.new/x264cli.h 2014-11-13 21:09:17 +0300 @@ -31,6 +31,7 @@ /* In microseconds */ #define UPDATE_INTERVAL 250000 +#define MAX_SCC 1 typedef void *hnd_t; @@ -87,4 +88,43 @@ typedef enum RANGE_PC } range_enum; +typedef struct __attribute__ ((__packed__)) { + int hour; + int min; + int sec; + int frame; +} cli_timecode_t; + +typedef struct { + FILE *scc_file; + int sff; + cli_timecode_t timecode; +} scc_opt_t; + +typedef struct{ + int fps_num; + int fps_den; + int mod; + int cc_count; + int frame_doubling; +} cli_timecode_ctx_t; + +typedef struct { + int b_progress; + int i_seek; + hnd_t hin; + hnd_t hout; + FILE *qpfile; + FILE *tcfile_out; + double timebase_convert_multiplier; + int i_pulldown; + int write_timecode; + int drop_frame; + cli_timecode_t timecode; + cli_timecode_ctx_t *timecode_ctx; + int num_scc; + scc_opt_t scc_opts[MAX_SCC]; + int echostar_captions; +} cli_opt_t; + #endif