vaapi_encode: Add VP9 support

Message ID 8a611c6c-90ea-8a2e-8964-bd9dca6c808c@jkqxz.net
State Deferred
Headers show

Commit Message

Mark Thompson Oct. 1, 2016, 9:26 a.m.
---
Tested on Kaby Lake.

CQP (well, C q_idx) and CBR mode implemented.  Some vestigial B-frame-alike support, but not actually working: it will need some more thought about how to construct the superframes from the output buffers.


 configure                     |   3 +
 libavcodec/Makefile           |   1 +
 libavcodec/allcodecs.c        |   1 +
 libavcodec/vaapi_encode_vp9.c | 290 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 295 insertions(+)
 create mode 100644 libavcodec/vaapi_encode_vp9.c

Comments

Anton Khirnov Oct. 1, 2016, 1:48 p.m. | #1
Quoting Mark Thompson (2016-10-01 11:26:09)
> ---
> Tested on Kaby Lake.

Looks simple enough to be ok. I guess it also deserves a changelog
entry.

> 
> CQP (well, C q_idx) and CBR mode implemented.  Some vestigial B-frame-alike support, but not actually working: it will need some more thought about how to construct the superframes from the output buffers.

A bitstream filter?
James Almer Oct. 2, 2016, 4:44 a.m. | #2
On 10/1/2016 10:48 AM, Anton Khirnov wrote:
> Quoting Mark Thompson (2016-10-01 11:26:09)
>> ---
>> Tested on Kaby Lake.
> 
> Looks simple enough to be ok. I guess it also deserves a changelog
> entry.
> 
>>
>> CQP (well, C q_idx) and CBR mode implemented.  Some vestigial B-frame-alike support, but not actually working: it will need some more thought about how to construct the superframes from the output buffers.
> 
> A bitstream filter?

Yes, and it's already been written.

https://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=2e6636aa87303d37b112e79f093ca39500f92364
https://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=af9cac1be1750ecc0e12c6788a3aeed1f1a778be
https://git.videolan.org/?p=ffmpeg.git;a=blobdiff;f=libavcodec/vp9_superframe_bsf.c;h=b686adbe1673f564d252a30cff11c5895a9a3b55;hp=d4a61eea0411e75438a46b13d5b798d42cab5a8d;hb=af9cac1be1750ecc0e12c6788a3aeed1f1a778be;hpb=6d160afab2fa8d3bfb216fee96d3537ffc9e86e8
Mark Thompson Oct. 2, 2016, 7:13 a.m. | #3
On 02/10/16 05:44, James Almer wrote:
> On 10/1/2016 10:48 AM, Anton Khirnov wrote:
>> Quoting Mark Thompson (2016-10-01 11:26:09)
>>> ---
>>> Tested on Kaby Lake.
>>
>> Looks simple enough to be ok. I guess it also deserves a changelog
>> entry.
>>
>>>
>>> CQP (well, C q_idx) and CBR mode implemented.  Some vestigial B-frame-alike support, but not actually working: it will need some more thought about how to construct the superframes from the output buffers.
>>
>> A bitstream filter?
> 
> Yes, and it's already been written.
> 
> https://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=2e6636aa87303d37b112e79f093ca39500f92364
> https://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=af9cac1be1750ecc0e12c6788a3aeed1f1a778be
> https://git.videolan.org/?p=ffmpeg.git;a=blobdiff;f=libavcodec/vp9_superframe_bsf.c;h=b686adbe1673f564d252a30cff11c5895a9a3b55;hp=d4a61eea0411e75438a46b13d5b798d42cab5a8d;hb=af9cac1be1750ecc0e12c6788a3aeed1f1a778be;hpb=6d160afab2fa8d3bfb216fee96d3537ffc9e86e8

It isn't quite sufficient to deal with what we get from the encoder here.

We don't just want to merge superframes where anything lacks the show_frame flag, we also want to insert the show_existing_frame packet to match where the frame should actually be.

It could work if the encoder added the reordered packets itself, but if it did that it would be trivial to produce the right output directly anyway.

- Mark

Patch

diff --git a/configure b/configure
index 3c416da..1b994f2 100755
--- a/configure
+++ b/configure
@@ -2207,6 +2207,8 @@  mpeg2_qsv_decoder_deps="libmfx"
 mpeg2_qsv_decoder_select="qsvdec mpeg2_qsv_hwaccel mpegvideo_parser"
 mpeg2_qsv_encoder_deps="libmfx"
 mpeg2_qsv_encoder_select="qsvenc"
