avformat/movenc: creating producer reference time (PRFT) box

The producer reference time box supplies relative wall-clock times
at which movie fragments, or files containing movie fragments
(such as segments) were produced.
The box is mainly useful in live streaming use cases. A media player
can parse the box and utilize the time fields to measure and improve
the latency during real time playout.
This commit is contained in:
Vishwanath Dixit
2018-05-07 15:27:51 +05:30
committed by Karthick Jeyapal
parent f09635f2a2
commit 5717cd80dc
3 changed files with 69 additions and 0 deletions

View File

@@ -1344,6 +1344,16 @@ be negative. This enables the initial sample to have DTS/CTS of zero, and
reduces the need for edit lists for some cases such as video tracks with reduces the need for edit lists for some cases such as video tracks with
B-frames. Additionally, eases conformance with the DASH-IF interoperability B-frames. Additionally, eases conformance with the DASH-IF interoperability
guidelines. guidelines.
@item -write_prft
Write producer time reference box (PRFT) with a specified time source for the
NTP field in the PRFT box. Set value as @samp{wallclock} to specify timesource
as wallclock time and @samp{pts} to specify timesource as input packets' PTS
values.
Setting value to @samp{pts} is applicable only for a live encoding use case,
where PTS values are set as as wallclock time at the source. For example, an
encoding use case with decklink capture source where @option{video_pts} and
@option{audio_pts} are set to @samp{abs_wallclock}.
@end table @end table
@subsection Example @subsection Example

View File

