[FFmpeg-devel,V6] libavfilter: add transpose_vaapi filter

Submitted by Zachary Zhou on Dec. 18, 2018, 9:54 a.m.

Details

Message ID 20181218095435.16507-1-zachary.zhou@intel.com
State New
Headers show

Commit Message

Zachary Zhou Dec. 18, 2018, 9:54 a.m.
Swap width and height when do clock/cclock rotation
Add reveral/reveral_flip options

ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128
-hwaccel_output_format vaapi -i input.264 -vf "transpose_vaapi=clock_flip"
-c:v h264_vaapi output.h264

Signed-off-by: Zachary Zhou <zachary.zhou@intel.com>
---
 configure                        |   2 +
 libavfilter/Makefile             |   1 +
 libavfilter/allfilters.c         |   1 +
 libavfilter/vf_transpose_vaapi.c | 360 +++++++++++++++++++++++++++++++
 4 files changed, 364 insertions(+)
 create mode 100644 libavfilter/vf_transpose_vaapi.c

Comments

Zhong Li Dec. 19, 2018, 2:08 p.m.
> From: ffmpeg-devel [mailto:ffmpeg-devel-bounces@ffmpeg.org] On Behalf

> Of Zachary Zhou

> Sent: Tuesday, December 18, 2018 5:55 PM

> To: ffmpeg-devel@ffmpeg.org

> Cc: Zhou, Zachary <zachary.zhou@intel.com>

> Subject: [FFmpeg-devel] [PATCH V6] libavfilter: add transpose_vaapi filter

> 

> Swap width and height when do clock/cclock rotation Add

> reveral/reveral_flip options

> 

> ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128

> -hwaccel_output_format vaapi -i input.264 -vf "transpose_vaapi=clock_flip"

> -c:v h264_vaapi output.h264

> 

> Signed-off-by: Zachary Zhou <zachary.zhou@intel.com>


LGTM, will test and apply if nobody against.
Mark Thompson Dec. 20, 2018, 9:46 p.m.
On 18/12/2018 09:54, Zachary Zhou wrote:
> Swap width and height when do clock/cclock rotation
> Add reveral/reveral_flip options
> 
> ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128
> -hwaccel_output_format vaapi -i input.264 -vf "transpose_vaapi=clock_flip"
> -c:v h264_vaapi output.h264
> 
> Signed-off-by: Zachary Zhou <zachary.zhou@intel.com>
> ---
>  configure                        |   2 +
>  libavfilter/Makefile             |   1 +
>  libavfilter/allfilters.c         |   1 +
>  libavfilter/vf_transpose_vaapi.c | 360 +++++++++++++++++++++++++++++++
>  4 files changed, 364 insertions(+)
>  create mode 100644 libavfilter/vf_transpose_vaapi.c
> 
> ...
> diff --git a/libavfilter/vf_transpose_vaapi.c b/libavfilter/vf_transpose_vaapi.c
> new file mode 100644
> index 0000000000..a2bf245da9
> --- /dev/null
> +++ b/libavfilter/vf_transpose_vaapi.c
> @@ -0,0 +1,360 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg 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.
> + *
> + * FFmpeg 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 FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +#include <string.h>
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "internal.h"
> +#include "vaapi_vpp.h"
> +
> +typedef enum {
> +    TRANSPOSE_PT_TYPE_NONE,
> +    TRANSPOSE_PT_TYPE_LANDSCAPE,
> +    TRANSPOSE_PT_TYPE_PORTRAIT,
> +} PassthroughType;
> +
> +enum TransposeDir {
> +    TRANSPOSE_CCLOCK_FLIP,
> +    TRANSPOSE_CLOCK,
> +    TRANSPOSE_CCLOCK,
> +    TRANSPOSE_CLOCK_FLIP,
> +    TRANSPOSE_REVERAL,
> +    TRANSPOSE_REVERAL_FLIP,

"REVERAL"?  I guess that's meant to be "REVERSAL".  I'm not convinced it's quite the right word, though.  REVERSAL_FLIP is standalone as hflip - if you included vflip as well (the missing case from the eight possible transforms, excluding identity) then that name might be better?

Also, please use the enums in transpose.h directly rather than duplicating them.

> +};
> +
> +typedef struct TransposeVAAPIContext {
> +    VAAPIVPPContext vpp_ctx; // must be the first field
> +    int passthrough;         // PassthroughType, landscape passthrough mode enabled
> +    int dir;                 // TransposeDir
> +
> +    int rotation_state;
> +    int mirror_state;
> +} TransposeVAAPIContext;
> +
> +static int transpose_vaapi_build_filter_params(AVFilterContext *avctx)
> +{
> +    VAAPIVPPContext *vpp_ctx   = avctx->priv;
> +    TransposeVAAPIContext *ctx = avctx->priv;
> +    VAStatus vas;
> +    int support_flag;
> +    VAProcPipelineCaps pipeline_caps;
> +
> +    memset(&pipeline_caps, 0, sizeof(pipeline_caps));
> +    vas = vaQueryVideoProcPipelineCaps(vpp_ctx->hwctx->display,
> +                                       vpp_ctx->va_context,
> +                                       NULL, 0,
> +                                       &pipeline_caps);
> +    if (vas != VA_STATUS_SUCCESS) {
> +        av_log(avctx, AV_LOG_ERROR, "Failed to query pipeline "
> +               "caps: %d (%s).\n", vas, vaErrorStr(vas));
> +        return AVERROR(EIO);
> +    }
> +
> +    if (!pipeline_caps.rotation_flags) {

This condition doesn't guarantee that all possibly rotations are supported - <https://github.com/intel/libva/blob/master/va/va_vpp.h#L611-L612>.

The mirror flags also need the same treatment.

> +        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support transpose\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    switch (ctx->dir) {
> +    case TRANSPOSE_CCLOCK_FLIP:
> +        ctx->rotation_state = VA_ROTATION_270;
> +        ctx->mirror_state   = VA_MIRROR_VERTICAL;
> +        break;
> +    case TRANSPOSE_CLOCK:
> +        ctx->rotation_state = VA_ROTATION_90;
> +        ctx->mirror_state   = VA_MIRROR_NONE;
> +        break;
> +    case TRANSPOSE_CCLOCK:
> +        ctx->rotation_state = VA_ROTATION_270;
> +        ctx->mirror_state   = VA_MIRROR_NONE;
> +        break;
> +    case TRANSPOSE_CLOCK_FLIP:
> +        ctx->rotation_state = VA_ROTATION_90;
> +        ctx->mirror_state   = VA_MIRROR_VERTICAL;
> +        break;
> +    case TRANSPOSE_REVERAL:
> +        ctx->rotation_state = VA_ROTATION_180;
> +        ctx->mirror_state   = VA_MIRROR_NONE;
> +        break;
> +    case TRANSPOSE_REVERAL_FLIP:
> +        ctx->rotation_state = VA_ROTATION_180;
> +        ctx->mirror_state   = VA_MIRROR_VERTICAL;
> +        break;
> +    default:
> +        av_log(avctx, AV_LOG_ERROR, "Failed to set direction to %d\n", ctx->dir);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    support_flag = pipeline_caps.rotation_flags & (1 << ctx->rotation_state);
> +    if (!support_flag) {
> +        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support %d\n",
> +               ctx->rotation_state);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    return 0;
> +}
> +
> +static int transpose_vaapi_filter_frame(AVFilterLink *inlink, AVFrame *input_frame)
> +{
> +    AVFilterContext *avctx     = inlink->dst;
> +    AVFilterLink *outlink      = avctx->outputs[0];
> +    VAAPIVPPContext *vpp_ctx   = avctx->priv;
> +    TransposeVAAPIContext *ctx = avctx->priv;
> +    AVFrame *output_frame      = NULL;
> +    VASurfaceID input_surface, output_surface;
> +    VARectangle input_region, output_region;
> +
> +    VAProcPipelineParameterBuffer params;
> +    int err;
> +
> +    if (ctx->passthrough)
> +        return ff_filter_frame(outlink, input_frame);
> +
> +    av_log(avctx, AV_LOG_DEBUG, "Filter input: %s, %ux%u (%"PRId64").\n",
> +           av_get_pix_fmt_name(input_frame->format),
> +           input_frame->width, input_frame->height, input_frame->pts);
> +
> +    if (vpp_ctx->va_context == VA_INVALID_ID)
> +        return AVERROR(EINVAL);
> +
> +    input_surface = (VASurfaceID)(uintptr_t)input_frame->data[3];
> +    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp input.\n",
> +           input_surface);
> +
> +    output_frame = ff_get_video_buffer(outlink, vpp_ctx->output_width,
> +                                       vpp_ctx->output_height);
> +    if (!output_frame) {
> +        err = AVERROR(ENOMEM);
> +        goto fail;
> +    }
> +
> +    output_surface = (VASurfaceID)(uintptr_t)output_frame->data[3];
> +    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp output.\n",
> +           output_surface);
> +    memset(&params, 0, sizeof(params));
> +    input_region = (VARectangle) {
> +        .x      = 0,
> +        .y      = 0,
> +        .width  = input_frame->width,
> +        .height = input_frame->height,
> +    };
> +
> +    output_region = (VARectangle) {
> +        .x      = 0,
> +        .y      = 0,
> +        .width  = output_frame->width,
> +        .height = output_frame->height,
> +    };
> +
> +    switch (ctx->rotation_state) {
> +    case VA_ROTATION_NONE:
> +    case VA_ROTATION_90:
> +    case VA_ROTATION_180:
> +    case VA_ROTATION_270:
> +        params.rotation_state = ctx->rotation_state;
> +        break;
> +    default:
> +        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support rotation %d\n",
> +               ctx->rotation_state);

This case...

> +        err = AVERROR(EINVAL);
> +        goto fail;
> +    }
> +
> +    switch (ctx->mirror_state) {
> +    case VA_MIRROR_HORIZONTAL:
> +    case VA_MIRROR_VERTICAL:
> +    case VA_MIRROR_NONE:
> +        params.mirror_state = ctx->mirror_state;
> +        break;
> +    default:
> +        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support mirroring %d\n",
> +               ctx->mirror_state);

