[FFmpeg-devel,v2,09/11] vaapi_encode: Add support for VFR mode

Submitted by Mark Thompson on Jan. 27, 2019, 11:47 p.m.

Details

Message ID 20190127234707.27689-9-sw@jkqxz.net
State New
Headers show

Commit Message

Mark Thompson Jan. 27, 2019, 11:47 p.m.
Use the frame-skip feature to maintain a specified framerate from the
point of view of the driver.
---
 doc/encoders.texi         |   7 +++
 libavcodec/vaapi_encode.c | 116 +++++++++++++++++++++++++++++++++++---
 libavcodec/vaapi_encode.h |  18 +++++-
 3 files changed, 132 insertions(+), 9 deletions(-)

Comments

Michael Niedermayer Jan. 28, 2019, 8:45 p.m.
On Sun, Jan 27, 2019 at 11:47:05PM +0000, Mark Thompson wrote:
> Use the frame-skip feature to maintain a specified framerate from the
> point of view of the driver.
> ---
>  doc/encoders.texi         |   7 +++
>  libavcodec/vaapi_encode.c | 116 +++++++++++++++++++++++++++++++++++---
>  libavcodec/vaapi_encode.h |  18 +++++-
>  3 files changed, 132 insertions(+), 9 deletions(-)

seems to break build


CC	libavcodec/vaapi_encode.o
libavcodec/vaapi_encode.c: In function ‘vaapi_encode_init_framerate’:
libavcodec/vaapi_encode.c:1718:12: error: ‘VAAPIEncodeContext’ has no member named ‘max_fps’
     if (ctx->max_fps.num > 0 && ctx->max_fps.den > 0) {
            ^
libavcodec/vaapi_encode.c:1718:36: error: ‘VAAPIEncodeContext’ has no member named ‘max_fps’
     if (ctx->max_fps.num > 0 && ctx->max_fps.den > 0) {
                                    ^
make: *** [libavcodec/vaapi_encode.o] Error 1
make: Target `all' not remade because of errors.

[...]
Mark Thompson Feb. 4, 2019, 9:28 a.m.
On 28/01/2019 20:45, Michael Niedermayer wrote:
> On Sun, Jan 27, 2019 at 11:47:05PM +0000, Mark Thompson wrote:
>> Use the frame-skip feature to maintain a specified framerate from the
>> point of view of the driver.
>> ---
>>  doc/encoders.texi         |   7 +++
>>  libavcodec/vaapi_encode.c | 116 +++++++++++++++++++++++++++++++++++---
>>  libavcodec/vaapi_encode.h |  18 +++++-
>>  3 files changed, 132 insertions(+), 9 deletions(-)
> 
> seems to break build
> 
> 
> CC	libavcodec/vaapi_encode.o
> libavcodec/vaapi_encode.c: In function ‘vaapi_encode_init_framerate’:
> libavcodec/vaapi_encode.c:1718:12: error: ‘VAAPIEncodeContext’ has no member named ‘max_fps’
>      if (ctx->max_fps.num > 0 && ctx->max_fps.den > 0) {
>             ^
> libavcodec/vaapi_encode.c:1718:36: error: ‘VAAPIEncodeContext’ has no member named ‘max_fps’
>      if (ctx->max_fps.num > 0 && ctx->max_fps.den > 0) {
>                                     ^
> make: *** [libavcodec/vaapi_encode.o] Error 1
> make: Target `all' not remade because of errors.

Urgh, not retested on older versions after late refactoring which changed the name of a variable.

Fixed.

Thanks,

- Mark

Patch hide | download patch | download mbox

diff --git a/doc/encoders.texi b/doc/encoders.texi
index 29625ba07c..b8322c4e95 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -2848,6 +2848,13 @@  Quality-defined variable-bitrate.
 Average variable bitrate.
 @end table
 
+@item max_fps
+Enable VFR encoding with this maximum framerate.  This is implemented by
+indicating to the driver that frames have been skipped at some locations in
+the stream, and requires driver support.  The minimum interval between frames
+must not be smaller than this, and there may be problems if the maximum
+interval is more than a small multiple of it.
+
 @end table
 
 Each encoder also has its own specific options:
diff --git a/libavcodec/vaapi_encode.c b/libavcodec/vaapi_encode.c
index d0e101b118..e5491fcd03 100644
--- a/libavcodec/vaapi_encode.c
+++ b/libavcodec/vaapi_encode.c
@@ -412,6 +412,29 @@  static int vaapi_encode_issue(AVCodecContext *avctx,
         }
     }
 
+#if VA_CHECK_VERSION(0, 40, 0)
+    if (ctx->vfr_mode && pic->frame_skips > 0) {
+        struct {
+            VAEncMiscParameterBuffer misc;
+            VAEncMiscParameterSkipFrame skip;
+        } param = {
+            .misc = {
+                .type = VAEncMiscParameterTypeSkipFrame,
+            },
+            .skip = {
+                .skip_frame_flag = 1,
+                .num_skip_frames = pic->frame_skips,
+            },
+        };
+
+        err = vaapi_encode_make_param_buffer(avctx, pic,
+                                             VAEncMiscParameterBufferType,
+                                             (char*)&param, sizeof(param));
+        if (err < 0)
+            goto fail;
+    }
+#endif
+
     vas = vaBeginPicture(ctx->hwctx->display, ctx->va_context,
                          pic->input_surface);
     if (vas != VA_STATUS_SUCCESS) {
@@ -942,6 +965,36 @@  int ff_vaapi_encode_send_frame(AVCodecContext *avctx, const AVFrame *frame)
         pic->input_surface = (VASurfaceID)(uintptr_t)frame->data[3];
         pic->pts = frame->pts;
 
+        if (ctx->vfr_mode && ctx->input_order > 0) {
+            if (frame->pts < ctx->prev_pts) {
+                av_log(avctx, AV_LOG_WARNING, "Timestamp discontinuity "
+                       "(backward step: %"PRId64" -> %"PRId64"): "
+                       "VFR mode reset.\n", ctx->prev_pts, frame->pts);
+                ctx->ticks_outstanding = av_make_q(0, 1);
+            } else {
+                AVRational step_ticks, ticks;
+                int ticks_int;
+                step_ticks = av_div_q(av_make_q(frame->pts - ctx->prev_pts, 1),
+                                      ctx->ticks_per_frame);
+                ticks = av_add_q(ctx->ticks_outstanding, step_ticks);
+                ticks_int = ticks.num / ticks.den;
+                if (ticks_int < 1) {
+                    av_log(avctx, AV_LOG_WARNING, "Max FPS exceeded!\n");
+                } else if (ticks_int > 256) {
+                    av_log(avctx, AV_LOG_WARNING, "Timestamp discontinuity "
+                           "(forward step: %"PRId64" -> %"PRId64"): "
+                           "VFR mode reset.\n", ctx->prev_pts, frame->pts);
+                } else {
+                    av_log(avctx, AV_LOG_DEBUG, "Inserting %d frame skips before "
+                           "frame %"PRId64".\n", ticks_int - 1, frame->pts);
+                    pic->frame_skips = ticks_int - 1;
+                }
+                ctx->ticks_outstanding =
+                    av_sub_q(ticks, av_make_q(ticks_int, 1));
+            }
+        }
+        ctx->prev_pts = frame->pts;
+
         if (ctx->input_order == 0)
             ctx->first_pts = pic->pts;
         if (ctx->input_order == ctx->decode_delay)
@@ -1315,7 +1368,6 @@  static av_cold int vaapi_encode_init_rate_control(AVCodecContext *avctx)
     int     rc_quality;
     int64_t hrd_buffer_size;
     int64_t hrd_initial_buffer_fullness;
-    int fr_num, fr_den;
     VAConfigAttrib rc_attr = { VAConfigAttribRateControl };
     VAStatus vas;
     char supported_rc_modes_string[64];
@@ -1607,22 +1659,66 @@  rc_mode_found:
                                       sizeof(ctx->hrd_params));
     }
 
