Some demuxers set a timecode in the format or streams metadata. The muxers now make use of this metadata instead of a duplicated private option. This makes possible transparent copy of the timecode when transmuxing and transcoding. -timecode option for MPEG1/2 codec is also renamed to -gop_timecode. The global ffmpeg -timecode option will set it anyway so no option change visible for the user.tags/n1.0
@@ -5618,6 +5618,16 @@ static int opt_deinterlace(const char *opt, const char *arg) | |||||
return 0; | return 0; | ||||
} | } | ||||
static int opt_timecode(OptionsContext *o, const char *opt, const char *arg) | |||||
{ | |||||
char *tcr = av_asprintf("timecode=%s", arg); | |||||
int ret = parse_option(o, "metadata:g", tcr, options); | |||||
if (ret >= 0) | |||||
ret = opt_default("gop_timecode", arg); | |||||
av_free(tcr); | |||||
return ret; | |||||
} | |||||
static void parse_cpuflags(int argc, char **argv, const OptionDef *options) | static void parse_cpuflags(int argc, char **argv, const OptionDef *options) | ||||
{ | { | ||||
int idx = locate_option(argc, argv, options, "cpuflags"); | int idx = locate_option(argc, argv, options, "cpuflags"); | ||||
@@ -5752,6 +5762,7 @@ static const OptionDef options[] = { | |||||
{ "sameq", OPT_BOOL | OPT_VIDEO, {(void*)&same_quant}, "use same quantizer as source (implies VBR)" }, | { "sameq", OPT_BOOL | OPT_VIDEO, {(void*)&same_quant}, "use same quantizer as source (implies VBR)" }, | ||||
{ "same_quant", OPT_BOOL | OPT_VIDEO, {(void*)&same_quant}, | { "same_quant", OPT_BOOL | OPT_VIDEO, {(void*)&same_quant}, | ||||
"use same quantizer as source (implies VBR)" }, | "use same quantizer as source (implies VBR)" }, | ||||
{ "timecode", HAS_ARG | OPT_VIDEO | OPT_FUNC2, {(void*)opt_timecode}, "set initial TimeCode value.", "hh:mm:ss[:;.]ff" }, | |||||
{ "pass", HAS_ARG | OPT_VIDEO, {(void*)opt_pass}, "select the pass number (1 or 2)", "n" }, | { "pass", HAS_ARG | OPT_VIDEO, {(void*)opt_pass}, "select the pass number (1 or 2)", "n" }, | ||||
{ "passlogfile", HAS_ARG | OPT_VIDEO, {(void*)&opt_passlogfile}, "select two pass log file name prefix", "prefix" }, | { "passlogfile", HAS_ARG | OPT_VIDEO, {(void*)&opt_passlogfile}, "select two pass log file name prefix", "prefix" }, | ||||
{ "deinterlace", OPT_EXPERT | OPT_VIDEO, {(void*)opt_deinterlace}, | { "deinterlace", OPT_EXPERT | OPT_VIDEO, {(void*)opt_deinterlace}, | ||||
@@ -931,8 +931,7 @@ static void mpeg1_encode_block(MpegEncContext *s, | |||||
#define OFFSET(x) offsetof(MpegEncContext, x) | #define OFFSET(x) offsetof(MpegEncContext, x) | ||||
#define VE AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | #define VE AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | ||||
#define COMMON_OPTS\ | #define COMMON_OPTS\ | ||||
{AV_TIMECODE_OPTION(MpegEncContext, tc_opt_str, \ | |||||
AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)},\ | |||||
{ "gop_timecode", "MPEG GOP Timecode in hh:mm:ss[:;.]ff format", OFFSET(tc_opt_str), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, VE },\ | |||||
{ "intra_vlc", "Use MPEG-2 intra VLC table.", OFFSET(intra_vlc_format), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE },\ | { "intra_vlc", "Use MPEG-2 intra VLC table.", OFFSET(intra_vlc_format), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE },\ | ||||
{ "drop_frame_timecode", "Timecode is in drop frame format.", OFFSET(drop_frame_timecode), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE}, \ | { "drop_frame_timecode", "Timecode is in drop frame format.", OFFSET(drop_frame_timecode), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE}, \ | ||||
{ "scan_offset", "Reserve space for SVCD scan offset user data.", OFFSET(scan_offset), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE }, | { "scan_offset", "Reserve space for SVCD scan offset user data.", OFFSET(scan_offset), AV_OPT_TYPE_INT, { 0 }, 0, 1, VE }, | ||||
@@ -52,7 +52,6 @@ struct DVMuxContext { | |||||
int has_audio; /* frame under contruction has audio */ | int has_audio; /* frame under contruction has audio */ | ||||
int has_video; /* frame under contruction has video */ | int has_video; /* frame under contruction has video */ | ||||
uint8_t frame_buf[DV_MAX_FRAME_SIZE]; /* frame under contruction */ | uint8_t frame_buf[DV_MAX_FRAME_SIZE]; /* frame under contruction */ | ||||
char *tc_opt_str; /* timecode option string */ | |||||
AVTimecode tc; /* timecode context */ | AVTimecode tc; /* timecode context */ | ||||
}; | }; | ||||
@@ -356,6 +355,7 @@ static int dv_write_header(AVFormatContext *s) | |||||
{ | { | ||||
AVRational rate; | AVRational rate; | ||||
DVMuxContext *dvc = s->priv_data; | DVMuxContext *dvc = s->priv_data; | ||||
AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); | |||||
if (!dv_init_mux(s)) { | if (!dv_init_mux(s)) { | ||||
av_log(s, AV_LOG_ERROR, "Can't initialize DV format!\n" | av_log(s, AV_LOG_ERROR, "Can't initialize DV format!\n" | ||||
@@ -366,9 +366,16 @@ static int dv_write_header(AVFormatContext *s) | |||||
} | } | ||||
rate.num = dvc->sys->ltc_divisor; | rate.num = dvc->sys->ltc_divisor; | ||||
rate.den = 1; | rate.den = 1; | ||||
if (dvc->tc_opt_str) | |||||
return av_timecode_init_from_string(&dvc->tc, rate, | |||||
dvc->tc_opt_str, s); | |||||
if (!tcr) { // no global timecode, look into the streams | |||||
int i; | |||||
for (i = 0; i < s->nb_streams; i++) { | |||||
tcr = av_dict_get(s->streams[i]->metadata, "timecode", NULL, 0); | |||||
if (tcr) | |||||
break; | |||||
} | |||||
} | |||||
if (tcr) | |||||
return av_timecode_init_from_string(&dvc->tc, rate, tcr->value, s); | |||||
return av_timecode_init(&dvc->tc, rate, 0, 0, s); | return av_timecode_init(&dvc->tc, rate, 0, 0, s); | ||||
} | } | ||||
@@ -398,16 +405,6 @@ static int dv_write_trailer(struct AVFormatContext *s) | |||||
return 0; | return 0; | ||||
} | } | ||||
static const AVClass class = { | |||||
.class_name = "dv", | |||||
.item_name = av_default_item_name, | |||||
.version = LIBAVUTIL_VERSION_INT, | |||||
.option = (const AVOption[]){ | |||||
{AV_TIMECODE_OPTION(DVMuxContext, tc_opt_str, AV_OPT_FLAG_ENCODING_PARAM)}, | |||||
{NULL}, | |||||
}, | |||||
}; | |||||
AVOutputFormat ff_dv_muxer = { | AVOutputFormat ff_dv_muxer = { | ||||
.name = "dv", | .name = "dv", | ||||
.long_name = NULL_IF_CONFIG_SMALL("DV video format"), | .long_name = NULL_IF_CONFIG_SMALL("DV video format"), | ||||
@@ -418,5 +415,4 @@ AVOutputFormat ff_dv_muxer = { | |||||
.write_header = dv_write_header, | .write_header = dv_write_header, | ||||
.write_packet = dv_write_packet, | .write_packet = dv_write_packet, | ||||
.write_trailer = dv_write_trailer, | .write_trailer = dv_write_trailer, | ||||
.priv_class = &class, | |||||
}; | }; |
@@ -41,7 +41,6 @@ typedef struct GXFTimecode{ | |||||
int ff; | int ff; | ||||
int color; | int color; | ||||
int drop; | int drop; | ||||
char *str; | |||||
} GXFTimecode; | } GXFTimecode; | ||||
typedef struct GXFStreamContext { | typedef struct GXFStreamContext { | ||||
@@ -655,11 +654,11 @@ static void gxf_init_timecode_track(GXFStreamContext *sc, GXFStreamContext *vsc) | |||||
sc->fields = vsc->fields; | sc->fields = vsc->fields; | ||||
} | } | ||||
static int gxf_init_timecode(AVFormatContext *s, GXFTimecode *tc, int fields) | |||||
static int gxf_init_timecode(AVFormatContext *s, GXFTimecode *tc, const char *tcstr, int fields) | |||||
{ | { | ||||
char c; | char c; | ||||
if (sscanf(tc->str, "%d:%d:%d%c%d", &tc->hh, &tc->mm, &tc->ss, &c, &tc->ff) != 5) { | |||||
if (sscanf(tcstr, "%d:%d:%d%c%d", &tc->hh, &tc->mm, &tc->ss, &c, &tc->ff) != 5) { | |||||
av_log(s, AV_LOG_ERROR, "unable to parse timecode, " | av_log(s, AV_LOG_ERROR, "unable to parse timecode, " | ||||
"syntax: hh:mm:ss[:;.]ff\n"); | "syntax: hh:mm:ss[:;.]ff\n"); | ||||
return -1; | return -1; | ||||
@@ -681,6 +680,7 @@ static int gxf_write_header(AVFormatContext *s) | |||||
GXFStreamContext *vsc = NULL; | GXFStreamContext *vsc = NULL; | ||||
uint8_t tracks[255] = {0}; | uint8_t tracks[255] = {0}; | ||||
int i, media_info = 0; | int i, media_info = 0; | ||||
AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); | |||||
if (!pb->seekable) { | if (!pb->seekable) { | ||||
av_log(s, AV_LOG_ERROR, "gxf muxer does not support streamed output, patch welcome"); | av_log(s, AV_LOG_ERROR, "gxf muxer does not support streamed output, patch welcome"); | ||||
@@ -741,6 +741,8 @@ static int gxf_write_header(AVFormatContext *s) | |||||
"gxf muxer only accepts PAL or NTSC resolutions currently\n"); | "gxf muxer only accepts PAL or NTSC resolutions currently\n"); | ||||
return -1; | return -1; | ||||
} | } | ||||
if (!tcr) | |||||
tcr = av_dict_get(st->metadata, "timecode", NULL, 0); | |||||
avpriv_set_pts_info(st, 64, gxf->time_base.num, gxf->time_base.den); | avpriv_set_pts_info(st, 64, gxf->time_base.num, gxf->time_base.den); | ||||
if (gxf_find_lines_index(st) < 0) | if (gxf_find_lines_index(st) < 0) | ||||
sc->lines_index = -1; | sc->lines_index = -1; | ||||
@@ -792,9 +794,8 @@ static int gxf_write_header(AVFormatContext *s) | |||||
if (ff_audio_interleave_init(s, GXF_samples_per_frame, (AVRational){ 1, 48000 }) < 0) | if (ff_audio_interleave_init(s, GXF_samples_per_frame, (AVRational){ 1, 48000 }) < 0) | ||||
return -1; | return -1; | ||||
if (gxf->tc.str) { | |||||
gxf_init_timecode(s, &gxf->tc, vsc->fields); | |||||
} | |||||
if (tcr) | |||||
gxf_init_timecode(s, &gxf->tc, tcr->value, vsc->fields); | |||||
gxf_init_timecode_track(&gxf->timecode_track, vsc); | gxf_init_timecode_track(&gxf->timecode_track, vsc); | ||||
gxf->flags |= 0x200000; // time code track is non-drop frame | gxf->flags |= 0x200000; // time code track is non-drop frame | ||||
@@ -983,18 +984,6 @@ static int gxf_interleave_packet(AVFormatContext *s, AVPacket *out, AVPacket *pk | |||||
ff_interleave_packet_per_dts, gxf_compare_field_nb); | ff_interleave_packet_per_dts, gxf_compare_field_nb); | ||||
} | } | ||||
static const AVOption options[] = { | |||||
{ AV_TIMECODE_OPTION(GXFContext, tc.str, AV_OPT_FLAG_ENCODING_PARAM) }, | |||||
{ NULL } | |||||
}; | |||||
static const AVClass gxf_muxer_class = { | |||||
.class_name = "GXF muxer", | |||||
.item_name = av_default_item_name, | |||||
.option = options, | |||||
.version = LIBAVUTIL_VERSION_INT, | |||||
}; | |||||
AVOutputFormat ff_gxf_muxer = { | AVOutputFormat ff_gxf_muxer = { | ||||
.name = "gxf", | .name = "gxf", | ||||
.long_name = NULL_IF_CONFIG_SMALL("GXF format"), | .long_name = NULL_IF_CONFIG_SMALL("GXF format"), | ||||
@@ -1006,5 +995,4 @@ AVOutputFormat ff_gxf_muxer = { | |||||
.write_packet = gxf_write_packet, | .write_packet = gxf_write_packet, | ||||
.write_trailer = gxf_write_trailer, | .write_trailer = gxf_write_trailer, | ||||
.interleave_packet = gxf_interleave_packet, | .interleave_packet = gxf_interleave_packet, | ||||
.priv_class = &gxf_muxer_class, | |||||
}; | }; |
@@ -189,7 +189,6 @@ typedef struct MXFContext { | |||||
unsigned body_partitions_count; | unsigned body_partitions_count; | ||||
int last_key_index; ///< index of last key frame | int last_key_index; ///< index of last key frame | ||||
uint64_t duration; | uint64_t duration; | ||||
char *tc_opt_str; ///< timecode option string | |||||
AVTimecode tc; ///< timecode context | AVTimecode tc; ///< timecode context | ||||
AVStream *timecode_track; | AVStream *timecode_track; | ||||
int timecode_base; ///< rounded time code base (25 or 30) | int timecode_base; ///< rounded time code base (25 or 30) | ||||
@@ -1403,6 +1402,7 @@ static int mxf_write_header(AVFormatContext *s) | |||||
const int *samples_per_frame = NULL; | const int *samples_per_frame = NULL; | ||||
AVDictionaryEntry *t; | AVDictionaryEntry *t; | ||||
int64_t timestamp = 0; | int64_t timestamp = 0; | ||||
AVDictionaryEntry *tcr = av_dict_get(s->metadata, "timecode", NULL, 0); | |||||
if (!s->nb_streams) | if (!s->nb_streams) | ||||
return -1; | return -1; | ||||
@@ -1443,9 +1443,10 @@ static int mxf_write_header(AVFormatContext *s) | |||||
} | } | ||||
rate = (AVRational){mxf->time_base.den, mxf->time_base.num}; | rate = (AVRational){mxf->time_base.den, mxf->time_base.num}; | ||||
avpriv_set_pts_info(st, 64, mxf->time_base.num, mxf->time_base.den); | avpriv_set_pts_info(st, 64, mxf->time_base.num, mxf->time_base.den); | ||||
if (mxf->tc_opt_str) | |||||
ret = av_timecode_init_from_string(&mxf->tc, rate, | |||||
mxf->tc_opt_str, s); | |||||
if (!tcr) | |||||
tcr = av_dict_get(st->metadata, "timecode", NULL, 0); | |||||
if (tcr) | |||||
ret = av_timecode_init_from_string(&mxf->tc, rate, tcr->value, s); | |||||
else | else | ||||
ret = av_timecode_init(&mxf->tc, rate, 0, 0, s); | ret = av_timecode_init(&mxf->tc, rate, 0, 0, s); | ||||
if (ret < 0) | if (ret < 0) | ||||
@@ -1877,26 +1878,6 @@ static int mxf_interleave(AVFormatContext *s, AVPacket *out, AVPacket *pkt, int | |||||
mxf_interleave_get_packet, mxf_compare_timestamps); | mxf_interleave_get_packet, mxf_compare_timestamps); | ||||
} | } | ||||
static const AVClass mxf_class = { | |||||
.class_name = "mxf", | |||||
.item_name = av_default_item_name, | |||||
.version = LIBAVUTIL_VERSION_INT, | |||||
.option = (const AVOption[]){ | |||||
{AV_TIMECODE_OPTION(MXFContext, tc_opt_str, AV_OPT_FLAG_ENCODING_PARAM)}, | |||||
{NULL} | |||||
}, | |||||
}; | |||||
static const AVClass mxf_d10_class = { | |||||
.class_name = "mxf_d10", | |||||
.item_name = av_default_item_name, | |||||
.version = LIBAVUTIL_VERSION_INT, | |||||
.option = (const AVOption[]){ | |||||
{AV_TIMECODE_OPTION(MXFContext, tc_opt_str, AV_OPT_FLAG_ENCODING_PARAM)}, | |||||
{NULL} | |||||
}, | |||||
}; | |||||
AVOutputFormat ff_mxf_muxer = { | AVOutputFormat ff_mxf_muxer = { | ||||
.name = "mxf", | .name = "mxf", | ||||
.long_name = NULL_IF_CONFIG_SMALL("Material eXchange Format"), | .long_name = NULL_IF_CONFIG_SMALL("Material eXchange Format"), | ||||
@@ -1910,7 +1891,6 @@ AVOutputFormat ff_mxf_muxer = { | |||||
.write_trailer = mxf_write_footer, | .write_trailer = mxf_write_footer, | ||||
.flags = AVFMT_NOTIMESTAMPS, | .flags = AVFMT_NOTIMESTAMPS, | ||||
.interleave_packet = mxf_interleave, | .interleave_packet = mxf_interleave, | ||||
.priv_class = &mxf_class, | |||||
}; | }; | ||||
AVOutputFormat ff_mxf_d10_muxer = { | AVOutputFormat ff_mxf_d10_muxer = { | ||||
@@ -1925,5 +1905,4 @@ AVOutputFormat ff_mxf_d10_muxer = { | |||||
.write_trailer = mxf_write_footer, | .write_trailer = mxf_write_footer, | ||||
.flags = AVFMT_NOTIMESTAMPS, | .flags = AVFMT_NOTIMESTAMPS, | ||||
.interleave_packet = mxf_interleave, | .interleave_packet = mxf_interleave, | ||||
.priv_class = &mxf_d10_class, | |||||
}; | }; |
@@ -32,12 +32,6 @@ | |||||
#define AV_TIMECODE_STR_SIZE 16 | #define AV_TIMECODE_STR_SIZE 16 | ||||
#define AV_TIMECODE_OPTION(ctx, string_field, flags) \ | |||||
"timecode", "set timecode value following hh:mm:ss[:;.]ff format, " \ | |||||
"use ';' or '.' before frame number for drop frame", \ | |||||
offsetof(ctx, string_field), \ | |||||
AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, flags | |||||
enum AVTimecodeFlag { | enum AVTimecodeFlag { | ||||
AV_TIMECODE_FLAG_DROPFRAME = 1<<0, ///< timecode is drop frame | AV_TIMECODE_FLAG_DROPFRAME = 1<<0, ///< timecode is drop frame | ||||
AV_TIMECODE_FLAG_24HOURSMAX = 1<<1, ///< timecode wraps after 24 hours | AV_TIMECODE_FLAG_24HOURSMAX = 1<<1, ///< timecode wraps after 24 hours | ||||