... and this case can't ever be hit, can they?  (They are only ever set to these values in build_filter_params().)  I wouldn't bother checking, but if you do then an assert would be best - EINVAL indicates that the user passed some invalid option, which isn't the case here.

> +        err = AVERROR(EINVAL);
> +        goto fail;
> +    }
> +
> +    if (vpp_ctx->nb_filter_buffers) {
> +        params.filters     = &vpp_ctx->filter_buffers[0];
> +        params.num_filters = vpp_ctx->nb_filter_buffers;
> +    }
> +
> +    params.surface = input_surface;
> +    params.surface_region = &input_region;
> +    params.surface_color_standard =
> +        ff_vaapi_vpp_colour_standard(input_frame->colorspace);
> +
> +    params.output_region = &output_region;
> +#define BG_BLACK 0xff000000
> +    params.output_background_color = BG_BLACK;
> +#undef BG_BLACK

I'm not sure the inline constant adds anything.  All the other VAAPI filters have it too, so maybe a separate patch to add the constant to vaapi_vpp.h (VAAPI_VPP_BACKGROUND_BLACK?) and use it everywhere would make sense?

> +    params.output_color_standard = params.surface_color_standard;
> +
> +    params.pipeline_flags = 0;
> +    params.filter_flags = VA_FRAME_PICTURE;
> +
> +    err = ff_vaapi_vpp_render_picture(avctx, &params, output_surface);
> +    if (err < 0)
> +        goto fail;
> +
> +    err = av_frame_copy_props(output_frame, input_frame);
> +    if (err < 0)
> +        goto fail;
> +    av_frame_free(&input_frame);
> +
> +    av_log(avctx, AV_LOG_DEBUG, "Filter output: %s, %ux%u (%"PRId64").\n",
> +           av_get_pix_fmt_name(output_frame->format),
> +           output_frame->width, output_frame->height, output_frame->pts);
> +
> +    return ff_filter_frame(outlink, output_frame);
> +
> +fail:
> +    av_frame_free(&input_frame);
> +    av_frame_free(&output_frame);
> +    return err;
> +}
> +
> +static av_cold int transpose_vaapi_init(AVFilterContext *avctx)
> +{
> +    VAAPIVPPContext *vpp_ctx = avctx->priv;
> +
> +    ff_vaapi_vpp_ctx_init(avctx);
> +    vpp_ctx->pipeline_uninit     = ff_vaapi_vpp_pipeline_uninit;
> +    vpp_ctx->build_filter_params = transpose_vaapi_build_filter_params;
> +    vpp_ctx->output_format       = AV_PIX_FMT_NONE;
> +
> +    return 0;
> +}
> +
> +static void transpose_vaapi_vpp_ctx_uninit(AVFilterContext *avctx)
> +{
> +    return ff_vaapi_vpp_ctx_uninit(avctx);
> +}
> +
> +static int transpose_vaapi_vpp_query_formats(AVFilterContext *avctx)
> +{
> +    return ff_vaapi_vpp_query_formats(avctx);
> +}
> +
> +static int transpose_vaapi_vpp_config_input(AVFilterLink *inlink)
> +{
> +    return ff_vaapi_vpp_config_input(inlink);
> +}
> +
> +static int transpose_vaapi_vpp_config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *avctx     = outlink->src;
> +    VAAPIVPPContext *vpp_ctx   = avctx->priv;
> +    TransposeVAAPIContext *ctx = avctx->priv;
> +    AVFilterLink *inlink       = avctx->inputs[0];
> +
> +    if ((inlink->w >= inlink->h && ctx->passthrough == TRANSPOSE_PT_TYPE_LANDSCAPE) ||
> +        (inlink->w <= inlink->h && ctx->passthrough == TRANSPOSE_PT_TYPE_PORTRAIT)) {
> +        av_log(avctx, AV_LOG_VERBOSE,
> +               "w:%d h:%d -> w:%d h:%d (passthrough mode)\n",
> +               inlink->w, inlink->h, inlink->w, inlink->h);
> +        return ff_vaapi_vpp_config_output(outlink);
> +    }
> +
> +    ctx->passthrough = TRANSPOSE_PT_TYPE_NONE;
> +
> +    switch (ctx->dir) {
> +    case TRANSPOSE_CCLOCK_FLIP:
> +    case TRANSPOSE_CCLOCK:
> +    case TRANSPOSE_CLOCK:
> +    case TRANSPOSE_CLOCK_FLIP:
> +        vpp_ctx->output_width  = avctx->inputs[0]->h;
> +        vpp_ctx->output_height = avctx->inputs[0]->w;
> +        av_log(avctx, AV_LOG_DEBUG, "swap width and height for clock/cclock rotation\n");
> +        break;
> +    default:
> +        break;
> +    }
> +
> +    return ff_vaapi_vpp_config_output(outlink);
> +}
> +
> +static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h)
> +{
> +    TransposeVAAPIContext *ctx = inlink->dst->priv;
> +
> +    return ctx->passthrough ?
> +        ff_null_get_video_buffer(inlink, w, h) :
> +        ff_default_get_video_buffer(inlink, w, h);
> +}
> +
> +#define OFFSET(x) offsetof(TransposeVAAPIContext, x)
> +#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM)
> +static const AVOption transpose_vaapi_options[] = {
> +    { "dir", "set transpose direction", OFFSET(dir), AV_OPT_TYPE_INT, { .i64 = TRANSPOSE_CCLOCK_FLIP }, 0, 5, FLAGS, "dir" },
> +        { "cclock_flip",  "rotate counter-clockwise with vertical flip",     0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK_FLIP  }, .flags=FLAGS, .unit = "dir" },
> +        { "clock",        "rotate clockwise",                                0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK        }, .flags=FLAGS, .unit = "dir" },
> +        { "cclock",       "rotate counter-clockwise",                        0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK       }, .flags=FLAGS, .unit = "dir" },
> +        { "clock_flip",   "rotate clockwise with vertical flip",             0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK_FLIP   }, .flags=FLAGS, .unit = "dir" },
> +        { "reveral",      "rotate clockwise 180 degrees",                    0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL      }, .flags=FLAGS, .unit = "dir" },
> +        { "reveral_flip", "rotate clockwise 180 degrees with vertical flip", 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL_FLIP }, .flags=FLAGS, .unit = "dir" },

