diff --git a/Makefile b/Makefile index 7b1e55015b..c06d862011 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ OBJS-avconv-$(HAVE_VDPAU_X11) += avconv_vdpau.o OBJS-avconv-$(HAVE_DXVA2_LIB) += avconv_dxva2.o OBJS-avconv-$(CONFIG_VDA) += avconv_vda.o OBJS-avconv-$(CONFIG_LIBMFX) += avconv_qsv.o +OBJS-avconv-$(CONFIG_VAAPI) += avconv_vaapi.o TESTTOOLS = audiogen videogen rotozoom tiny_psnr base64 HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options diff --git a/avconv.c b/avconv.c index 68e35151b0..07c81fa2ad 100644 --- a/avconv.c +++ b/avconv.c @@ -2736,6 +2736,8 @@ static int transcode(void) } } + av_buffer_unref(&hw_device_ctx); + /* finished ! */ ret = 0; diff --git a/avconv.h b/avconv.h index d8ee44f7b1..05abae6865 100644 --- a/avconv.h +++ b/avconv.h @@ -55,6 +55,7 @@ enum HWAccelID { HWACCEL_DXVA2, HWACCEL_VDA, HWACCEL_QSV, + HWACCEL_VAAPI, }; typedef struct HWAccel { @@ -115,6 +116,8 @@ typedef struct OptionsContext { int nb_hwaccels; SpecifierOpt *hwaccel_devices; int nb_hwaccel_devices; + SpecifierOpt *hwaccel_output_formats; + int nb_hwaccel_output_formats; SpecifierOpt *autorotate; int nb_autorotate; @@ -261,6 +264,7 @@ typedef struct InputStream { /* hwaccel options */ enum HWAccelID hwaccel_id; char *hwaccel_device; + enum AVPixelFormat hwaccel_output_format; /* hwaccel context */ enum HWAccelID active_hwaccel_id; @@ -270,6 +274,7 @@ typedef struct InputStream { int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame); enum AVPixelFormat hwaccel_pix_fmt; enum AVPixelFormat hwaccel_retrieved_pix_fmt; + AVBufferRef *hw_frames_ctx; /* stats */ // combined size of all the packets read @@ -428,6 +433,8 @@ extern const AVIOInterruptCB int_cb; extern const OptionDef options[]; extern const HWAccel hwaccels[]; +extern int hwaccel_lax_profile_check; +extern AVBufferRef *hw_device_ctx; void reset_options(OptionsContext *o); void show_usage(void); @@ -451,5 +458,7 @@ int dxva2_init(AVCodecContext *s); int vda_init(AVCodecContext *s); int qsv_init(AVCodecContext *s); int qsv_transcode_init(OutputStream *ost); +int vaapi_decode_init(AVCodecContext *avctx); +int vaapi_device_init(const char *device); #endif /* AVCONV_H */ diff --git a/avconv_filter.c b/avconv_filter.c index 3ecad56476..a412f6e314 100644 --- a/avconv_filter.c +++ b/avconv_filter.c @@ -307,7 +307,7 @@ static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, if (ret < 0) return ret; - if (codec->width || codec->height) { + if (!hw_device_ctx && (codec->width || codec->height)) { char args[255]; AVFilterContext *filter; @@ -513,6 +513,7 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, par->format = ist->hwaccel_retrieve_data ? ist->hwaccel_retrieved_pix_fmt : ist->dec_ctx->pix_fmt; par->time_base = tb; + par->hw_frames_ctx = ist->hw_frames_ctx; ret = av_buffersrc_parameters_set(ifilter->filter, par); av_freep(&par); @@ -719,6 +720,12 @@ int configure_filtergraph(FilterGraph *fg) if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0) return ret; + if (hw_device_ctx) { + for (i = 0; i < fg->graph->nb_filters; i++) { + fg->graph->filters[i]->hw_device_ctx = av_buffer_ref(hw_device_ctx); + } + } + if (simple && (!inputs || inputs->next || !outputs || outputs->next)) { av_log(NULL, AV_LOG_ERROR, "Simple filtergraph '%s' does not have " "exactly one input and output.\n", graph_desc); diff --git a/avconv_opt.c b/avconv_opt.c index 3df11da9e1..d022879da2 100644 --- a/avconv_opt.c +++ b/avconv_opt.c @@ -67,9 +67,14 @@ const HWAccel hwaccels[] = { #endif #if CONFIG_LIBMFX { "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV }, +#endif +#if CONFIG_VAAPI + { "vaapi", vaapi_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI }, #endif { 0 }, }; +int hwaccel_lax_profile_check = 0; +AVBufferRef *hw_device_ctx; char *vstats_filename; @@ -321,6 +326,17 @@ static int opt_attach(void *optctx, const char *opt, const char *arg) return 0; } +#if CONFIG_VAAPI +static int opt_vaapi_device(void *optctx, const char *opt, const char *arg) +{ + int err; + err = vaapi_device_init(arg); + if (err < 0) + exit_program(1); + return 0; +} +#endif + /** * Parse a metadata specifier passed as 'arg' parameter. * @param arg metadata string to parse @@ -488,6 +504,7 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic) AVCodecParameters *par = st->codecpar; InputStream *ist = av_mallocz(sizeof(*ist)); char *framerate = NULL, *hwaccel = NULL, *hwaccel_device = NULL; + char *hwaccel_output_format = NULL; char *codec_tag = NULL; char *next; @@ -581,6 +598,19 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic) if (!ist->hwaccel_device) exit_program(1); } + + MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, + hwaccel_output_format, ic, st); + if (hwaccel_output_format) { + ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); + if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " + "format: %s", hwaccel_output_format); + } + } else { + ist->hwaccel_output_format = AV_PIX_FMT_NONE; + } + ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; break; @@ -2475,11 +2505,17 @@ const OptionDef options[] = { { "hwaccel_device", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccel_devices) }, "select a device for HW acceleration", "devicename" }, + { "hwaccel_output_format", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | + OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccel_output_formats) }, + "select output format used with HW accelerated decoding", "format" }, + { "hwaccels", OPT_EXIT, { .func_arg = show_hwaccels }, "show available HW acceleration methods" }, { "autorotate", HAS_ARG | OPT_BOOL | OPT_SPEC | OPT_EXPERT | OPT_INPUT, { .off = OFFSET(autorotate) }, "automatically insert correct rotate filters" }, + { "hwaccel_lax_profile_check", OPT_BOOL | OPT_EXPERT, { &hwaccel_lax_profile_check}, + "attempt to decode anyway if HW accelerated decoder's supported profiles do not exactly match the stream" }, /* audio options */ { "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames }, @@ -2535,5 +2571,10 @@ const OptionDef options[] = { { "dcodec", HAS_ARG | OPT_DATA | OPT_PERFILE | OPT_EXPERT | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_data_codec }, "force data codec ('copy' to copy stream)", "codec" }, +#if CONFIG_VAAPI + { "vaapi_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_vaapi_device }, + "set VAAPI hardware device (DRM path or X11 display name)", "device" }, +#endif + { NULL, }, }; 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; +} diff --git a/configure b/configure index 7b0e5b5fd7..f91a5d410b 100755 --- a/configure +++ b/configure @@ -1636,6 +1636,7 @@ HAVE_LIST=" sdl section_data_rel_ro threads + vaapi_drm vaapi_x11 vdpau_x11 xlib @@ -4694,10 +4695,15 @@ enabled vaapi && check_code cc "va/va.h" "vaCreateSurfaces(0, 0, 0, 0, 0, 0, 0, 0)" || disable vaapi -enabled vaapi && enabled xlib && +if enabled vaapi ; then + enabled xlib && check_lib2 "va/va.h va/va_x11.h" vaGetDisplay -lva -lva-x11 && enable vaapi_x11 + check_lib2 "va/va.h va/va_drm.h" vaGetDisplayDRM -lva -lva-drm && + enable vaapi_drm +fi + enabled vdpau && check_cpp_condition vdpau/vdpau.h "defined VDP_DECODER_PROFILE_MPEG4_PART2_ASP" || disable vdpau