diff mbox

[FFmpeg-devel,v2,2/3] avformat/movenc: creating producer reference time (PRFT) box

Message ID 1525687071-5954-1-git-send-email-vdixit@akamai.com
State Accepted
Commit 5717cd80dcb825efcbcb0b936a02b755cd2d5f62
Headers show

Commit Message

Dixit, Vishwanath May 7, 2018, 9:57 a.m. UTC
From: Vishwanath Dixit <vdixit@akamai.com>

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.
---
 doc/muxers.texi      | 10 ++++++++++
 libavformat/movenc.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/movenc.h |  9 +++++++++
 3 files changed, 69 insertions(+)

Comments

Jeyapal, Karthick May 29, 2018, 4:58 a.m. UTC | #1
Pushed Patchset.

Regards,
Karthick

On 5/7/18 3:27 PM, vdixit@akamai.com wrote:
> From: Vishwanath Dixit <vdixit@akamai.com>

>

> 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.

> ---

>  doc/muxers.texi      | 10 ++++++++++

>  libavformat/movenc.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++

>  libavformat/movenc.h |  9 +++++++++

>  3 files changed, 69 insertions(+)

>

> diff --git a/doc/muxers.texi b/doc/muxers.texi

> index 6f03bba..db81901 100644

> --- a/doc/muxers.texi

> +++ b/doc/muxers.texi

> @@ -1329,6 +1329,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

>  B-frames. Additionally, eases conformance with the DASH-IF interoperability

>  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

>  

>  @subsection Example

> diff --git a/libavformat/movenc.c b/libavformat/movenc.c

> index 0b44fd6..7e616e8 100644

> --- a/libavformat/movenc.c

> +++ b/libavformat/movenc.c

> @@ -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 },

>      { "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_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 },

>  };

>  

> @@ -4514,6 +4517,49 @@ static int mov_write_sidx_tags(AVIOContext *pb, MOVMuxContext *mov,

>      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,

>                                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))

>          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 ||

>          !(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) ||

>          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].entries          = samples_in_chunk;

>      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->frag_discont) {

>              /* First packet of a new fragment. We already wrote the duration

> diff --git a/libavformat/movenc.h b/libavformat/movenc.h

> index c9b4072..f2967bf 100644

> --- a/libavformat/movenc.h

> +++ b/libavformat/movenc.h

> @@ -46,6 +46,7 @@

>  typedef struct MOVIentry {

>      uint64_t     pos;

>      int64_t      dts;

> +    int64_t      pts;

>      unsigned int size;

>      unsigned int samples_in_chunk;

>      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,

>  } MOVEncryptionScheme;

>  

> +typedef enum {

> +    MOV_PRFT_NONE = 0,

> +    MOV_PRFT_SRC_WALLCLOCK,

> +    MOV_PRFT_SRC_PTS,

> +    MOV_PRFT_NB

> +} MOVPrftBox;

> +

>  typedef struct MOVMuxContext {

>      const AVClass *av_class;

>      int     mode;

> @@ -224,6 +232,7 @@ typedef struct MOVMuxContext {

>      int use_stream_ids_as_track_ids;

>      int track_ids_ok;

>      int write_tmcd;

> +    MOVPrftBox write_prft;

>  } MOVMuxContext;

>  

>  #define FF_MOV_FLAG_RTP_HINT              (1 <<  0)
diff mbox

Patch

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 6f03bba..db81901 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -1329,6 +1329,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
 B-frames. Additionally, eases conformance with the DASH-IF interoperability
 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
 
 @subsection Example
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 0b44fd6..7e616e8 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -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 },
     { "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_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 },
 };
 
@@ -4514,6 +4517,49 @@  static int mov_write_sidx_tags(AVIOContext *pb, MOVMuxContext *mov,
     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,
                               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))
         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 ||
         !(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) ||
         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].entries          = samples_in_chunk;
     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->frag_discont) {
             /* First packet of a new fragment. We already wrote the duration
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index c9b4072..f2967bf 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -46,6 +46,7 @@ 
 typedef struct MOVIentry {
     uint64_t     pos;
     int64_t      dts;
+    int64_t      pts;
     unsigned int size;
     unsigned int samples_in_chunk;
     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,
 } MOVEncryptionScheme;
 
+typedef enum {
+    MOV_PRFT_NONE = 0,
+    MOV_PRFT_SRC_WALLCLOCK,
+    MOV_PRFT_SRC_PTS,
+    MOV_PRFT_NB
+} MOVPrftBox;
+
 typedef struct MOVMuxContext {
     const AVClass *av_class;
     int     mode;
@@ -224,6 +232,7 @@  typedef struct MOVMuxContext {
     int use_stream_ids_as_track_ids;
     int track_ids_ok;
     int write_tmcd;
+    MOVPrftBox write_prft;
 } MOVMuxContext;
 
 #define FF_MOV_FLAG_RTP_HINT              (1 <<  0)