"rotate clockwise 180 degrees with vertical flip" is I think better known as "flip horizontally".

> +
> +    { "passthrough", "do not apply transposition if the input matches the specified geometry",
> +      OFFSET(passthrough), AV_OPT_TYPE_INT, {.i64=TRANSPOSE_PT_TYPE_NONE},  0, INT_MAX, FLAGS, "passthrough" },
> +        { "none",      "always apply transposition",   0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_NONE},      INT_MIN, INT_MAX, FLAGS, "passthrough" },
> +        { "portrait",  "preserve portrait geometry",   0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_PORTRAIT},  INT_MIN, INT_MAX, FLAGS, "passthrough" },
> +        { "landscape", "preserve landscape geometry",  0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_LANDSCAPE}, INT_MIN, INT_MAX, FLAGS, "passthrough" },
> +
> +    { NULL }
> +};
> +
> +
> +AVFILTER_DEFINE_CLASS(transpose_vaapi);
> +
> +static const AVFilterPad transpose_vaapi_inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .filter_frame = &transpose_vaapi_filter_frame,
> +        .get_video_buffer = get_video_buffer,
> +        .config_props = &transpose_vaapi_vpp_config_input,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad transpose_vaapi_outputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO,
> +        .config_props = &transpose_vaapi_vpp_config_output,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_vf_transpose_vaapi = {
> +    .name           = "transpose_vaapi",
> +    .description    = NULL_IF_CONFIG_SMALL("VAAPI VPP for transpose"),
> +    .priv_size      = sizeof(TransposeVAAPIContext),
> +    .init           = &transpose_vaapi_init,
> +    .uninit         = &transpose_vaapi_vpp_ctx_uninit,
> +    .query_formats  = &transpose_vaapi_vpp_query_formats,
> +    .inputs         = transpose_vaapi_inputs,
> +    .outputs        = transpose_vaapi_outputs,
> +    .priv_class     = &transpose_vaapi_class,
> +    .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
> +};
> 

- Mark
Zachary Zhou Dec. 24, 2018, 7:02 a.m.
> -----Original Message-----

> From: ffmpeg-devel [mailto:ffmpeg-devel-bounces@ffmpeg.org] On Behalf Of

> Mark Thompson

> Sent: Friday, December 21, 2018 5:46 AM

> To: ffmpeg-devel@ffmpeg.org

> Subject: Re: [FFmpeg-devel] [PATCH V6] libavfilter: add transpose_vaapi filter

> 

> On 18/12/2018 09:54, Zachary Zhou wrote:

> > Swap width and height when do clock/cclock rotation Add

> > reveral/reveral_flip options

> >

> > ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128

> > -hwaccel_output_format vaapi -i input.264 -vf "transpose_vaapi=clock_flip"

> > -c:v h264_vaapi output.h264

> >

> > Signed-off-by: Zachary Zhou <zachary.zhou@intel.com>

> > ---

> >  configure                        |   2 +

> >  libavfilter/Makefile             |   1 +

> >  libavfilter/allfilters.c         |   1 +

> >  libavfilter/vf_transpose_vaapi.c | 360

> > +++++++++++++++++++++++++++++++

> >  4 files changed, 364 insertions(+)

> >  create mode 100644 libavfilter/vf_transpose_vaapi.c

> >

> > ...

> > diff --git a/libavfilter/vf_transpose_vaapi.c

> > b/libavfilter/vf_transpose_vaapi.c

> > new file mode 100644

> > index 0000000000..a2bf245da9

> > --- /dev/null

> > +++ b/libavfilter/vf_transpose_vaapi.c

> > @@ -0,0 +1,360 @@

> > +/*

> > + * This file is part of FFmpeg.

> > + *

> > + * FFmpeg 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.

> > + *

> > + * FFmpeg 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 FFmpeg; if not, write to the Free Software

> > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA

> > +02110-1301 USA  */ #include <string.h>

> > +

> > +#include "libavutil/avassert.h"

> > +#include "libavutil/mem.h"

> > +#include "libavutil/opt.h"

> > +#include "libavutil/pixdesc.h"

> > +

> > +#include "avfilter.h"

> > +#include "formats.h"

> > +#include "internal.h"

> > +#include "vaapi_vpp.h"

> > +

