diff --git a/Changelog b/Changelog index ae0cb36b06..fef0d14899 100644 --- a/Changelog +++ b/Changelog @@ -8,6 +8,7 @@ version - setsar/setdar filters now support variables in ratio expressions - elbg filter - string validation in ffprobe +- support for decoding through VDPAU in ffmpeg (the -hwaccel option) version 2.1: diff --git a/Makefile b/Makefile index a44a29a9c1..10202d0588 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,9 @@ PROGS-$(CONFIG_FFSERVER) += ffserver PROGS := $(PROGS-yes:%=%$(PROGSSUF)$(EXESUF)) INSTPROGS = $(PROGS-yes:%=%$(PROGSSUF)$(EXESUF)) + OBJS-ffmpeg = ffmpeg_opt.o ffmpeg_filter.o +OBJS-ffmpeg-$(HAVE_VDPAU_X11) += ffmpeg_vdpau.o TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options TOOLS = qt-faststart trasher @@ -90,7 +92,7 @@ endef $(foreach D,$(FFLIBS),$(eval $(call DOSUBDIR,lib$(D)))) define DOPROG -OBJS-$(1) += $(1).o cmdutils.o $(EXEOBJS) +OBJS-$(1) += $(1).o cmdutils.o $(EXEOBJS) $(OBJS-$(1)-yes) $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1)) $$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1)) $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1)) diff --git a/configure b/configure index e4eb01cc32..1e138fa9dc 100755 --- a/configure +++ b/configure @@ -1559,11 +1559,13 @@ HAVE_LIST=" threads unistd_h usleep + vdpau_x11 vfp_args VirtualAlloc windows_h winsock2_h xform_asm + xlib xmm_clobbers " @@ -4426,10 +4428,12 @@ if enabled libcdio; then die "ERROR: libcdio-paranoia not found" fi +check_lib X11/Xlib.h XOpenDisplay -lX11 && enable xlib + enabled x11grab && -require X11 X11/Xlib.h XOpenDisplay -lX11 && require Xext X11/extensions/XShm.h XShmCreateImage -lXext && -require Xfixes X11/extensions/Xfixes.h XFixesGetCursorImage -lXfixes +require Xfixes X11/extensions/Xfixes.h XFixesGetCursorImage -lXfixes && +{ enabled xlib || die "ERROR: Xlib not found"; } enabled vaapi && check_lib va/va.h vaInitialize -lva || @@ -4439,6 +4443,10 @@ enabled vdpau && check_cpp_condition vdpau/vdpau.h "defined VDP_DECODER_PROFILE_MPEG4_PART2_ASP" || disable vdpau +enabled vdpau && enabled xlib && + check_lib2 "vdpau/vdpau.h vdpau/vdpau_x11.h" vdp_device_create_x11 -lvdpau && + enable vdpau_x11 + # Funny iconv installations are not unusual, so check it after all flags have been set disabled iconv || check_func_headers iconv.h iconv || check_lib2 iconv.h iconv -liconv || disable iconv diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index 1a2a801770..18a858baa4 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -631,6 +631,9 @@ Do not use any hardware acceleration (the default). @item auto Automatically select the hardware acceleration method. + +@item vdpau +Use VDPAU (Video Decode and Presentation API for Unix) hardware acceleration. @end table This option has no effect if the selected hwaccel is not available or not @@ -648,6 +651,12 @@ Select a device to use for hardware acceleration. This option only makes sense when the @option{-hwaccel} option is also specified. Its exact meaning depends on the specific hardware acceleration method chosen. + +@table @option +@item vdpau +For VDPAU, this option specifies the X11 display/screen to use. If this option +is not specified, the value of the @var{DISPLAY} environment variable is used +@end table @end table @section Audio Options diff --git a/ffmpeg.h b/ffmpeg.h index ff296fc854..433baf84fa 100644 --- a/ffmpeg.h +++ b/ffmpeg.h @@ -59,6 +59,7 @@ enum HWAccelID { HWACCEL_NONE = 0, HWACCEL_AUTO, + HWACCEL_VDPAU, }; typedef struct HWAccel { @@ -485,4 +486,6 @@ FilterGraph *init_simple_filtergraph(InputStream *ist, OutputStream *ost); int ffmpeg_parse_options(int argc, char **argv); +int vdpau_init(AVCodecContext *s); + #endif /* FFMPEG_H */ diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c index 8e01c685d1..dc70c8b6a5 100644 --- a/ffmpeg_opt.c +++ b/ffmpeg_opt.c @@ -64,6 +64,9 @@ } const HWAccel hwaccels[] = { +#if HAVE_VDPAU_X11 + { "vdpau", vdpau_init, HWACCEL_VDPAU, AV_PIX_FMT_VDPAU }, +#endif { 0 }, }; diff --git a/ffmpeg_vdpau.c b/ffmpeg_vdpau.c new file mode 100644 index 0000000000..fe0930695e --- /dev/null +++ b/ffmpeg_vdpau.c @@ -0,0 +1,335 @@ +/* + * 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 + +#include +#include + +#include + +#include "ffmpeg.h" + +#include "libavcodec/vdpau.h" + +#include "libavutil/avassert.h" +#include "libavutil/buffer.h" +#include "libavutil/frame.h" +#include "libavutil/pixfmt.h" + +typedef struct VDPAUContext { + Display *dpy; + + VdpDevice device; + VdpDecoder decoder; + VdpGetProcAddress *get_proc_address; + + VdpGetErrorString *get_error_string; + VdpGetInformationString *get_information_string; + VdpDeviceDestroy *device_destroy; + VdpDecoderCreate *decoder_create; + VdpDecoderDestroy *decoder_destroy; + VdpDecoderRender *decoder_render; + VdpVideoSurfaceCreate *video_surface_create; + VdpVideoSurfaceDestroy *video_surface_destroy; + VdpVideoSurfaceGetBitsYCbCr *video_surface_get_bits; + VdpVideoSurfaceGetParameters *video_surface_get_parameters; + VdpVideoSurfaceQueryGetPutBitsYCbCrCapabilities *video_surface_query; + + AVFrame *tmp_frame; + + enum AVPixelFormat pix_fmt; + VdpYCbCrFormat vdpau_format; +} VDPAUContext; + +static void vdpau_uninit(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + VDPAUContext *ctx = ist->hwaccel_ctx; + + ist->hwaccel_uninit = NULL; + ist->hwaccel_get_buffer = NULL; + ist->hwaccel_retrieve_data = NULL; + + if (ctx->decoder_destroy) + ctx->decoder_destroy(ctx->decoder); + + if (ctx->device_destroy) + ctx->device_destroy(ctx->device); + + if (ctx->dpy) + XCloseDisplay(ctx->dpy); + + av_frame_free(&ctx->tmp_frame); + + av_freep(&ist->hwaccel_ctx); + av_freep(&s->hwaccel_context); +} + +static void vdpau_release_buffer(void *opaque, uint8_t *data) +{ + VdpVideoSurface surface = *(VdpVideoSurface*)data; + VDPAUContext *ctx = opaque; + + ctx->video_surface_destroy(surface); + av_freep(&data); +} + +static int vdpau_get_buffer(AVCodecContext *s, AVFrame *frame, int flags) +{ + InputStream *ist = s->opaque; + VDPAUContext *ctx = ist->hwaccel_ctx; + VdpVideoSurface *surface; + VdpStatus err; + + av_assert0(frame->format == AV_PIX_FMT_VDPAU); + + surface = av_malloc(sizeof(*surface)); + if (!surface) + return AVERROR(ENOMEM); + + frame->buf[0] = av_buffer_create((uint8_t*)surface, sizeof(*surface), + vdpau_release_buffer, ctx, + AV_BUFFER_FLAG_READONLY); + if (!frame->buf[0]) { + av_freep(&surface); + return AVERROR(ENOMEM); + } + + // properly we should keep a pool of surfaces instead of creating + // them anew for each frame, but since we don't care about speed + // much in this code, we don't bother + err = ctx->video_surface_create(ctx->device, VDP_CHROMA_TYPE_420, + frame->width, frame->height, surface); + if (err != VDP_STATUS_OK) { + av_log(NULL, AV_LOG_ERROR, "Error allocating a VDPAU video surface: %s\n", + ctx->get_error_string(err)); + av_buffer_unref(&frame->buf[0]); + return AVERROR_UNKNOWN; + } + + frame->data[3] = (uint8_t*)(uintptr_t)*surface; + + return 0; +} + +static int vdpau_retrieve_data(AVCodecContext *s, AVFrame *frame) +{ + VdpVideoSurface surface = (VdpVideoSurface)(uintptr_t)frame->data[3]; + InputStream *ist = s->opaque; + VDPAUContext *ctx = ist->hwaccel_ctx; + VdpStatus err; + int ret, chroma_type; + + err = ctx->video_surface_get_parameters(surface, &chroma_type, + &ctx->tmp_frame->width, + &ctx->tmp_frame->height); + if (err != VDP_STATUS_OK) { + av_log(NULL, AV_LOG_ERROR, "Error getting surface parameters: %s\n", + ctx->get_error_string(err)); + return AVERROR_UNKNOWN; + } + ctx->tmp_frame->format = ctx->pix_fmt; + + ret = av_frame_get_buffer(ctx->tmp_frame, 32); + if (ret < 0) + return ret; + + ctx->tmp_frame->width = frame->width; + ctx->tmp_frame->height = frame->height; + + err = ctx->video_surface_get_bits(surface, ctx->vdpau_format, + (void * const *)ctx->tmp_frame->data, + ctx->tmp_frame->linesize); + if (err != VDP_STATUS_OK) { + av_log(NULL, AV_LOG_ERROR, "Error retrieving frame data from VDPAU: %s\n", + ctx->get_error_string(err)); + ret = AVERROR_UNKNOWN; + goto fail; + } + + if (ctx->vdpau_format == VDP_YCBCR_FORMAT_YV12) + FFSWAP(uint8_t*, ctx->tmp_frame->data[1], ctx->tmp_frame->data[2]); + + ret = av_frame_copy_props(ctx->tmp_frame, frame); + if (ret < 0) + goto fail; + + av_frame_unref(frame); + av_frame_move_ref(frame, ctx->tmp_frame); + return 0; + +fail: + av_frame_unref(ctx->tmp_frame); + return ret; +} + +static const int vdpau_formats[][2] = { + { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV420P }, + { VDP_YCBCR_FORMAT_NV12, AV_PIX_FMT_NV12 }, + { VDP_YCBCR_FORMAT_YUYV, AV_PIX_FMT_YUYV422 }, + { VDP_YCBCR_FORMAT_UYVY, AV_PIX_FMT_UYVY422 }, +}; + +static int vdpau_alloc(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR; + AVVDPAUContext *vdpau_ctx; + VDPAUContext *ctx; + const char *display, *vendor; + VdpStatus err; + int i; + + ctx = av_mallocz(sizeof(*ctx)); + if (!ctx) + return AVERROR(ENOMEM); + + ist->hwaccel_ctx = ctx; + ist->hwaccel_uninit = vdpau_uninit; + ist->hwaccel_get_buffer = vdpau_get_buffer; + ist->hwaccel_retrieve_data = vdpau_retrieve_data; + + ctx->tmp_frame = av_frame_alloc(); + if (!ctx->tmp_frame) + goto fail; + + ctx->dpy = XOpenDisplay(ist->hwaccel_device); + if (!ctx->dpy) { + av_log(NULL, loglevel, "Cannot open the X11 display %s.\n", + XDisplayName(ist->hwaccel_device)); + goto fail; + } + display = XDisplayString(ctx->dpy); + + err = vdp_device_create_x11(ctx->dpy, XDefaultScreen(ctx->dpy), &ctx->device, + &ctx->get_proc_address); + if (err != VDP_STATUS_OK) { + av_log(NULL, loglevel, "VDPAU device creation on X11 display %s failed.\n", + display); + goto fail; + } + +#define GET_CALLBACK(id, result) \ +do { \ + void *tmp; \ + err = ctx->get_proc_address(ctx->device, id, &tmp); \ + if (err != VDP_STATUS_OK) { \ + av_log(NULL, loglevel, "Error getting the " #id " callback.\n"); \ + goto fail; \ + } \ + ctx->result = tmp; \ +} while (0) + + GET_CALLBACK(VDP_FUNC_ID_GET_ERROR_STRING, get_error_string); + GET_CALLBACK(VDP_FUNC_ID_GET_INFORMATION_STRING, get_information_string); + GET_CALLBACK(VDP_FUNC_ID_DEVICE_DESTROY, device_destroy); + GET_CALLBACK(VDP_FUNC_ID_DECODER_CREATE, decoder_create); + GET_CALLBACK(VDP_FUNC_ID_DECODER_DESTROY, decoder_destroy); + GET_CALLBACK(VDP_FUNC_ID_DECODER_RENDER, decoder_render); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_CREATE, video_surface_create); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY, video_surface_destroy); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, video_surface_get_bits); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, video_surface_get_parameters); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_QUERY_GET_PUT_BITS_Y_CB_CR_CAPABILITIES, + video_surface_query); + + for (i = 0; i < FF_ARRAY_ELEMS(vdpau_formats); i++) { + VdpBool supported; + err = ctx->video_surface_query(ctx->device, VDP_CHROMA_TYPE_420, + vdpau_formats[i][0], &supported); + if (err != VDP_STATUS_OK) { + av_log(NULL, loglevel, + "Error querying VDPAU surface capabilities: %s\n", + ctx->get_error_string(err)); + goto fail; + } + if (supported) + break; + } + if (i == FF_ARRAY_ELEMS(vdpau_formats)) { + av_log(NULL, loglevel, + "No supported VDPAU format for retrieving the data.\n"); + return AVERROR(EINVAL); + } + ctx->vdpau_format = vdpau_formats[i][0]; + ctx->pix_fmt = vdpau_formats[i][1]; + + vdpau_ctx = av_vdpau_alloc_context(); + if (!vdpau_ctx) + goto fail; + vdpau_ctx->render = ctx->decoder_render; + + s->hwaccel_context = vdpau_ctx; + + ctx->get_information_string(&vendor); + av_log(NULL, AV_LOG_VERBOSE, "Using VDPAU -- %s -- on X11 display %s, " + "to decode input stream #%d:%d.\n", vendor, + display, ist->file_index, ist->st->index); + + return 0; + +fail: + av_log(NULL, loglevel, "VDPAU init failed for stream #%d:%d.\n", + ist->file_index, ist->st->index); + vdpau_uninit(s); + return AVERROR(EINVAL); +} + +int vdpau_init(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR; + AVVDPAUContext *vdpau_ctx; + VDPAUContext *ctx; + VdpStatus err; + int profile, ret; + + if (!ist->hwaccel_ctx) { + ret = vdpau_alloc(s); + if (ret < 0) + return ret; + } + ctx = ist->hwaccel_ctx; + vdpau_ctx = s->hwaccel_context; + + ret = av_vdpau_get_profile(s, &profile); + if (ret < 0) { + av_log(NULL, loglevel, "No known VDPAU decoder profile for this stream.\n"); + return AVERROR(EINVAL); + } + + if (ctx->decoder) + ctx->decoder_destroy(ctx->decoder); + + err = ctx->decoder_create(ctx->device, profile, + s->coded_width, s->coded_height, + 16, &ctx->decoder); + if (err != VDP_STATUS_OK) { + av_log(NULL, loglevel, "Error creating the VDPAU decoder: %s\n", + ctx->get_error_string(err)); + return AVERROR_UNKNOWN; + } + + vdpau_ctx->decoder = ctx->decoder; + + ist->hwaccel_get_buffer = vdpau_get_buffer; + ist->hwaccel_retrieve_data = vdpau_retrieve_data; + + return 0; +}