@@ -98,6 +98,9 @@ static const AVOption options[] = {
{ "encryption_kid", "The media encryption key identifier (hex)", offsetof(MOVMuxContext, encryption_kid), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM }, { "encryption_kid", "The media encryption key identifier (hex)", offsetof(MOVMuxContext, encryption_kid), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM },
{ "use_stream_ids_as_track_ids", "use stream ids as track ids", offsetof(MOVMuxContext, use_stream_ids_as_track_ids), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "use_stream_ids_as_track_ids", "use stream ids as track ids", offsetof(MOVMuxContext, use_stream_ids_as_track_ids), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
{ "write_tmcd", "force or disable writing tmcd", offsetof(MOVMuxContext, write_tmcd), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "write_tmcd", "force or disable writing tmcd", offsetof(MOVMuxContext, write_tmcd), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM},
{ "write_prft", "Write producer reference time box with specified time source", offsetof(MOVMuxContext, write_prft), AV_OPT_TYPE_INT, {.i64 = MOV_PRFT_NONE}, 0, MOV_PRFT_NB-1, AV_OPT_FLAG_ENCODING_PARAM, "prft"},
{ "wallclock", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MOV_PRFT_SRC_WALLCLOCK}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM, "prft"},
{ "pts", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MOV_PRFT_SRC_PTS}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM, "prft"},
{ NULL }, { NULL },
}; };
@@ -4514,6 +4517,49 @@ static int mov_write_sidx_tags(AVIOContext *pb, MOVMuxContext *mov,
return 0; return 0;
} }
static int mov_write_prft_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks)
{
int64_t pos = avio_tell(pb), pts_us, ntp_ts;
MOVTrack *first_track;
/* PRFT should be associated with at most one track. So, choosing only the
* first track. */
if (tracks > 0)
return 0;
first_track = &(mov->tracks[0]);
if (!first_track->entry) {
av_log(mov->fc, AV_LOG_WARNING, "Unable to write PRFT, no entries in the track\n");
return 0;
}
if (first_track->cluster[0].pts == AV_NOPTS_VALUE) {
av_log(mov->fc, AV_LOG_WARNING, "Unable to write PRFT, first PTS is invalid\n");
return 0;
}
if (mov->write_prft == MOV_PRFT_SRC_WALLCLOCK) {
ntp_ts = ff_get_formatted_ntp_time(ff_ntp_time());
} else if (mov->write_prft == MOV_PRFT_SRC_PTS) {
pts_us = av_rescale_q(first_track->cluster[0].pts,
first_track->st->time_base, AV_TIME_BASE_Q);
ntp_ts = ff_get_formatted_ntp_time(pts_us + NTP_OFFSET_US);
} else {
av_log(mov->fc, AV_LOG_WARNING, "Unsupported PRFT box configuration: %d\n",
mov->write_prft);
return 0;
}
avio_wb32(pb, 0); // Size place holder
ffio_wfourcc(pb, "prft"); // Type
avio_w8(pb, 1); // Version
avio_wb24(pb, 0); // Flags
avio_wb32(pb, first_track->track_id); // reference track ID
avio_wb64(pb, ntp_ts); // NTP time stamp
avio_wb64(pb, first_track->cluster[0].pts); //media time
return update_size(pb, pos);
}
static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks, static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks,
int64_t mdat_size) int64_t mdat_size)
{ {
@@ -4528,6 +4574,9 @@ static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks,
if (mov->flags & FF_MOV_FLAG_DASH && !(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX)) if (mov->flags & FF_MOV_FLAG_DASH && !(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX))
mov_write_sidx_tags(pb, mov, tracks, moof_size + 8 + mdat_size); mov_write_sidx_tags(pb, mov, tracks, moof_size + 8 + mdat_size);
if (mov->write_prft > MOV_PRFT_NONE && mov->write_prft < MOV_PRFT_NB)
mov_write_prft_tag(pb, mov, tracks);
if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX || if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX ||
!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) || !(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) ||
mov->ism_lookahead) { mov->ism_lookahead) {
@@ -5317,6 +5366,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
trk->cluster[trk->entry].size = size; trk->cluster[trk->entry].size = size;
trk->cluster[trk->entry].entries = samples_in_chunk; trk->cluster[trk->entry].entries = samples_in_chunk;
trk->cluster[trk->entry].dts = pkt->dts; trk->cluster[trk->entry].dts = pkt->dts;
trk->cluster[trk->entry].pts = pkt->pts;
if (!trk->entry && trk->start_dts != AV_NOPTS_VALUE) { if (!trk->entry && trk->start_dts != AV_NOPTS_VALUE) {
if (!trk->frag_discont) { if (!trk->frag_discont) {
/* First packet of a new fragment. We already wrote the duration /* First packet of a new fragment. We already wrote the duration

View File

@@ -46,6 +46,7 @@
typedef struct MOVIentry { typedef struct MOVIentry {
uint64_t pos; uint64_t pos;
int64_t dts; int64_t dts;
int64_t pts;
unsigned int size; unsigned int size;
unsigned int samples_in_chunk; unsigned int samples_in_chunk;
unsigned int chunkNum; ///< Chunk number if the current entry is a chunk start otherwise 0 unsigned int chunkNum; ///< Chunk number if the current entry is a chunk start otherwise 0
@@ -169,6 +170,13 @@ typedef enum {
MOV_ENC_CENC_AES_CTR, MOV_ENC_CENC_AES_CTR,
} MOVEncryptionScheme; } MOVEncryptionScheme;
typedef enum {
MOV_PRFT_NONE = 0,
MOV_PRFT_SRC_WALLCLOCK,
MOV_PRFT_SRC_PTS,
MOV_PRFT_NB
} MOVPrftBox;
typedef struct MOVMuxContext { typedef struct MOVMuxContext {
const AVClass *av_class; const AVClass *av_class;
int mode; int mode;
@@ -224,6 +232,7 @@ typedef struct MOVMuxContext {
int use_stream_ids_as_track_ids; int use_stream_ids_as_track_ids;
int track_ids_ok; int track_ids_ok;
int write_tmcd; int write_tmcd;
MOVPrftBox write_prft;
} MOVMuxContext; } MOVMuxContext;
#define FF_MOV_FLAG_RTP_HINT (1 << 0) #define FF_MOV_FLAG_RTP_HINT (1 << 0)