> > +typedef enum {

> > +    TRANSPOSE_PT_TYPE_NONE,

> > +    TRANSPOSE_PT_TYPE_LANDSCAPE,

> > +    TRANSPOSE_PT_TYPE_PORTRAIT,

> > +} PassthroughType;

> > +

> > +enum TransposeDir {

> > +    TRANSPOSE_CCLOCK_FLIP,

> > +    TRANSPOSE_CLOCK,

> > +    TRANSPOSE_CCLOCK,

> > +    TRANSPOSE_CLOCK_FLIP,

> > +    TRANSPOSE_REVERAL,

> > +    TRANSPOSE_REVERAL_FLIP,

> 

> "REVERAL"?  I guess that's meant to be "REVERSAL".  I'm not convinced it's

> quite the right word, though.  REVERSAL_FLIP is standalone as hflip - if you

> included vflip as well (the missing case from the eight possible transforms,

> excluding identity) then that name might be better?


Yes it should be "REVERSAL".
I added 180 degrees, just want to say VAAPI can easy do 180 agree in one time, will better than twice call in software solution. 

if remove "REVERSAL" options will make transpose vaapi has exactly same options with other transpose filters,  if add another two options (rotation, mirror) may best use VAAPI functions (covering all rotations and mirrors).  which one you prefer ?  

From my view, just remove "REVERSAL" options may make it simple.

> 

> Also, please use the enums in transpose.h directly rather than duplicating

> them.


Got it will reuse those enums.

> 

> > +};

> > +

> > +typedef struct TransposeVAAPIContext {

> > +    VAAPIVPPContext vpp_ctx; // must be the first field

> > +    int passthrough;         // PassthroughType, landscape passthrough mode

> enabled

> > +    int dir;                 // TransposeDir

> > +

> > +    int rotation_state;

> > +    int mirror_state;

> > +} TransposeVAAPIContext;

> > +

> > +static int transpose_vaapi_build_filter_params(AVFilterContext

> > +*avctx) {

> > +    VAAPIVPPContext *vpp_ctx   = avctx->priv;

> > +    TransposeVAAPIContext *ctx = avctx->priv;

> > +    VAStatus vas;

> > +    int support_flag;

> > +    VAProcPipelineCaps pipeline_caps;

> > +

> > +    memset(&pipeline_caps, 0, sizeof(pipeline_caps));

> > +    vas = vaQueryVideoProcPipelineCaps(vpp_ctx->hwctx->display,

> > +                                       vpp_ctx->va_context,

> > +                                       NULL, 0,

> > +                                       &pipeline_caps);

> > +    if (vas != VA_STATUS_SUCCESS) {

> > +        av_log(avctx, AV_LOG_ERROR, "Failed to query pipeline "

> > +               "caps: %d (%s).\n", vas, vaErrorStr(vas));

> > +        return AVERROR(EIO);

> > +    }

> > +

> > +    if (!pipeline_caps.rotation_flags) {

> 

> This condition doesn't guarantee that all possibly rotations are supported -

> <https://github.com/intel/libva/blob/master/va/va_vpp.h#L611-L612>.


I think I did such check in end of this function.

> 

> The mirror flags also need the same treatment.

Yes, mirror flags should be checked. I filed a bug to iHD driver.
https://github.com/intel/media-driver/issues/382

would you like me to resend the patch after the bug fixed, or I send a separate patch for the patch later ?

> 

> > +        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support

> transpose\n");

> > +        return AVERROR(EINVAL);

> > +    }

> > +

> > +    switch (ctx->dir) {

> > +    case TRANSPOSE_CCLOCK_FLIP:

> > +        ctx->rotation_state = VA_ROTATION_270;

> > +        ctx->mirror_state   = VA_MIRROR_VERTICAL;

> > +        break;

> > +    case TRANSPOSE_CLOCK:

> > +        ctx->rotation_state = VA_ROTATION_90;

> > +        ctx->mirror_state   = VA_MIRROR_NONE;

> > +        break;

> > +    case TRANSPOSE_CCLOCK:

> > +        ctx->rotation_state = VA_ROTATION_270;

> > +        ctx->mirror_state   = VA_MIRROR_NONE;

> > +        break;

> > +    case TRANSPOSE_CLOCK_FLIP:

> > +        ctx->rotation_state = VA_ROTATION_90;

> > +        ctx->mirror_state   = VA_MIRROR_VERTICAL;

> > +        break;

> > +    case TRANSPOSE_REVERAL:

> > +        ctx->rotation_state = VA_ROTATION_180;

> > +        ctx->mirror_state   = VA_MIRROR_NONE;

> > +        break;

> > +    case TRANSPOSE_REVERAL_FLIP:

> > +        ctx->rotation_state = VA_ROTATION_180;

> > +        ctx->mirror_state   = VA_MIRROR_VERTICAL;

> > +        break;

> > +    default:

> > +        av_log(avctx, AV_LOG_ERROR, "Failed to set direction to %d\n", ctx-

> >dir);

> > +        return AVERROR(EINVAL);

> > +    }

> > +

> > +    support_flag = pipeline_caps.rotation_flags & (1 << ctx->rotation_state);

> > +    if (!support_flag) {

> > +        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support %d\n",

> > +               ctx->rotation_state);

> > +        return AVERROR(EINVAL);

> > +    }

> > +

> > +    return 0;

> > +}

> > +

> > +static int transpose_vaapi_filter_frame(AVFilterLink *inlink, AVFrame

> > +*input_frame) {

> > +    AVFilterContext *avctx     = inlink->dst;

> > +    AVFilterLink *outlink      = avctx->outputs[0];

> > +    VAAPIVPPContext *vpp_ctx   = avctx->priv;

> > +    TransposeVAAPIContext *ctx = avctx->priv;

> > +    AVFrame *output_frame      = NULL;

> > +    VASurfaceID input_surface, output_surface;

> > +    VARectangle input_region, output_region;

> > +

> > +    VAProcPipelineParameterBuffer params;

> > +    int err;

> > +

> > +    if (ctx->passthrough)

> > +        return ff_filter_frame(outlink, input_frame);

> > +

> > +    av_log(avctx, AV_LOG_DEBUG, "Filter input: %s, %ux%u (%"PRId64").\n",

> > +           av_get_pix_fmt_name(input_frame->format),

> > +           input_frame->width, input_frame->height,

> > + input_frame->pts);

> > +

> > +    if (vpp_ctx->va_context == VA_INVALID_ID)

> > +        return AVERROR(EINVAL);

> > +

> > +    input_surface = (VASurfaceID)(uintptr_t)input_frame->data[3];

> > +    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp

> input.\n",

> > +           input_surface);

> > +

> > +    output_frame = ff_get_video_buffer(outlink, vpp_ctx->output_width,

> > +                                       vpp_ctx->output_height);

> > +    if (!output_frame) {

> > +        err = AVERROR(ENOMEM);

> > +        goto fail;

> > +    }

> > +

> > +    output_surface = (VASurfaceID)(uintptr_t)output_frame->data[3];

> > +    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp

> output.\n",

