From 5d273d3efac340ef8de445c955ff44c7abed4e8f Mon Sep 17 00:00:00 2001 From: Mark Thompson Date: Wed, 23 Mar 2016 23:48:13 +0000 Subject: avconv: VAAPI hwcontext initialisation and hwaccel helper Signed-off-by: Anton Khirnov --- avconv_vaapi.c | 626 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 626 insertions(+) create mode 100644 avconv_vaapi.c (limited to 'avconv_vaapi.c') diff --git a/avconv_vaapi.c b/avconv_vaapi.c new file mode 100644 index 0000000000..a385236cc9 --- /dev/null +++ b/avconv_vaapi.c @@ -0,0 +1,626 @@ +/* + * 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 "config.h" + +#include +#include + +#include +#if HAVE_VAAPI_X11 +# include +#endif +#if HAVE_VAAPI_DRM +# include +#endif + +#include "libavutil/avassert.h" +#include "libavutil/avconfig.h" +#include "libavutil/buffer.h" +#include "libavutil/frame.h" +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_vaapi.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixfmt.h" + +#include "libavcodec/vaapi.h" + +#include "avconv.h" + + +static AVClass vaapi_class = { + .class_name = "vaapi", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +#define DEFAULT_SURFACES 20 + +typedef struct VAAPIDecoderContext { + const AVClass *class; + + AVBufferRef *device_ref; + AVHWDeviceContext *device; + AVBufferRef *frames_ref; + AVHWFramesContext *frames; + + VAProfile va_profile; + VAEntrypoint va_entrypoint; + VAConfigID va_config; + VAContextID va_context; + + enum AVPixelFormat decode_format; + int decode_width; + int decode_height; + int decode_surfaces; + + // The output need not have the same format, width and height as the + // decoded frames - the copy for non-direct-mapped access is actually + // a whole vpp instance which can do arbitrary scaling and format + // conversion. + enum AVPixelFormat output_format; + + struct vaapi_context decoder_vaapi_context; +} VAAPIDecoderContext; + + +static int vaapi_get_buffer(AVCodecContext *avctx, AVFrame *frame, int flags) +{ + InputStream *ist = avctx->opaque; + VAAPIDecoderContext *ctx = ist->hwaccel_ctx; + int err; + + err = av_hwframe_get_buffer(ctx->frames_ref, frame, 0); + if (err < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to allocate decoder surface.\n"); + } else { + av_log(ctx, AV_LOG_DEBUG, "Decoder given surface %#x.\n", + (unsigned int)(uintptr_t)frame->data[3]); + } + return err; +} + +static int vaapi_retrieve_data(AVCodecContext *avctx, AVFrame *input) +{ + InputStream *ist = avctx->opaque; + VAAPIDecoderContext *ctx = ist->hwaccel_ctx; + AVFrame *output = 0; + int err; + + av_assert0(input->format == AV_PIX_FMT_VAAPI); + + if (ctx->output_format == AV_PIX_FMT_VAAPI) { + // Nothing to do. + return 0; + } + + av_log(ctx, AV_LOG_DEBUG, "Retrieve data from surface %#x.\n", + (unsigned int)(uintptr_t)input->data[3]); + + output = av_frame_alloc(); + if (!output) + return AVERROR(ENOMEM); + + output->format = ctx->output_format; + + err = av_hwframe_transfer_data(output, input, 0); + if (err < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to transfer data to " + "output frame: %d.\n", err); + goto fail; + } + + err = av_frame_copy_props(output, input); + if (err < 0) { + av_frame_unref(output); + goto fail; + } + + av_frame_unref(input); + av_frame_move_ref(input, output); + av_frame_free(&output); + + return 0; + +fail: + if (output) + av_frame_free(&output); + return err; +} + + +static const struct { + enum AVCodecID codec_id; + int codec_profile; + VAProfile va_profile; +} vaapi_profile_map[] = { +#define MAP(c, p, v) { AV_CODEC_ID_ ## c, FF_PROFILE_ ## p, VAProfile ## v } + MAP(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2Simple ), + MAP(MPEG2VIDEO, MPEG2_MAIN, MPEG2Main ), + MAP(H263, UNKNOWN, H263Baseline), + MAP(MPEG4, MPEG4_SIMPLE, MPEG4Simple ), + MAP(MPEG4, MPEG4_ADVANCED_SIMPLE, + MPEG4AdvancedSimple), + MAP(MPEG4, MPEG4_MAIN, MPEG4Main ), + MAP(H264, H264_CONSTRAINED_BASELINE, + H264ConstrainedBaseline), + MAP(H264, H264_BASELINE, H264Baseline), + MAP(H264, H264_MAIN, H264Main ), + MAP(H264, H264_HIGH, H264High ), +#if VA_CHECK_VERSION(0, 37, 0) + MAP(HEVC, HEVC_MAIN, HEVCMain ), +#endif + MAP(WMV3, VC1_SIMPLE, VC1Simple ), + MAP(WMV3, VC1_MAIN, VC1Main ), + MAP(WMV3, VC1_COMPLEX, VC1Advanced ), + MAP(WMV3, VC1_ADVANCED, VC1Advanced ), + MAP(VC1, VC1_SIMPLE, VC1Simple ), + MAP(VC1, VC1_MAIN, VC1Main ), + MAP(VC1, VC1_COMPLEX, VC1Advanced ), + MAP(VC1, VC1_ADVANCED, VC1Advanced ), +#if VA_CHECK_VERSION(0, 35, 0) + MAP(VP8, UNKNOWN, VP8Version0_3 ), +#endif +#if VA_CHECK_VERSION(0, 37, 1) + MAP(VP9, VP9_0, VP9Profile0 ), +#endif +#undef MAP +}; + +static int vaapi_build_decoder_config(VAAPIDecoderContext *ctx, + AVCodecContext *avctx, + int fallback_allowed) +{ + AVVAAPIDeviceContext *hwctx = ctx->device->hwctx; + AVVAAPIHWConfig *hwconfig = NULL; + AVHWFramesConstraints *constraints = NULL; + VAStatus vas; + int err, i, j; + int loglevel = fallback_allowed ? AV_LOG_VERBOSE : AV_LOG_ERROR; + const AVCodecDescriptor *codec_desc; + const AVPixFmtDescriptor *pix_desc; + enum AVPixelFormat pix_fmt; + VAProfile profile, *profile_list = NULL; + int profile_count, exact_match, alt_profile; + + codec_desc = avcodec_descriptor_get(avctx->codec_id); + if (!codec_desc) { + err = AVERROR(EINVAL); + goto fail; + } + + profile_count = vaMaxNumProfiles(hwctx->display); + profile_list = av_malloc(profile_count * sizeof(VAProfile)); + if (!profile_list) { + err = AVERROR(ENOMEM); + goto fail; + } + + vas = vaQueryConfigProfiles(hwctx->display, + profile_list, &profile_count); + if (vas != VA_STATUS_SUCCESS) { + av_log(ctx, loglevel, "Failed to query profiles: %d (%s).\n", + vas, vaErrorStr(vas)); + err = AVERROR(EIO); + goto fail; + } + + profile = VAProfileNone; + exact_match = 0; + + for (i = 0; i < FF_ARRAY_ELEMS(vaapi_profile_map); i++) { + int profile_match = 0; + if (avctx->codec_id != vaapi_profile_map[i].codec_id) + continue; + if (avctx->profile == vaapi_profile_map[i].codec_profile) + profile_match = 1; + profile = vaapi_profile_map[i].va_profile; + for (j = 0; j < profile_count; j++) { + if (profile == profile_list[j]) { + exact_match = profile_match; + break; + } + } + if (j < profile_count) { + if (exact_match) + break; + alt_profile = vaapi_profile_map[i].codec_profile; + } + } + av_free(profile_list); + + if (profile == VAProfileNone) { + av_log(ctx, loglevel, "No VAAPI support for codec %s.\n", + codec_desc->name); + err = AVERROR(ENOSYS); + goto fail; + } + if (!exact_match) { + if (fallback_allowed || !hwaccel_lax_profile_check) { + av_log(ctx, loglevel, "No VAAPI support for codec %s " + "profile %d.\n", codec_desc->name, avctx->profile); + if (!fallback_allowed) { + av_log(ctx, AV_LOG_WARNING, "If you want attempt decoding " + "anyway with a possibly-incompatible profile, add " + "the option -hwaccel_lax_profile_check.\n"); + } + err = AVERROR(EINVAL); + goto fail; + } else { + av_log(ctx, AV_LOG_WARNING, "No VAAPI support for codec %s " + "profile %d: trying instead with profile %d.\n", + codec_desc->name, avctx->profile, alt_profile); + av_log(ctx, AV_LOG_WARNING, "This may fail or give " + "incorrect results, depending on your hardware.\n"); + } + } + + ctx->va_profile = profile; + ctx->va_entrypoint = VAEntrypointVLD; + + vas = vaCreateConfig(hwctx->display, ctx->va_profile, + ctx->va_entrypoint, 0, 0, &ctx->va_config); + if (vas != VA_STATUS_SUCCESS) { + av_log(ctx, AV_LOG_ERROR, "Failed to create decode pipeline " + "configuration: %d (%s).\n", vas, vaErrorStr(vas)); + err = AVERROR(EIO); + goto fail; + } + + hwconfig = av_hwdevice_hwconfig_alloc(ctx->device_ref); + if (!hwconfig) { + err = AVERROR(ENOMEM); + goto fail; + } + hwconfig->config_id = ctx->va_config; + + constraints = av_hwdevice_get_hwframe_constraints(ctx->device_ref, + hwconfig); + if (!constraints) + goto fail; + + // Decide on the decoder target format. + // If the user specified something with -hwaccel_output_format then + // try to use that to minimise conversions later. + ctx->decode_format = AV_PIX_FMT_NONE; + if (ctx->output_format != AV_PIX_FMT_NONE && + ctx->output_format != AV_PIX_FMT_VAAPI) { + for (i = 0; constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) { + if (constraints->valid_sw_formats[i] == ctx->decode_format) { + ctx->decode_format = ctx->output_format; + av_log(ctx, AV_LOG_DEBUG, "Using decode format %s (output " + "format).\n", av_get_pix_fmt_name(ctx->decode_format)); + break; + } + } + } + // Otherwise, we would like to try to choose something which matches the + // decoder output, but there isn't enough information available here to + // do so. Assume for now that we are always dealing with YUV 4:2:0, so + // pick a format which does that. + if (ctx->decode_format == AV_PIX_FMT_NONE) { + for (i = 0; constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) { + pix_fmt = constraints->valid_sw_formats[i]; + pix_desc = av_pix_fmt_desc_get(pix_fmt); + if (pix_desc->nb_components == 3 && + pix_desc->log2_chroma_w == 1 && + pix_desc->log2_chroma_h == 1) { + ctx->decode_format = pix_fmt; + av_log(ctx, AV_LOG_DEBUG, "Using decode format %s (format " + "matched).\n", av_get_pix_fmt_name(ctx->decode_format)); + break; + } + } + } + // Otherwise pick the first in the list and hope for the best. + if (ctx->decode_format == AV_PIX_FMT_NONE) { + ctx->decode_format = constraints->valid_sw_formats[0]; + av_log(ctx, AV_LOG_DEBUG, "Using decode format %s (first in list).\n", + av_get_pix_fmt_name(ctx->decode_format)); + if (i > 1) { + // There was a choice, and we picked randomly. Warn the user + // that they might want to choose intelligently instead. + av_log(ctx, AV_LOG_WARNING, "Using randomly chosen decode " + "format %s.\n", av_get_pix_fmt_name(ctx->decode_format)); + } + } + + // Ensure the picture size is supported by the hardware. + ctx->decode_width = avctx->coded_width; + ctx->decode_height = avctx->coded_height; + if (ctx->decode_width < constraints->min_width || + ctx->decode_height < constraints->min_height || + ctx->decode_width > constraints->max_width || + ctx->decode_height >constraints->max_height) { + av_log(ctx, AV_LOG_ERROR, "VAAPI hardware does not support image " + "size %dx%d (constraints: width %d-%d height %d-%d).\n", + ctx->decode_width, ctx->decode_height, + constraints->min_width, constraints->max_width, + constraints->min_height, constraints->max_height); + err = AVERROR(EINVAL); + goto fail; + } + + av_hwframe_constraints_free(&constraints); + av_freep(&hwconfig); + + // Decide how many reference frames we need. This might be doable more + // nicely based on the codec and input stream? + ctx->decode_surfaces = DEFAULT_SURFACES; + // For frame-threaded decoding, one additional surfaces is needed for + // each thread. + if (avctx->active_thread_type & FF_THREAD_FRAME) + ctx->decode_surfaces += avctx->thread_count; + + return 0; + +fail: + av_hwframe_constraints_free(&constraints); + av_freep(&hwconfig); + vaDestroyConfig(hwctx->display, ctx->va_config); + av_free(profile_list); + return err; +} + +static void vaapi_decode_uninit(AVCodecContext *avctx) +{ + InputStream *ist = avctx->opaque; + VAAPIDecoderContext *ctx = ist->hwaccel_ctx; + + if (ctx) { + AVVAAPIDeviceContext *hwctx = ctx->device->hwctx; + + if (ctx->va_context != VA_INVALID_ID) { + vaDestroyContext(hwctx->display, ctx->va_context); + ctx->va_context = VA_INVALID_ID; + } + if (ctx->va_config != VA_INVALID_ID) { + vaDestroyConfig(hwctx->display, ctx->va_config); + ctx->va_config = VA_INVALID_ID; + } + + av_buffer_unref(&ctx->frames_ref); + av_buffer_unref(&ctx->device_ref); + av_free(ctx); + } + + av_buffer_unref(&ist->hw_frames_ctx); + + ist->hwaccel_ctx = 0; + ist->hwaccel_uninit = 0; + ist->hwaccel_get_buffer = 0; + ist->hwaccel_retrieve_data = 0; +} + +int vaapi_decode_init(AVCodecContext *avctx) +{ + InputStream *ist = avctx->opaque; + AVVAAPIDeviceContext *hwctx; + AVVAAPIFramesContext *avfc; + VAAPIDecoderContext *ctx; + VAStatus vas; + int err; + int loglevel = (ist->hwaccel_id != HWACCEL_VAAPI ? AV_LOG_VERBOSE + : AV_LOG_ERROR); + + if (ist->hwaccel_ctx) + vaapi_decode_uninit(avctx); + + // We have -hwaccel without -vaapi_device, so just initialise here with + // the device passed as -hwaccel_device (if -vaapi_device was passed, it + // will always have been called before now). + if (!hw_device_ctx) { + err = vaapi_device_init(ist->hwaccel_device); + if (err < 0) + return err; + } + + ctx = av_mallocz(sizeof(*ctx)); + if (!ctx) + return AVERROR(ENOMEM); + ctx->class = &vaapi_class; + + ctx->device_ref = av_buffer_ref(hw_device_ctx); + ctx->device = (AVHWDeviceContext*)ctx->device_ref->data; + + ctx->va_config = VA_INVALID_ID; + ctx->va_context = VA_INVALID_ID; + + hwctx = ctx->device->hwctx; + + ctx->output_format = ist->hwaccel_output_format; + + err = vaapi_build_decoder_config(ctx, avctx, + ist->hwaccel_id != HWACCEL_VAAPI); + if (err < 0) { + av_log(ctx, loglevel, "No supported configuration for this codec."); + goto fail; + } + + avctx->pix_fmt = ctx->output_format; + + ctx->frames_ref = av_hwframe_ctx_alloc(ctx->device_ref); + if (!ctx->frames_ref) { + av_log(ctx, loglevel, "Failed to create VAAPI frame context.\n"); + err = AVERROR(ENOMEM); + goto fail; + } + + ctx->frames = (AVHWFramesContext*)ctx->frames_ref->data; + + ctx->frames->format = AV_PIX_FMT_VAAPI; + ctx->frames->sw_format = ctx->decode_format; + ctx->frames->width = ctx->decode_width; + ctx->frames->height = ctx->decode_height; + ctx->frames->initial_pool_size = ctx->decode_surfaces; + + err = av_hwframe_ctx_init(ctx->frames_ref); + if (err < 0) { + av_log(ctx, loglevel, "Failed to initialise VAAPI frame " + "context: %d\n", err); + goto fail; + } + + avfc = ctx->frames->hwctx; + + vas = vaCreateContext(hwctx->display, ctx->va_config, + ctx->decode_width, ctx->decode_height, + VA_PROGRESSIVE, + avfc->surface_ids, avfc->nb_surfaces, + &ctx->va_context); + if (vas != VA_STATUS_SUCCESS) { + av_log(ctx, AV_LOG_ERROR, "Failed to create decode pipeline " + "context: %d (%s).\n", vas, vaErrorStr(vas)); + err = AVERROR(EINVAL); + goto fail; + } + + av_log(ctx, AV_LOG_DEBUG, "VAAPI decoder (re)init complete.\n"); + + // We would like to set this on the AVCodecContext for use by whoever gets + // the frames from the decoder, but unfortunately the AVCodecContext we + // have here need not be the "real" one (H.264 makes many copies for + // threading purposes). To avoid the problem, we instead store it in the + // InputStream and propagate it from there. + ist->hw_frames_ctx = av_buffer_ref(ctx->frames_ref); + if (!ist->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto fail; + } + + ist->hwaccel_ctx = ctx; + ist->hwaccel_uninit = vaapi_decode_uninit; + ist->hwaccel_get_buffer = vaapi_get_buffer; + ist->hwaccel_retrieve_data = vaapi_retrieve_data; + + ctx->decoder_vaapi_context.display = hwctx->display; + ctx->decoder_vaapi_context.config_id = ctx->va_config; + ctx->decoder_vaapi_context.context_id = ctx->va_context; + avctx->hwaccel_context = &ctx->decoder_vaapi_context; + + return 0; + +fail: + vaapi_decode_uninit(avctx); + return err; +} + +static AVClass *vaapi_log = &vaapi_class; + +static av_cold void vaapi_device_uninit(AVHWDeviceContext *hwdev) +{ + AVVAAPIDeviceContext *hwctx = hwdev->hwctx; + av_log(&vaapi_log, AV_LOG_VERBOSE, "Terminating VAAPI connection.\n"); + vaTerminate(hwctx->display); +} + +av_cold int vaapi_device_init(const char *device) +{ + AVHWDeviceContext *hwdev; + AVVAAPIDeviceContext *hwctx; + VADisplay display; + VAStatus vas; + int major, minor, err; + + display = 0; + +#if HAVE_VAAPI_X11 + if (!display) { + Display *x11_display; + + // Try to open the device as an X11 display. + x11_display = XOpenDisplay(device); + if (!x11_display) { + av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open X11 display " + "%s.\n", XDisplayName(device)); + } else { + display = vaGetDisplay(x11_display); + if (!display) { + av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open a VA display " + "from X11 display %s.\n", XDisplayName(device)); + XCloseDisplay(x11_display); + } else { + av_log(&vaapi_log, AV_LOG_VERBOSE, "Opened VA display via " + "X11 display %s.\n", XDisplayName(device)); + } + } + } +#endif + +#if HAVE_VAAPI_DRM + if (!display && device) { + int drm_fd; + + // Try to open the device as a DRM path. + drm_fd = open(device, O_RDWR); + if (drm_fd < 0) { + av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open DRM device %s.\n", + device); + } else { + display = vaGetDisplayDRM(drm_fd); + if (!display) { + av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open a VA display " + "from DRM device %s.\n", device); + close(drm_fd); + } else { + av_log(&vaapi_log, AV_LOG_VERBOSE, "Opened VA display via " + "DRM device %s.\n", device); + } + } + } +#endif + + if (!display) { + av_log(&vaapi_log, AV_LOG_ERROR, "No VA display found for " + "device %s.\n", device); + return AVERROR(EINVAL); + } + + vas = vaInitialize(display, &major, &minor); + if (vas != VA_STATUS_SUCCESS) { + av_log(&vaapi_log, AV_LOG_ERROR, "Failed to initialise VAAPI " + "connection: %d (%s).\n", vas, vaErrorStr(vas)); + return AVERROR(EIO); + } + av_log(&vaapi_log, AV_LOG_VERBOSE, "Initialised VAAPI connection: " + "version %d.%d\n", major, minor); + + hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI); + if (!hw_device_ctx) { + av_log(&vaapi_log, AV_LOG_ERROR, "Failed to create VAAPI " + "hardware context.\n"); + vaTerminate(display); + return AVERROR(ENOMEM); + } + + hwdev = (AVHWDeviceContext*)hw_device_ctx->data; + hwdev->free = &vaapi_device_uninit; + + hwctx = hwdev->hwctx; + hwctx->display = display; + + err = av_hwdevice_ctx_init(hw_device_ctx); + if (err < 0) { + av_log(&vaapi_log, AV_LOG_ERROR, "Failed to initialise VAAPI " + "hardware context: %d\n", err); + return err; + } + + return 0; +} -- cgit v1.2.3