mirror of https://git.ffmpeg.org/ffmpeg.git
gxfenc: support timecode option
Reviewed-by: Baptiste Coudurier
This commit is contained in:
parent
a99dbe014d
commit
070a40f1b7
|
@ -20,7 +20,9 @@
|
|||
*/
|
||||
|
||||
#include "libavutil/intfloat_readwrite.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavcodec/timecode.h"
|
||||
#include "avformat.h"
|
||||
#include "internal.h"
|
||||
#include "gxf.h"
|
||||
|
@ -29,6 +31,19 @@
|
|||
|
||||
#define GXF_AUDIO_PACKET_SIZE 65536
|
||||
|
||||
#define GXF_TIMECODE(c, d, h, m, s, f) \
|
||||
((c) << 30 | (d) << 29 | (h) << 24 | (m) << 16 | (s) << 8 | (f))
|
||||
|
||||
typedef struct GXFTimecode{
|
||||
int hh;
|
||||
int mm;
|
||||
int ss;
|
||||
int ff;
|
||||
int color;
|
||||
int drop;
|
||||
char *str;
|
||||
} GXFTimecode;
|
||||
|
||||
typedef struct GXFStreamContext {
|
||||
AudioInterleaveContext aic;
|
||||
uint32_t track_type;
|
||||
|
@ -49,6 +64,7 @@ typedef struct GXFStreamContext {
|
|||
} GXFStreamContext;
|
||||
|
||||
typedef struct GXFContext {
|
||||
AVClass *av_class;
|
||||
uint32_t nb_fields;
|
||||
uint16_t audio_tracks;
|
||||
uint16_t mpeg_tracks;
|
||||
|
@ -67,6 +83,7 @@ typedef struct GXFContext {
|
|||
uint64_t *map_offsets; ///< offset of map packets
|
||||
unsigned map_offsets_nb;
|
||||
unsigned packet_count;
|
||||
GXFTimecode tc;
|
||||
} GXFContext;
|
||||
|
||||
static const struct {
|
||||
|
@ -200,19 +217,21 @@ static int gxf_write_mpeg_auxiliary(AVIOContext *pb, AVStream *st)
|
|||
return size + 3;
|
||||
}
|
||||
|
||||
static int gxf_write_timecode_auxiliary(AVIOContext *pb, GXFStreamContext *sc)
|
||||
static int gxf_write_timecode_auxiliary(AVIOContext *pb, GXFContext *gxf)
|
||||
{
|
||||
avio_w8(pb, 0); /* fields */
|
||||
avio_w8(pb, 0); /* seconds */
|
||||
avio_w8(pb, 0); /* minutes */
|
||||
avio_w8(pb, 0); /* flags + hours */
|
||||
uint32_t timecode = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop,
|
||||
gxf->tc.hh, gxf->tc.mm,
|
||||
gxf->tc.ss, gxf->tc.ff);
|
||||
|
||||
avio_wl32(pb, timecode);
|
||||
/* reserved */
|
||||
avio_wb32(pb, 0);
|
||||
avio_wl32(pb, 0);
|
||||
return 8;
|
||||
}
|
||||
|
||||
static int gxf_write_track_description(AVFormatContext *s, GXFStreamContext *sc, int index)
|
||||
{
|
||||
GXFContext *gxf = s->priv_data;
|
||||
AVIOContext *pb = s->pb;
|
||||
int64_t pos;
|
||||
int mpeg = sc->track_type == 4 || sc->track_type == 9;
|
||||
|
@ -236,7 +255,7 @@ static int gxf_write_track_description(AVFormatContext *s, GXFStreamContext *sc,
|
|||
avio_w8(pb, TRACK_AUX);
|
||||
avio_w8(pb, 8);
|
||||
if (sc->track_type == 3)
|
||||
gxf_write_timecode_auxiliary(pb, sc);
|
||||
gxf_write_timecode_auxiliary(pb, gxf);
|
||||
else
|
||||
avio_wl64(pb, 0);
|
||||
}
|
||||
|
@ -398,7 +417,9 @@ static int gxf_write_umf_material_description(AVFormatContext *s)
|
|||
int timecode_base = gxf->time_base.den == 60000 ? 60 : 50;
|
||||
int64_t timestamp = 0;
|
||||
AVDictionaryEntry *t;
|
||||
uint32_t timecode;
|
||||
uint64_t nb_fields;
|
||||
uint32_t timecode_in; // timecode at mark in
|
||||
uint32_t timecode_out; // timecode at mark out
|
||||
|
||||
#if FF_API_TIMESTAMP
|
||||
if (s->timestamp)
|
||||
|
@ -408,20 +429,29 @@ static int gxf_write_umf_material_description(AVFormatContext *s)
|
|||
if (t = av_dict_get(s->metadata, "creation_time", NULL, 0))
|
||||
timestamp = ff_iso8601_to_unix_time(t->value);
|
||||
|
||||
// XXX drop frame
|
||||
timecode =
|
||||
gxf->nb_fields / (timecode_base * 3600) % 24 << 24 | // hours
|
||||
gxf->nb_fields / (timecode_base * 60) % 60 << 16 | // minutes
|
||||
gxf->nb_fields / timecode_base % 60 << 8 | // seconds
|
||||
gxf->nb_fields % timecode_base; // fields
|
||||
timecode_in = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop,
|
||||
gxf->tc.hh, gxf->tc.mm,
|
||||
gxf->tc.ss, gxf->tc.ff);
|
||||
|
||||
nb_fields = gxf->nb_fields +
|
||||
gxf->tc.hh * (timecode_base * 3600) +
|
||||
gxf->tc.mm * (timecode_base * 60) +
|
||||
gxf->tc.ss * timecode_base +
|
||||
gxf->tc.ff;
|
||||
|
||||
timecode_out = GXF_TIMECODE(gxf->tc.color, gxf->tc.drop,
|
||||
nb_fields / (timecode_base * 3600) % 24,
|
||||
nb_fields / (timecode_base * 60) % 60,
|
||||
nb_fields / timecode_base % 60,
|
||||
nb_fields % timecode_base);
|
||||
|
||||
avio_wl32(pb, gxf->flags);
|
||||
avio_wl32(pb, gxf->nb_fields); /* length of the longest track */
|
||||
avio_wl32(pb, gxf->nb_fields); /* length of the shortest track */
|
||||
avio_wl32(pb, 0); /* mark in */
|
||||
avio_wl32(pb, gxf->nb_fields); /* mark out */
|
||||
avio_wl32(pb, 0); /* timecode mark in */
|
||||
avio_wl32(pb, timecode); /* timecode mark out */
|
||||
avio_wl32(pb, timecode_in); /* timecode mark in */
|
||||
avio_wl32(pb, timecode_out); /* timecode mark out */
|
||||
avio_wl64(pb, timestamp); /* modification time */
|
||||
avio_wl64(pb, timestamp); /* creation time */
|
||||
avio_wl16(pb, 0); /* reserved */
|
||||
|
@ -496,9 +526,9 @@ static int gxf_write_umf_media_mpeg(AVIOContext *pb, AVStream *st)
|
|||
return 32;
|
||||
}
|
||||
|
||||
static int gxf_write_umf_media_timecode(AVIOContext *pb, GXFStreamContext *sc)
|
||||
static int gxf_write_umf_media_timecode(AVIOContext *pb, int drop)
|
||||
{
|
||||
avio_wl32(pb, 1); /* non drop frame */
|
||||
avio_wl32(pb, drop); /* drop frame */
|
||||
avio_wl32(pb, 0); /* reserved */
|
||||
avio_wl32(pb, 0); /* reserved */
|
||||
avio_wl32(pb, 0); /* reserved */
|
||||
|
@ -578,7 +608,7 @@ static int gxf_write_umf_media_description(AVFormatContext *s)
|
|||
avio_wl32(pb, 0); /* reserved */
|
||||
|
||||
if (sc == &gxf->timecode_track)
|
||||
gxf_write_umf_media_timecode(pb, sc); /* 8 0bytes */
|
||||
gxf_write_umf_media_timecode(pb, gxf->tc.drop);
|
||||
else {
|
||||
AVStream *st = s->streams[i];
|
||||
switch (st->codec->codec_id) {
|
||||
|
@ -641,6 +671,25 @@ static void gxf_init_timecode_track(GXFStreamContext *sc, GXFStreamContext *vsc)
|
|||
sc->fields = vsc->fields;
|
||||
}
|
||||
|
||||
static int gxf_init_timecode(AVFormatContext *s, GXFTimecode *tc, int fields)
|
||||
{
|
||||
char c;
|
||||
|
||||
if (sscanf(tc->str, "%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, "
|
||||
"syntax: hh:mm:ss[:;.]ff\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tc->color = 0;
|
||||
tc->drop = c != ':';
|
||||
|
||||
if (fields == 2)
|
||||
tc->ff = tc->ff * 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gxf_write_header(AVFormatContext *s)
|
||||
{
|
||||
AVIOContext *pb = s->pb;
|
||||
|
@ -759,6 +808,10 @@ static int gxf_write_header(AVFormatContext *s)
|
|||
if (ff_audio_interleave_init(s, GXF_samples_per_frame, (AVRational){ 1, 48000 }) < 0)
|
||||
return -1;
|
||||
|
||||
if (gxf->tc.str) {
|
||||
gxf_init_timecode(s, &gxf->tc, vsc->fields);
|
||||
}
|
||||
|
||||
gxf_init_timecode_track(&gxf->timecode_track, vsc);
|
||||
gxf->flags |= 0x200000; // time code track is non-drop frame
|
||||
|
||||
|
@ -946,6 +999,18 @@ static int gxf_interleave_packet(AVFormatContext *s, AVPacket *out, AVPacket *pk
|
|||
av_interleave_packet_per_dts, gxf_compare_field_nb);
|
||||
}
|
||||
|
||||
static const AVOption options[] = {
|
||||
{ TIMECODE_OPT(GXFContext, 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 = {
|
||||
.name = "gxf",
|
||||
.long_name = NULL_IF_CONFIG_SMALL("GXF format"),
|
||||
|
@ -957,4 +1022,5 @@ AVOutputFormat ff_gxf_muxer = {
|
|||
.write_packet = gxf_write_packet,
|
||||
.write_trailer = gxf_write_trailer,
|
||||
.interleave_packet = gxf_interleave_packet,
|
||||
.priv_class = &gxf_muxer_class,
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
346d38d330ab5cb0caa6b5537167bc0d *./tests/data/lavf/lavf.gxf
|
||||
1c1693cf2358025f1e37ac76e1da925a *./tests/data/lavf/lavf.gxf
|
||||
796392 ./tests/data/lavf/lavf.gxf
|
||||
./tests/data/lavf/lavf.gxf CRC=0x102918fd
|
||||
|
|
Loading…
Reference in New Issue