> > +           output_surface);

> > +    memset(&params, 0, sizeof(params));

> > +    input_region = (VARectangle) {

> > +        .x      = 0,

> > +        .y      = 0,

> > +        .width  = input_frame->width,

> > +        .height = input_frame->height,

> > +    };

> > +

> > +    output_region = (VARectangle) {

> > +        .x      = 0,

> > +        .y      = 0,

> > +        .width  = output_frame->width,

> > +        .height = output_frame->height,

> > +    };

> > +

> > +    switch (ctx->rotation_state) {

> > +    case VA_ROTATION_NONE:

> > +    case VA_ROTATION_90:

> > +    case VA_ROTATION_180:

> > +    case VA_ROTATION_270:

> > +        params.rotation_state = ctx->rotation_state;

> > +        break;

> > +    default:

> > +        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support rotation %d\n",

> > +               ctx->rotation_state);

> 

> This case...

> 

> > +        err = AVERROR(EINVAL);

> > +        goto fail;

> > +    }

> > +

> > +    switch (ctx->mirror_state) {

> > +    case VA_MIRROR_HORIZONTAL:

> > +    case VA_MIRROR_VERTICAL:

> > +    case VA_MIRROR_NONE:

> > +        params.mirror_state = ctx->mirror_state;

> > +        break;

> > +    default:

> > +        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support

> mirroring %d\n",

> > +               ctx->mirror_state);

> 

> ... and this case can't ever be hit, can they?  (They are only ever set to these

> values in build_filter_params().)  I wouldn't bother checking, but if you do

> then an assert would be best - EINVAL indicates that the user passed some

> invalid option, which isn't the case here.


Yes, you are right, they can't be hit.
I am changing it to add av_assert0(0);

> 

> > +        err = AVERROR(EINVAL);

> > +        goto fail;

> > +    }

> > +

> > +    if (vpp_ctx->nb_filter_buffers) {

> > +        params.filters     = &vpp_ctx->filter_buffers[0];

> > +        params.num_filters = vpp_ctx->nb_filter_buffers;

> > +    }

> > +

> > +    params.surface = input_surface;

> > +    params.surface_region = &input_region;

> > +    params.surface_color_standard =

> > +        ff_vaapi_vpp_colour_standard(input_frame->colorspace);

> > +

> > +    params.output_region = &output_region; #define BG_BLACK

> > +0xff000000

> > +    params.output_background_color = BG_BLACK; #undef BG_BLACK

> 

> I'm not sure the inline constant adds anything.  All the other VAAPI filters have

> it too, so maybe a separate patch to add the constant to vaapi_vpp.h

> (VAAPI_VPP_BACKGROUND_BLACK?) and use it everywhere would make

> sense?


I  am changing it back, and try to add a comments in the end 
params.output_background_color = 0xff000000;   //Black in ARGB format

And I think it is good to add the constant to vaapi_vpp.h(VAAPI_VPP_BACKGROUND_BLACK), 
because I think people usually don't change this value, and it may more understandable.
I am happy to do the change if you think we need do it.

> 

> > +    params.output_color_standard = params.surface_color_standard;

> > +

> > +    params.pipeline_flags = 0;

> > +    params.filter_flags = VA_FRAME_PICTURE;

> > +

> > +    err = ff_vaapi_vpp_render_picture(avctx, &params, output_surface);

> > +    if (err < 0)

> > +        goto fail;

> > +

> > +    err = av_frame_copy_props(output_frame, input_frame);

> > +    if (err < 0)

> > +        goto fail;

> > +    av_frame_free(&input_frame);

> > +

> > +    av_log(avctx, AV_LOG_DEBUG, "Filter output: %s, %ux%u

> (%"PRId64").\n",

> > +           av_get_pix_fmt_name(output_frame->format),

> > +           output_frame->width, output_frame->height,

> > + output_frame->pts);

> > +

> > +    return ff_filter_frame(outlink, output_frame);

> > +

> > +fail:

> > +    av_frame_free(&input_frame);

> > +    av_frame_free(&output_frame);

> > +    return err;

> > +}

> > +

> > +static av_cold int transpose_vaapi_init(AVFilterContext *avctx) {

> > +    VAAPIVPPContext *vpp_ctx = avctx->priv;

> > +

> > +    ff_vaapi_vpp_ctx_init(avctx);

> > +    vpp_ctx->pipeline_uninit     = ff_vaapi_vpp_pipeline_uninit;

> > +    vpp_ctx->build_filter_params = transpose_vaapi_build_filter_params;

> > +    vpp_ctx->output_format       = AV_PIX_FMT_NONE;

> > +

> > +    return 0;

> > +}

> > +

> > +static void transpose_vaapi_vpp_ctx_uninit(AVFilterContext *avctx) {

> > +    return ff_vaapi_vpp_ctx_uninit(avctx); }

> > +

> > +static int transpose_vaapi_vpp_query_formats(AVFilterContext *avctx)

> > +{

> > +    return ff_vaapi_vpp_query_formats(avctx);

> > +}

> > +

> > +static int transpose_vaapi_vpp_config_input(AVFilterLink *inlink) {

> > +    return ff_vaapi_vpp_config_input(inlink);

> > +}

> > +

> > +static int transpose_vaapi_vpp_config_output(AVFilterLink *outlink) {

> > +    AVFilterContext *avctx     = outlink->src;

> > +    VAAPIVPPContext *vpp_ctx   = avctx->priv;

> > +    TransposeVAAPIContext *ctx = avctx->priv;

> > +    AVFilterLink *inlink       = avctx->inputs[0];

> > +

> > +    if ((inlink->w >= inlink->h && ctx->passthrough ==

> TRANSPOSE_PT_TYPE_LANDSCAPE) ||

> > +        (inlink->w <= inlink->h && ctx->passthrough ==

> TRANSPOSE_PT_TYPE_PORTRAIT)) {

> > +        av_log(avctx, AV_LOG_VERBOSE,

> > +               "w:%d h:%d -> w:%d h:%d (passthrough mode)\n",

> > +               inlink->w, inlink->h, inlink->w, inlink->h);

> > +        return ff_vaapi_vpp_config_output(outlink);

> > +    }

> > +

> > +    ctx->passthrough = TRANSPOSE_PT_TYPE_NONE;

> > +

> > +    switch (ctx->dir) {

> > +    case TRANSPOSE_CCLOCK_FLIP:

> > +    case TRANSPOSE_CCLOCK:

> > +    case TRANSPOSE_CLOCK:

> > +    case TRANSPOSE_CLOCK_FLIP:

> > +        vpp_ctx->output_width  = avctx->inputs[0]->h;

> > +        vpp_ctx->output_height = avctx->inputs[0]->w;

> > +        av_log(avctx, AV_LOG_DEBUG, "swap width and height for clock/cclock

> rotation\n");

> > +        break;

> > +    default:

> > +        break;

> > +    }

> > +

> > +    return ff_vaapi_vpp_config_output(outlink);

> > +}

> > +

> > +static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h)