+vp9_vaapi_encoder_deps="VAEncPictureParameterBufferVP9"
+vp9_vaapi_encoder_select="vaapi_encode"

 nvenc_h264_encoder_deps="nvenc"
 nvenc_hevc_encoder_deps="nvenc"
@@ -4535,6 +4537,7 @@  check_type "va/va.h va/va_vpp.h" "VAProcPipelineParameterBuffer"
 check_type "va/va.h va/va_enc_h264.h" "VAEncPictureParameterBufferH264"
 check_type "va/va.h va/va_enc_hevc.h" "VAEncPictureParameterBufferHEVC"
 check_type "va/va.h va/va_enc_jpeg.h" "VAEncPictureParameterBufferJPEG"
+check_type "va/va.h va/va_enc_vp9.h"  "VAEncPictureParameterBufferVP9"

 check_type "vdpau/vdpau.h" "VdpPictureInfoHEVC"

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index bec461b..00870df 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -477,6 +477,7 @@  OBJS-$(CONFIG_VP7_DECODER)             += vp8.o vp56rac.o
 OBJS-$(CONFIG_VP8_DECODER)             += vp8.o vp56rac.o
 OBJS-$(CONFIG_VP9_DECODER)             += vp9.o vp9data.o vp9dsp.o \
                                           vp9block.o vp9prob.o vp9mvs.o vp56rac.o
+OBJS-$(CONFIG_VP9_VAAPI_ENCODER)       += vaapi_encode_vp9.o
 OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
 OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o
 OBJS-$(CONFIG_WEBP_DECODER)            += webp.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 41af38e..f18fffa 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -505,6 +505,7 @@  void avcodec_register_all(void)
     REGISTER_ENCODER(NVENC_H264,        nvenc_h264);
     REGISTER_ENCODER(NVENC_HEVC,        nvenc_hevc);
 #endif
+    REGISTER_ENCODER(VP9_VAAPI,         vp9_vaapi);

     /* parsers */
     REGISTER_PARSER(AAC,                aac);