-    if (avctx->framerate.num > 0 && avctx->framerate.den > 0)
-        av_reduce(&fr_num, &fr_den,
-                  avctx->framerate.num, avctx->framerate.den, 65535);
-    else
+    return 0;
+}
+
+static av_cold int vaapi_encode_init_framerate(AVCodecContext *avctx)
+{
+    VAAPIEncodeContext *ctx = avctx->priv_data;
+
+#if VA_CHECK_VERSION(0, 40, 0)
+    int fr_num, fr_den;
+
+    ctx->vfr_mode = ctx->vfr_max_fps.num > 0 && ctx->vfr_max_fps.den > 0;
+    if (ctx->vfr_mode) {
+        VAConfigAttrib attr = { VAConfigAttribEncSkipFrame };
+        VAStatus vas;
+
+        vas = vaGetConfigAttributes(ctx->hwctx->display,
+                                    ctx->va_profile, ctx->va_entrypoint,
+                                    &attr, 1);
+        if (vas != VA_STATUS_SUCCESS) {
+            av_log(avctx, AV_LOG_ERROR, "Failed to query skip-frame "
+                   "config attribute: %d (%s).\n", vas, vaErrorStr(vas));
+            return AVERROR_EXTERNAL;
+        }
+        if (attr.value == VA_ATTRIB_NOT_SUPPORTED ||
+            attr.value == 0) {
+            av_log(avctx, AV_LOG_ERROR, "Skip-frame attribute is not "
+                   "supported by driver: VFR mode cannot be used.\n");
+            return AVERROR(EINVAL);
+        }
+
         av_reduce(&fr_num, &fr_den,
-                  avctx->time_base.den, avctx->time_base.num, 65535);
+                  ctx->vfr_max_fps.num, ctx->vfr_max_fps.den, 65535);
+
+        ctx->ticks_per_frame = av_inv_q(av_mul_q(avctx->time_base,
+                                                 av_make_q(fr_num, fr_den)));
+        ctx->ticks_outstanding = av_make_q(0, 1);
+    } else {
+        if (avctx->framerate.num > 0 && avctx->framerate.den > 0)
+            av_reduce(&fr_num, &fr_den,
+                      avctx->framerate.num, avctx->framerate.den, 65535);
+        else
+            av_reduce(&fr_num, &fr_den,
+                      avctx->time_base.den, avctx->time_base.num, 65535);
+    }
 