> > +{

> > +    TransposeVAAPIContext *ctx = inlink->dst->priv;

> > +

> > +    return ctx->passthrough ?

> > +        ff_null_get_video_buffer(inlink, w, h) :

> > +        ff_default_get_video_buffer(inlink, w, h); }

> > +

> > +#define OFFSET(x) offsetof(TransposeVAAPIContext, x) #define FLAGS

> > +(AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM) static

> const

> > +AVOption transpose_vaapi_options[] = {

> > +    { "dir", "set transpose direction", OFFSET(dir), AV_OPT_TYPE_INT, { .i64 =

> TRANSPOSE_CCLOCK_FLIP }, 0, 5, FLAGS, "dir" },

> > +        { "cclock_flip",  "rotate counter-clockwise with vertical flip",     0,

> AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK_FLIP  }, .flags=FLAGS, .unit

> = "dir" },

> > +        { "clock",        "rotate clockwise",                                0,

> AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK        }, .flags=FLAGS, .unit =

> "dir" },

> > +        { "cclock",       "rotate counter-clockwise",                        0,

> AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK       }, .flags=FLAGS, .unit =

> "dir" },

> > +        { "clock_flip",   "rotate clockwise with vertical flip",             0,

> AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK_FLIP   }, .flags=FLAGS, .unit

> = "dir" },

> > +        { "reveral",      "rotate clockwise 180 degrees",                    0,

> AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL      }, .flags=FLAGS, .unit =

> "dir" },

> > +        { "reveral_flip", "rotate clockwise 180 degrees with vertical

> > +flip", 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL_FLIP },

> > +.flags=FLAGS, .unit = "dir" },

> 

> "rotate clockwise 180 degrees with vertical flip" is I think better known as "flip

> horizontally".


I will change it.


> 

> > +

> > +    { "passthrough", "do not apply transposition if the input matches the

> specified geometry",

> > +      OFFSET(passthrough), AV_OPT_TYPE_INT,

> {.i64=TRANSPOSE_PT_TYPE_NONE},  0, INT_MAX, FLAGS, "passthrough" },

> > +        { "none",      "always apply transposition",   0, AV_OPT_TYPE_CONST,

> {.i64=TRANSPOSE_PT_TYPE_NONE},      INT_MIN, INT_MAX, FLAGS,

> "passthrough" },

> > +        { "portrait",  "preserve portrait geometry",   0, AV_OPT_TYPE_CONST,

> {.i64=TRANSPOSE_PT_TYPE_PORTRAIT},  INT_MIN, INT_MAX, FLAGS,

> "passthrough" },

> > +        { "landscape", "preserve landscape geometry",  0,

> > + AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_LANDSCAPE}, INT_MIN,

> > + INT_MAX, FLAGS, "passthrough" },

> > +

> > +    { NULL }

> > +};

> > +

> > +

> > +AVFILTER_DEFINE_CLASS(transpose_vaapi);

> > +

> > +static const AVFilterPad transpose_vaapi_inputs[] = {

> > +    {

> > +        .name         = "default",

> > +        .type         = AVMEDIA_TYPE_VIDEO,

> > +        .filter_frame = &transpose_vaapi_filter_frame,

> > +        .get_video_buffer = get_video_buffer,

> > +        .config_props = &transpose_vaapi_vpp_config_input,

> > +    },

> > +    { NULL }

> > +};

> > +

> > +static const AVFilterPad transpose_vaapi_outputs[] = {

> > +    {

> > +        .name = "default",

> > +        .type = AVMEDIA_TYPE_VIDEO,

> > +        .config_props = &transpose_vaapi_vpp_config_output,

> > +    },

> > +    { NULL }

> > +};

> > +

> > +AVFilter ff_vf_transpose_vaapi = {

> > +    .name           = "transpose_vaapi",

> > +    .description    = NULL_IF_CONFIG_SMALL("VAAPI VPP for transpose"),

> > +    .priv_size      = sizeof(TransposeVAAPIContext),

> > +    .init           = &transpose_vaapi_init,

> > +    .uninit         = &transpose_vaapi_vpp_ctx_uninit,

> > +    .query_formats  = &transpose_vaapi_vpp_query_formats,

> > +    .inputs         = transpose_vaapi_inputs,

> > +    .outputs        = transpose_vaapi_outputs,

> > +    .priv_class     = &transpose_vaapi_class,

> > +    .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, };

> >

> 

> - Mark


Thank you Mark for the review.
- Zach

> _______________________________________________

> ffmpeg-devel mailing list

> ffmpeg-devel@ffmpeg.org

> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

Patch hide | download patch | download mbox

diff --git a/configure b/configure
index be49c19b88..04ee671a60 100755
--- a/configure
+++ b/configure
@@ -3481,6 +3481,7 @@  tinterlace_pad_test_deps="tinterlace_filter"
 tonemap_filter_deps="const_nan"
 tonemap_opencl_filter_deps="opencl const_nan"
 transpose_opencl_filter_deps="opencl"
+transpose_vaapi_filter_deps="vaapi VAProcPipelineCaps_rotation_flags"
 unsharp_opencl_filter_deps="opencl"
 uspp_filter_deps="gpl avcodec"
 vaguedenoiser_filter_deps="gpl"
@@ -5984,6 +5985,7 @@  check_type "d3d9.h dxva2api.h" DXVA2_ConfigPictureDecode -D_WIN32_WINNT=0x0602
 
 check_type "va/va.h va/va_dec_hevc.h" "VAPictureParameterBufferHEVC"
 check_struct "va/va.h" "VADecPictureParameterBufferVP9" bit_depth
+check_struct "va/va.h va/va_vpp.h" "VAProcPipelineCaps" rotation_flags
 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_vp8.h"  "VAEncPictureParameterBufferVP8"
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 6e2658186d..4246d3480f 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -394,6 +394,7 @@  OBJS-$(CONFIG_TPAD_FILTER)                   += vf_tpad.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_TRANSPOSE_NPP_FILTER)          += vf_transpose_npp.o cuda_check.o
 OBJS-$(CONFIG_TRANSPOSE_OPENCL_FILTER)       += vf_transpose_opencl.o opencl.o opencl/transpose.o
+OBJS-$(CONFIG_TRANSPOSE_VAAPI_FILTER)        += vf_transpose_vaapi.o vaapi_vpp.o
 OBJS-$(CONFIG_TRIM_FILTER)                   += trim.o
 OBJS-$(CONFIG_UNPREMULTIPLY_FILTER)          += vf_premultiply.o framesync.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index a600069500..0b82ff7ced 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -373,6 +373,7 @@  extern AVFilter ff_vf_tpad;
 extern AVFilter ff_vf_transpose;
 extern AVFilter ff_vf_transpose_npp;
 extern AVFilter ff_vf_transpose_opencl;
+extern AVFilter ff_vf_transpose_vaapi;
 extern AVFilter ff_vf_trim;
 extern AVFilter ff_vf_unpremultiply;
 extern AVFilter ff_vf_unsharp;