diff --git a/libavcodec/vaapi_encode_vp9.c b/libavcodec/vaapi_encode_vp9.c
new file mode 100644
index 0000000..ecb6693
--- /dev/null
+++ b/libavcodec/vaapi_encode_vp9.c
@@ -0,0 +1,290 @@ 
+/*
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <va/va.h>
+#include <va/va_enc_vp9.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/common.h"
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixfmt.h"
+
+#include "avcodec.h"
+#include "internal.h"
+#include "vaapi_encode.h"
+
+
+typedef struct VAAPIEncodeVP9Context {
+    int q_idx_idr;
+    int q_idx_p;
+    int q_idx_b;
+
+    struct {
+        VAEncMiscParameterBuffer misc;
+        VAEncMiscParameterFrameRate fr;
+    } fr_params;
+} VAAPIEncodeVP9Context;
+
+typedef struct VAAPIEncodeVP9Options {
+    int q_idx;
+    int loop_filter_level;
+    int loop_filter_sharpness;
+} VAAPIEncodeVP9Options;
+
+
+#define vseq_var(name)     vseq->name, name
+#define vseq_field(name)   vseq->seq_fields.bits.name, name
+#define vpic_var(name)     vpic->name, name
+#define vpic_field(name)   vpic->pic_fields.bits.name, name
+
+
+static int vaapi_encode_vp9_init_sequence_params(AVCodecContext *avctx)
+{
+    VAAPIEncodeContext                *ctx = avctx->priv_data;
+    VAEncSequenceParameterBufferVP9  *vseq = ctx->codec_sequence_params;
+    VAEncPictureParameterBufferVP9   *vpic = ctx->codec_picture_params;
+
+    vseq->max_frame_width  = avctx->width;
+    vseq->max_frame_height = avctx->height;
+
+    vseq->kf_auto = 0;
+
+    if (!(ctx->va_rc_mode & VA_RC_CQP)) {
+        vseq->bits_per_second = avctx->bit_rate;
+        vseq->intra_period    = avctx->gop_size;
+    }
+
+    vpic->frame_width_src  = avctx->width;
+    vpic->frame_height_src = avctx->height;
+    vpic->frame_width_dst  = avctx->width;
+    vpic->frame_height_dst = avctx->height;
+
+    return 0;
+}
+
+static int vaapi_encode_vp9_init_picture_params(AVCodecContext *avctx,
+                                                VAAPIEncodePicture *pic)
+{
+    VAAPIEncodeContext              *ctx = avctx->priv_data;
+    VAEncPictureParameterBufferVP9 *vpic = pic->codec_picture_params;
+    VAAPIEncodeVP9Context          *priv = ctx->priv_data;
+    VAAPIEncodeVP9Options           *opt = ctx->codec_options;
+    int i;
+
+    vpic->reconstructed_frame = pic->recon_surface;
+    vpic->coded_buf = pic->output_buffer;
+
+    switch (pic->type) {
+    case PICTURE_TYPE_IDR:
+        av_assert0(pic->nb_refs == 0);
+        vpic->ref_flags.bits.force_kf = 1;
+        vpic->refresh_frame_flags = 0x01;
+        break;
+    case PICTURE_TYPE_P:
+        av_assert0(pic->nb_refs == 1);
+        vpic->ref_flags.bits.ref_frame_ctrl_l0  = 1;
+        vpic->ref_flags.bits.ref_last_idx       = 0;
+        vpic->ref_flags.bits.ref_last_sign_bias = 1;
+        vpic->refresh_frame_flags = 0x01;
+        break;
+    case PICTURE_TYPE_B:
+        av_assert0(0 && "B-frames not supported");
+        break;
+    default:
+        av_assert0(0 && "invalid picture type");
+    }
+
+    if (pic->refs[0])
+        vpic->reference_frames[0] = pic->refs[0]->recon_surface;
+    if (pic->refs[1])
+        vpic->reference_frames[1] = pic->refs[1]->recon_surface;
+    for (i = pic->nb_refs;
+         i < FF_ARRAY_ELEMS(vpic->reference_frames); i++)
+        vpic->reference_frames[i] = VA_INVALID_SURFACE;
+
+    vpic->pic_flags.bits.frame_type = (pic->type != PICTURE_TYPE_IDR);
+    vpic->pic_flags.bits.show_frame = 1;
+
+    if (pic->type == PICTURE_TYPE_IDR)
+        vpic->luma_ac_qindex     = priv->q_idx_idr;
+    else if (pic->type == PICTURE_TYPE_P)
+        vpic->luma_ac_qindex     = priv->q_idx_p;
+    else
+        vpic->luma_ac_qindex     = priv->q_idx_b;
+    vpic->luma_dc_qindex_delta   = 0;
+    vpic->chroma_ac_qindex_delta = 0;
+    vpic->chroma_dc_qindex_delta = 0;
+
+    vpic->filter_level    = opt->loop_filter_level;
+    vpic->sharpness_level = opt->loop_filter_sharpness;
+
+    return 0;
+}
+
+static av_cold int vaapi_encode_vp9_configure(AVCodecContext *avctx)
+{
+    VAAPIEncodeContext     *ctx = avctx->priv_data;
+    VAAPIEncodeVP9Context *priv = ctx->priv_data;
+    VAAPIEncodeVP9Options  *opt = ctx->codec_options;
+
+    priv->q_idx_p = opt->q_idx;
+    if (avctx->i_quant_factor > 0.0)
+        priv->q_idx_idr = (int)((priv->q_idx_p * avctx->i_quant_factor +
+                                 avctx->i_quant_offset) + 0.5);
+    else
+        priv->q_idx_idr = priv->q_idx_p;
+    if (avctx->b_quant_factor > 0.0)
+        priv->q_idx_b = (int)((priv->q_idx_p * avctx->b_quant_factor +
+                               avctx->b_quant_offset) + 0.5);
+    else
+        priv->q_idx_b = priv->q_idx_p;
+
+    if (!(ctx->va_rc_mode & VA_RC_CQP)) {
+        unsigned int num, den;
+        // Framerate isn't supplied inside the parameter structures,
+        // so we need to have supply it separately here.  VAAPI
+        // specifies that this can be fractional, but that isn't
+        // actually implemented in the i965 driver so just give it
+        // the best integer we can make here.
+        if (avctx->framerate.num > 0 && avctx->framerate.den > 0) {
+            num = avctx->framerate.num;
+            den = avctx->framerate.den;
+        } else {
+            num = avctx->time_base.den;
+            den = avctx->time_base.num;
+        }
+        if (num < den)
+            num = den;
+
+        priv->fr_params.misc.type = VAEncMiscParameterTypeFrameRate;
+        priv->fr_params.fr.framerate = num / den;
+
+        ctx->global_params[ctx->nb_global_params] =
+            &priv->fr_params.misc;
+        ctx->global_params_size[ctx->nb_global_params++] =
+            sizeof(priv->fr_params);
+    }
+
+    return 0;
+}
+
+static const VAAPIEncodeType vaapi_encode_type_vp9 = {
+    .configure             = &vaapi_encode_vp9_configure,
+
+    .priv_data_size        = sizeof(VAAPIEncodeVP9Context),
+
+    .sequence_params_size  = sizeof(VAEncSequenceParameterBufferVP9),
+    .init_sequence_params  = &vaapi_encode_vp9_init_sequence_params,
+
+    .picture_params_size   = sizeof(VAEncPictureParameterBufferVP9),
+    .init_picture_params   = &vaapi_encode_vp9_init_picture_params,
+};
+
+static av_cold int vaapi_encode_vp9_init(AVCodecContext *avctx)
+{
+    VAAPIEncodeContext *ctx = avctx->priv_data;
+
+    ctx->codec = &vaapi_encode_type_vp9;
+
+    switch (avctx->profile) {
+    case FF_PROFILE_VP9_0:
+    case FF_PROFILE_UNKNOWN:
+        ctx->va_profile = VAProfileVP9Profile0;
+        ctx->va_rt_format = VA_RT_FORMAT_YUV420;
+        break;
+    case FF_PROFILE_VP9_1:
+        av_log(avctx, AV_LOG_ERROR, "VP9 profile 1 is not "
+               "supported.\n");
+        return AVERROR_PATCHWELCOME;
+    case FF_PROFILE_VP9_2:
+        ctx->va_profile = VAProfileVP9Profile2;
+        ctx->va_rt_format = VA_RT_FORMAT_YUV420_10BPP;
+        break;
+    case FF_PROFILE_VP9_3:
+        av_log(avctx, AV_LOG_ERROR, "VP9 profile 3 is not "
+               "supported.\n");
+        return AVERROR_PATCHWELCOME;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Unknown VP9 profile %d.\n",
+               avctx->profile);
+        return AVERROR(EINVAL);
+    }
+    ctx->va_entrypoint = VAEntrypointEncSlice;
+
+    if (avctx->bit_rate > 0)
+        ctx->va_rc_mode = VA_RC_CBR;
+    else
+        ctx->va_rc_mode = VA_RC_CQP;
+
+    // Packed headers are not currently supported.
+    ctx->va_packed_headers = 0;
+
+    // Surfaces must be aligned to superblock boundaries.
+    ctx->surface_width  = FFALIGN(avctx->width,  64);
+    ctx->surface_height = FFALIGN(avctx->height, 64);
+
+    return ff_vaapi_encode_init(avctx);
+}
+
+#define OFFSET(x) (offsetof(VAAPIEncodeContext, codec_options_data) + \
+                   offsetof(VAAPIEncodeVP9Options, x))
+#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM)
+static const AVOption vaapi_encode_vp9_options[] = {
+    { "q_idx", "Constant Q-index (for P-frames; scaled by qfactor/qoffset for I/B)",
+      OFFSET(q_idx), AV_OPT_TYPE_INT, { .i64 = 50 }, 0, 255, FLAGS },
+    { "loop_filter_level", "Loop filter level",
+      OFFSET(loop_filter_level), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, 63, FLAGS },
+    { "loop_filter_sharpness", "Loop filter sharpness",
+      OFFSET(loop_filter_sharpness), AV_OPT_TYPE_INT, { .i64 = 4 }, 0, 15, FLAGS },
+    { NULL },
+};
+
+static const AVCodecDefault vaapi_encode_vp9_defaults[] = {
+    { "profile",        "0"   },
+    { "b",              "0"   },
+    { "bf",             "0"   },
+    { "g",              "120" },
+    { NULL },
+};
+
+static const AVClass vaapi_encode_vp9_class = {
+    .class_name = "vp9_vaapi",
+    .item_name  = av_default_item_name,
+    .option     = vaapi_encode_vp9_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVCodec ff_vp9_vaapi_encoder = {
+    .name           = "vp9_vaapi",
+    .long_name      = NULL_IF_CONFIG_SMALL("VP9 (VAAPI)"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_VP9,
+    .priv_data_size = (sizeof(VAAPIEncodeContext) +
+                       sizeof(VAAPIEncodeVP9Options)),
+    .init           = &vaapi_encode_vp9_init,
+    .encode2        = &ff_vaapi_encode2,
+    .close          = &ff_vaapi_encode_close,
+    .priv_class     = &vaapi_encode_vp9_class,
+    .capabilities   = AV_CODEC_CAP_DELAY,
+    .defaults       = vaapi_encode_vp9_defaults,
+    .pix_fmts = (const enum AVPixelFormat[]) {
+        AV_PIX_FMT_VAAPI,
+        AV_PIX_FMT_NONE,
+    },
+};