-    av_log(avctx, AV_LOG_VERBOSE, "RC framerate: %d/%d (%.2f fps).\n",
+    av_log(avctx, AV_LOG_VERBOSE, "RC framerate (%s mode): "
+           "%d/%d (%.2f fps).\n", ctx->vfr_mode ? "VFR" : "CFR",
            fr_num, fr_den, (double)fr_num / fr_den);
 
     ctx->fr_params.misc.type = VAEncMiscParameterTypeFrameRate;
     ctx->fr_params.fr.framerate = (unsigned int)fr_den << 16 | fr_num;
 
-#if VA_CHECK_VERSION(0, 40, 0)
     vaapi_encode_add_global_param(avctx, &ctx->fr_params.misc,
                                   sizeof(ctx->fr_params));
+
+#else
+    if (ctx->max_fps.num > 0 && ctx->max_fps.den > 0) {
+        av_log(avctx, AV_LOG_WARNING, "Variable framerate is "
+               "not supported with this VAAPI version.\n");
+    }
 #endif
 
     return 0;
@@ -2075,6 +2171,10 @@  av_cold int ff_vaapi_encode_init(AVCodecContext *avctx)
     if (err < 0)
         goto fail;
 
+    err = vaapi_encode_init_framerate(avctx);
+    if (err < 0)
+        goto fail;
+
     err = vaapi_encode_init_gop_structure(avctx);
     if (err < 0)
         goto fail;
diff --git a/libavcodec/vaapi_encode.h b/libavcodec/vaapi_encode.h
index 44a8db566e..054cdaafbb 100644
--- a/libavcodec/vaapi_encode.h
+++ b/libavcodec/vaapi_encode.h
@@ -69,6 +69,9 @@  typedef struct VAAPIEncodePicture {
     int64_t         pts;
     int             force_idr;
 
+    // Number of frame-skips to insert before this frame in VFR mode.
+    int             frame_skips;
+
     int             type;
     int             b_depth;
     int             encode_issued;
@@ -184,6 +187,10 @@  typedef struct VAAPIEncodeContext {
     // (Forces CQP mode when set, overriding everything else.)
     int             explicit_qp;
 
+    // When set, enable skip-frame VFR mode with this maximum
+    // framerate.
+    AVRational      vfr_max_fps;
+
     // Desired packed headers.
     unsigned int    desired_packed_headers;
 
@@ -297,6 +304,12 @@  typedef struct VAAPIEncodeContext {
     int64_t         dts_pts_diff;
     int64_t         ts_ring[MAX_REORDER_DELAY * 3];
 
+    // VFR state.
+    int             vfr_mode;
+    AVRational      ticks_per_frame;
+    AVRational      ticks_outstanding;
+    int64_t         prev_pts;
+
     // Slice structure.
     int slice_block_rows;
     int slice_block_cols;
@@ -445,7 +458,10 @@  int ff_vaapi_encode_close(AVCodecContext *avctx);
     VAAPI_ENCODE_RC_MODE(VBR,  "Variable-bitrate"), \
     VAAPI_ENCODE_RC_MODE(ICQ,  "Intelligent constant-quality"), \
     VAAPI_ENCODE_RC_MODE(QVBR, "Quality-defined variable-bitrate"), \
-    VAAPI_ENCODE_RC_MODE(AVBR, "Average variable-bitrate")
+    VAAPI_ENCODE_RC_MODE(AVBR, "Average variable-bitrate"), \
+    { "max_fps", "Enable VFR mode with this maximum framerate", \
+      OFFSET(common.vfr_max_fps), AV_OPT_TYPE_RATIONAL, \
+      { .dbl = 0.0 }, 0, INT_MAX, FLAGS }
 
 
 #endif /* AVCODEC_VAAPI_ENCODE_H */