diff --git a/libavfilter/vf_transpose_vaapi.c b/libavfilter/vf_transpose_vaapi.c
new file mode 100644
index 0000000000..a2bf245da9
--- /dev/null
+++ b/libavfilter/vf_transpose_vaapi.c
@@ -0,0 +1,360 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <string.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "vaapi_vpp.h"
+
+typedef enum {
+    TRANSPOSE_PT_TYPE_NONE,
+    TRANSPOSE_PT_TYPE_LANDSCAPE,
+    TRANSPOSE_PT_TYPE_PORTRAIT,
+} PassthroughType;
+
+enum TransposeDir {
+    TRANSPOSE_CCLOCK_FLIP,
+    TRANSPOSE_CLOCK,
+    TRANSPOSE_CCLOCK,
+    TRANSPOSE_CLOCK_FLIP,
+    TRANSPOSE_REVERAL,
+    TRANSPOSE_REVERAL_FLIP,
+};
+
+typedef struct TransposeVAAPIContext {
+    VAAPIVPPContext vpp_ctx; // must be the first field
+    int passthrough;         // PassthroughType, landscape passthrough mode enabled
+    int dir;                 // TransposeDir
+
+    int rotation_state;
+    int mirror_state;
+} TransposeVAAPIContext;
+
+static int transpose_vaapi_build_filter_params(AVFilterContext *avctx)
+{
+    VAAPIVPPContext *vpp_ctx   = avctx->priv;
+    TransposeVAAPIContext *ctx = avctx->priv;
+    VAStatus vas;
+    int support_flag;
+    VAProcPipelineCaps pipeline_caps;
+
+    memset(&pipeline_caps, 0, sizeof(pipeline_caps));
+    vas = vaQueryVideoProcPipelineCaps(vpp_ctx->hwctx->display,
+                                       vpp_ctx->va_context,
+                                       NULL, 0,
+                                       &pipeline_caps);
+    if (vas != VA_STATUS_SUCCESS) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to query pipeline "
+               "caps: %d (%s).\n", vas, vaErrorStr(vas));
+        return AVERROR(EIO);
+    }
+
+    if (!pipeline_caps.rotation_flags) {
+        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support transpose\n");
+        return AVERROR(EINVAL);
+    }
+
+    switch (ctx->dir) {
+    case TRANSPOSE_CCLOCK_FLIP:
+        ctx->rotation_state = VA_ROTATION_270;
+        ctx->mirror_state   = VA_MIRROR_VERTICAL;
+        break;
+    case TRANSPOSE_CLOCK:
+        ctx->rotation_state = VA_ROTATION_90;
+        ctx->mirror_state   = VA_MIRROR_NONE;
+        break;
+    case TRANSPOSE_CCLOCK:
+        ctx->rotation_state = VA_ROTATION_270;
+        ctx->mirror_state   = VA_MIRROR_NONE;
+        break;
+    case TRANSPOSE_CLOCK_FLIP:
+        ctx->rotation_state = VA_ROTATION_90;
+        ctx->mirror_state   = VA_MIRROR_VERTICAL;
+        break;
+    case TRANSPOSE_REVERAL:
+        ctx->rotation_state = VA_ROTATION_180;
+        ctx->mirror_state   = VA_MIRROR_NONE;
+        break;
+    case TRANSPOSE_REVERAL_FLIP:
+        ctx->rotation_state = VA_ROTATION_180;
+        ctx->mirror_state   = VA_MIRROR_VERTICAL;
+        break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Failed to set direction to %d\n", ctx->dir);
+        return AVERROR(EINVAL);
+    }
+
+    support_flag = pipeline_caps.rotation_flags & (1 << ctx->rotation_state);
+    if (!support_flag) {
+        av_log(avctx, AV_LOG_ERROR, "VAAPI driver doesn't support %d\n",
+               ctx->rotation_state);
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static int transpose_vaapi_filter_frame(AVFilterLink *inlink, AVFrame *input_frame)
+{
+    AVFilterContext *avctx     = inlink->dst;
+    AVFilterLink *outlink      = avctx->outputs[0];
+    VAAPIVPPContext *vpp_ctx   = avctx->priv;
+    TransposeVAAPIContext *ctx = avctx->priv;
+    AVFrame *output_frame      = NULL;
+    VASurfaceID input_surface, output_surface;
+    VARectangle input_region, output_region;
+
+    VAProcPipelineParameterBuffer params;
+    int err;
+
+    if (ctx->passthrough)
+        return ff_filter_frame(outlink, input_frame);
+
+    av_log(avctx, AV_LOG_DEBUG, "Filter input: %s, %ux%u (%"PRId64").\n",
+           av_get_pix_fmt_name(input_frame->format),
+           input_frame->width, input_frame->height, input_frame->pts);
+
+    if (vpp_ctx->va_context == VA_INVALID_ID)
+        return AVERROR(EINVAL);
+
+    input_surface = (VASurfaceID)(uintptr_t)input_frame->data[3];
+    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp input.\n",
+           input_surface);
+
+    output_frame = ff_get_video_buffer(outlink, vpp_ctx->output_width,
+                                       vpp_ctx->output_height);
+    if (!output_frame) {
+        err = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    output_surface = (VASurfaceID)(uintptr_t)output_frame->data[3];
+    av_log(avctx, AV_LOG_DEBUG, "Using surface %#x for transpose vpp output.\n",
+           output_surface);
+    memset(&params, 0, sizeof(params));
+    input_region = (VARectangle) {
+        .x      = 0,
+        .y      = 0,
+        .width  = input_frame->width,
+        .height = input_frame->height,
+    };
+
+    output_region = (VARectangle) {
+        .x      = 0,
+        .y      = 0,
+        .width  = output_frame->width,
+        .height = output_frame->height,
+    };
+
+    switch (ctx->rotation_state) {
+    case VA_ROTATION_NONE:
+    case VA_ROTATION_90:
+    case VA_ROTATION_180:
+    case VA_ROTATION_270:
+        params.rotation_state = ctx->rotation_state;
+        break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support rotation %d\n",
+               ctx->rotation_state);
+        err = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    switch (ctx->mirror_state) {
+    case VA_MIRROR_HORIZONTAL:
+    case VA_MIRROR_VERTICAL:
+    case VA_MIRROR_NONE:
+        params.mirror_state = ctx->mirror_state;
+        break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "VAAPI doesn't support mirroring %d\n",
+               ctx->mirror_state);
+        err = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    if (vpp_ctx->nb_filter_buffers) {
+        params.filters     = &vpp_ctx->filter_buffers[0];
+        params.num_filters = vpp_ctx->nb_filter_buffers;
+    }
+
+    params.surface = input_surface;
+    params.surface_region = &input_region;
+    params.surface_color_standard =
+        ff_vaapi_vpp_colour_standard(input_frame->colorspace);
+
+    params.output_region = &output_region;
+#define BG_BLACK 0xff000000
+    params.output_background_color = BG_BLACK;
+#undef BG_BLACK
+    params.output_color_standard = params.surface_color_standard;
+
+    params.pipeline_flags = 0;
+    params.filter_flags = VA_FRAME_PICTURE;
+
+    err = ff_vaapi_vpp_render_picture(avctx, &params, output_surface);
+    if (err < 0)
+        goto fail;
+
+    err = av_frame_copy_props(output_frame, input_frame);
+    if (err < 0)
+        goto fail;
+    av_frame_free(&input_frame);
+
+    av_log(avctx, AV_LOG_DEBUG, "Filter output: %s, %ux%u (%"PRId64").\n",
+           av_get_pix_fmt_name(output_frame->format),
+           output_frame->width, output_frame->height, output_frame->pts);
+
+    return ff_filter_frame(outlink, output_frame);
+
+fail:
+    av_frame_free(&input_frame);
+    av_frame_free(&output_frame);
+    return err;
+}
+
+static av_cold int transpose_vaapi_init(AVFilterContext *avctx)
+{
+    VAAPIVPPContext *vpp_ctx = avctx->priv;
+
+    ff_vaapi_vpp_ctx_init(avctx);
+    vpp_ctx->pipeline_uninit     = ff_vaapi_vpp_pipeline_uninit;
+    vpp_ctx->build_filter_params = transpose_vaapi_build_filter_params;
+    vpp_ctx->output_format       = AV_PIX_FMT_NONE;
+
+    return 0;
+}
+
+static void transpose_vaapi_vpp_ctx_uninit(AVFilterContext *avctx)
+{
+    return ff_vaapi_vpp_ctx_uninit(avctx);
+}
+
+static int transpose_vaapi_vpp_query_formats(AVFilterContext *avctx)
+{
+    return ff_vaapi_vpp_query_formats(avctx);
+}
+
+static int transpose_vaapi_vpp_config_input(AVFilterLink *inlink)
+{
+    return ff_vaapi_vpp_config_input(inlink);
+}
+
+static int transpose_vaapi_vpp_config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *avctx     = outlink->src;
+    VAAPIVPPContext *vpp_ctx   = avctx->priv;
+    TransposeVAAPIContext *ctx = avctx->priv;
+    AVFilterLink *inlink       = avctx->inputs[0];
+
+    if ((inlink->w >= inlink->h && ctx->passthrough == TRANSPOSE_PT_TYPE_LANDSCAPE) ||
+        (inlink->w <= inlink->h && ctx->passthrough == TRANSPOSE_PT_TYPE_PORTRAIT)) {
+        av_log(avctx, AV_LOG_VERBOSE,
+               "w:%d h:%d -> w:%d h:%d (passthrough mode)\n",
+               inlink->w, inlink->h, inlink->w, inlink->h);
+        return ff_vaapi_vpp_config_output(outlink);
+    }
+
+    ctx->passthrough = TRANSPOSE_PT_TYPE_NONE;
+
+    switch (ctx->dir) {
+    case TRANSPOSE_CCLOCK_FLIP:
+    case TRANSPOSE_CCLOCK:
+    case TRANSPOSE_CLOCK:
+    case TRANSPOSE_CLOCK_FLIP:
+        vpp_ctx->output_width  = avctx->inputs[0]->h;
+        vpp_ctx->output_height = avctx->inputs[0]->w;
+        av_log(avctx, AV_LOG_DEBUG, "swap width and height for clock/cclock rotation\n");
+        break;
+    default:
+        break;
+    }
+
+    return ff_vaapi_vpp_config_output(outlink);
+}
+
+static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h)
+{
+    TransposeVAAPIContext *ctx = inlink->dst->priv;
+
+    return ctx->passthrough ?
+        ff_null_get_video_buffer(inlink, w, h) :
+        ff_default_get_video_buffer(inlink, w, h);
+}
+
+#define OFFSET(x) offsetof(TransposeVAAPIContext, x)
+#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM)
+static const AVOption transpose_vaapi_options[] = {
+    { "dir", "set transpose direction", OFFSET(dir), AV_OPT_TYPE_INT, { .i64 = TRANSPOSE_CCLOCK_FLIP }, 0, 5, FLAGS, "dir" },
+        { "cclock_flip",  "rotate counter-clockwise with vertical flip",     0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK_FLIP  }, .flags=FLAGS, .unit = "dir" },
+        { "clock",        "rotate clockwise",                                0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK        }, .flags=FLAGS, .unit = "dir" },
+        { "cclock",       "rotate counter-clockwise",                        0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK       }, .flags=FLAGS, .unit = "dir" },
+        { "clock_flip",   "rotate clockwise with vertical flip",             0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK_FLIP   }, .flags=FLAGS, .unit = "dir" },
+        { "reveral",      "rotate clockwise 180 degrees",                    0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL      }, .flags=FLAGS, .unit = "dir" },
+        { "reveral_flip", "rotate clockwise 180 degrees with vertical flip", 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERAL_FLIP }, .flags=FLAGS, .unit = "dir" },
+
+    { "passthrough", "do not apply transposition if the input matches the specified geometry",
+      OFFSET(passthrough), AV_OPT_TYPE_INT, {.i64=TRANSPOSE_PT_TYPE_NONE},  0, INT_MAX, FLAGS, "passthrough" },
+        { "none",      "always apply transposition",   0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_NONE},      INT_MIN, INT_MAX, FLAGS, "passthrough" },
+        { "portrait",  "preserve portrait geometry",   0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_PORTRAIT},  INT_MIN, INT_MAX, FLAGS, "passthrough" },
+        { "landscape", "preserve landscape geometry",  0, AV_OPT_TYPE_CONST, {.i64=TRANSPOSE_PT_TYPE_LANDSCAPE}, INT_MIN, INT_MAX, FLAGS, "passthrough" },
+
+    { NULL }
+};
+
+
+AVFILTER_DEFINE_CLASS(transpose_vaapi);
+
+static const AVFilterPad transpose_vaapi_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = &transpose_vaapi_filter_frame,
+        .get_video_buffer = get_video_buffer,
+        .config_props = &transpose_vaapi_vpp_config_input,
+    },
+    { NULL }
+};
+
+static const AVFilterPad transpose_vaapi_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+        .config_props = &transpose_vaapi_vpp_config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_transpose_vaapi = {
+    .name           = "transpose_vaapi",
+    .description    = NULL_IF_CONFIG_SMALL("VAAPI VPP for transpose"),
+    .priv_size      = sizeof(TransposeVAAPIContext),
+    .init           = &transpose_vaapi_init,
+    .uninit         = &transpose_vaapi_vpp_ctx_uninit,
+    .query_formats  = &transpose_vaapi_vpp_query_formats,
+    .inputs         = transpose_vaapi_inputs,
+    .outputs        = transpose_vaapi_outputs,
+    .priv_class     = &transpose_vaapi_class,
+    .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};