diff --git a/modules/LIST b/modules/LIST index 3d121bc26b..c16827bb36 100644 --- a/modules/LIST +++ b/modules/LIST @@ -363,6 +363,7 @@ $Id$ * vcdx: input module for accessing Video CDs with navigation & stills * vda: VDADecoder hardware-accelerated decoding * vdpau: VDPAU hardware-accelerated decoding + * vdpau_avcodec: VDPAU hardware-accelerated decoding * vdummy: dummy video display * visual: visualisation system * vmem: video memory output diff --git a/modules/hw/vdpau/Makefile.am b/modules/hw/vdpau/Makefile.am index f45f1b1c62..c51629faac 100644 --- a/modules/hw/vdpau/Makefile.am +++ b/modules/hw/vdpau/Makefile.am @@ -2,6 +2,7 @@ basedir = vdpau include $(top_srcdir)/modules/common.am AM_CFLAGS += $(VDPAU_CFLAGS) +AM_LIBADD += libvlc_vdpau.la libvlc_vdpau_la_SOURCES = vlc_vdpau.c vlc_vdpau.h instance.c libvlc_vdpau_la_CPPFLAGS = @@ -12,3 +13,15 @@ libvlc_vdpau_la_LDFLAGS = \ -export-symbols-regex ^vdp_ \ -version-info 0:0:0 pkglib_LTLIBRARIES = libvlc_vdpau.la + +libvlc_LTLIBRARIES = + +libvdpau_avcodec_plugin_la_SOURCES = avcodec.c +libvdpau_avcodec_plugin_la_CFLAGS = $(AM_CFLAGS) \ + $(X_CFLAGS) $(CFLAGS_avcodec) +libvdpau_avcodec_plugin_la_LIBADD = $(AM_LIBADD) \ + $(X_LIBS) $(X_PRE_LIBS) -lX11 $(LIBS_avcodec) +libvdpau_avcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_avcodec) +if HAVE_AVCODEC_VDPAU +libvlc_LTLIBRARIES += libvdpau_avcodec_plugin.la +endif diff --git a/modules/hw/vdpau/avcodec.c b/modules/hw/vdpau/avcodec.c new file mode 100644 index 0000000000..56033331fc --- /dev/null +++ b/modules/hw/vdpau/avcodec.c @@ -0,0 +1,429 @@ +/***************************************************************************** + * avcodec.c: VDPAU decoder for libav + ***************************************************************************** + * Copyright (C) 2012-2013 Rémi Denis-Courmont + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vlc_vdpau.h" +#include "../../codec/avcodec/va.h" + +static int Open(vlc_va_t *, int, const es_format_t *); +static void Close(vlc_va_t *); + +vlc_module_begin() + set_description(N_("Video Decode and Presentation API for Unix (VDPAU)")) + set_capability("hw decoder", 100) + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_VCODEC) + set_callbacks(Open, Close) + add_shortcut("vdpau") +vlc_module_end() + +#define MAX_SURFACES (16+1) + +struct vlc_va_sys_t +{ + vdp_t *vdp; + VdpDevice device; + VdpDecoderProfile profile; + VdpYCbCrFormat format; + AVVDPAUContext context; + VdpVideoSurface surfaces[MAX_SURFACES]; + uint32_t available; + vlc_fourcc_t chroma; + uint16_t width; + uint16_t height; +}; + +static int Lock(vlc_va_t *va, AVFrame *ff) +{ + vlc_va_sys_t *sys = va->sys; + + for (unsigned i = 0; i < AV_NUM_DATA_POINTERS; i++) + { + ff->data[i] = NULL; + ff->linesize[i] = 0; + } + + if (!sys->available) + { + msg_Err(va, "no free surfaces"); + return VLC_EGENERIC; + } + + unsigned idx = ctz (sys->available); + sys->available &= ~(1 << idx); + + VdpVideoSurface *surface = sys->surfaces + idx; + assert(*surface != VDP_INVALID_HANDLE); + ff->data[0] = (void *)surface; /* must be non-NULL */ + ff->data[3] = (void *)(uintptr_t)*surface; + ff->opaque = surface; + return VLC_SUCCESS; +} + +static void Unlock(vlc_va_t *va, AVFrame *ff) +{ + vlc_va_sys_t *sys = va->sys; + VdpVideoSurface *surface = ff->opaque; + unsigned idx = surface - sys->surfaces; + + assert(idx < MAX_SURFACES); + sys->available |= (1 << idx); + + ff->data[0] = ff->data[3] = NULL; + ff->opaque = NULL; +} + +static int Copy(vlc_va_t *va, picture_t *pic, AVFrame *ff) +{ + vlc_va_sys_t *sys = va->sys; + VdpVideoSurface *surface = ff->opaque; + void *planes[3]; + uint32_t pitches[3]; + VdpStatus err; + + for (unsigned i = 0; i < 3; i++) + { + planes[i] = pic->p[i].p_pixels; + pitches[i] = pic->p[i].i_pitch; + } + + err = vdp_video_surface_get_bits_y_cb_cr(sys->vdp, *surface, sys->format, + planes, pitches); + if (err != VDP_STATUS_OK) + { + msg_Err(va, "surface copy failure: %s", + vdp_get_error_string(sys->vdp, err)); + return VLC_EGENERIC; + } + return VLC_SUCCESS; +} + +static int Init(vlc_va_t *va, void **ctxp, vlc_fourcc_t *chromap, + int width, int height) +{ + vlc_va_sys_t *sys = va->sys; + VdpStatus err; + + width = (width + 1) & ~1; + height = (height + 3) & ~3; + sys->width = width; + sys->height = height; + + unsigned surfaces = 2; + switch (sys->profile) + { + case VDP_DECODER_PROFILE_H264_BASELINE: + case VDP_DECODER_PROFILE_H264_MAIN: + case VDP_DECODER_PROFILE_H264_HIGH: + surfaces = 16; + break; + } + + err = vdp_decoder_create(sys->vdp, sys->device, sys->profile, width, + height, surfaces, &sys->context.decoder); + if (err != VDP_STATUS_OK) + { + msg_Err(va, "%s creation failure: %s", "decoder", + vdp_get_error_string(sys->vdp, err)); + sys->context.decoder = VDP_INVALID_HANDLE; + return VLC_EGENERIC; + } + + assert (width > 0 && height > 0); + surfaces++; + assert (surfaces <= MAX_SURFACES); + sys->available = 0; + + /* TODO: select better chromas when appropriate */ + for (unsigned i = 0; i < surfaces; i++) + { + err = vdp_video_surface_create(sys->vdp, sys->device, + VDP_CHROMA_TYPE_420, width, height, sys->surfaces + i); + if (err != VDP_STATUS_OK) + { + msg_Err(va, "%s creation failure: %s", "video surface", + vdp_get_error_string(sys->vdp, err)); + sys->surfaces[i] = VDP_INVALID_HANDLE; + break; + } + sys->available |= (1 << i); + } + + *ctxp = &sys->context; + *chromap = sys->chroma; + return VLC_SUCCESS; +} + +static void Deinit(vlc_va_t *va) +{ + vlc_va_sys_t *sys = va->sys; + + for (unsigned i = 0; i < MAX_SURFACES; i++) + { + VdpVideoSurface *surface = sys->surfaces + i; + + if (*surface != VDP_INVALID_HANDLE) + { + vdp_video_surface_destroy(sys->vdp, *surface); + *surface = VDP_INVALID_HANDLE; + } + } + + assert(sys->context.decoder != VDP_INVALID_HANDLE); + vdp_decoder_destroy(sys->vdp, sys->context.decoder); + av_freep(&sys->context.bitstream_buffers); +} + +static int Setup(vlc_va_t *va, void **ctxp, vlc_fourcc_t *chromap, + int width, int height) +{ + vlc_va_sys_t *sys = va->sys; + + if (sys->context.decoder != VDP_INVALID_HANDLE) + { + if (sys->width == width && sys->height == height) + return VLC_SUCCESS; + Deinit(va); + sys->context.decoder = VDP_INVALID_HANDLE; + } + + return Init(va, ctxp, chromap, width, height); +} + +static int Open(vlc_va_t *va, int codec, const es_format_t *fmt) +{ + VdpStatus err; + VdpDecoderProfile profile; + int level; + + switch (codec) + { + case AV_CODEC_ID_MPEG1VIDEO: + profile = VDP_DECODER_PROFILE_MPEG1; + level = VDP_DECODER_LEVEL_MPEG1_NA; + break; + + case AV_CODEC_ID_MPEG2VIDEO: + switch (fmt->i_profile) + { + case FF_PROFILE_MPEG2_MAIN: + profile = VDP_DECODER_PROFILE_MPEG2_MAIN; + break; + case FF_PROFILE_MPEG2_SIMPLE: + profile = VDP_DECODER_PROFILE_MPEG2_SIMPLE; + break; + default: + msg_Err(va, "unsupported %s profile %d", "MPEG2", fmt->i_profile); + return VLC_EGENERIC; + } + level = VDP_DECODER_LEVEL_MPEG2_HL; + break; + + case AV_CODEC_ID_H263: + profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP; + level = VDP_DECODER_LEVEL_MPEG4_PART2_ASP_L5; + break; + case AV_CODEC_ID_MPEG4: + switch (fmt->i_profile) + { + case FF_PROFILE_MPEG4_SIMPLE: + profile = VDP_DECODER_PROFILE_MPEG4_PART2_SP; + break; + case FF_PROFILE_MPEG4_SIMPLE_STUDIO: + msg_Err(va, "unsupported %s profile %d", "MPEG4", fmt->i_profile); + return VLC_EGENERIC; + default: + profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP; + break; + } + level = fmt->i_level; + break; + + case AV_CODEC_ID_H264: + switch (fmt->i_profile + & ~(FF_PROFILE_H264_CONSTRAINED|FF_PROFILE_H264_INTRA)) + { + case FF_PROFILE_H264_BASELINE: + profile = VDP_DECODER_PROFILE_H264_BASELINE; + break; + case FF_PROFILE_H264_MAIN: + profile = VDP_DECODER_PROFILE_H264_MAIN; + break; + case FF_PROFILE_H264_HIGH: + profile = VDP_DECODER_PROFILE_H264_HIGH; + break; + case FF_PROFILE_H264_EXTENDED: + default: + msg_Err(va, "unsupported %s profile %d", "H.264", fmt->i_profile); + return VLC_EGENERIC; + } + level = fmt->i_level; + if ((fmt->i_profile & FF_PROFILE_H264_INTRA) && (fmt->i_level == 11)) + level = VDP_DECODER_LEVEL_H264_1b; + break; + + case AV_CODEC_ID_WMV3: + case AV_CODEC_ID_VC1: + switch (fmt->i_profile) + { + case FF_PROFILE_VC1_SIMPLE: + profile = VDP_DECODER_PROFILE_VC1_SIMPLE; + break; + case FF_PROFILE_VC1_MAIN: + profile = VDP_DECODER_PROFILE_VC1_MAIN; + break; + case FF_PROFILE_VC1_ADVANCED: + profile = VDP_DECODER_PROFILE_VC1_ADVANCED; + break; + default: + msg_Err(va, "unsupported %s profile %d", "VC-1", fmt->i_profile); + return VLC_EGENERIC; + } + level = fmt->i_level; + break; + + default: + msg_Err(va, "unknown codec %d", codec); + return VLC_EGENERIC; + } + + if (!vlc_xlib_init(VLC_OBJECT(va))) + { + msg_Err(va, "Xlib is required for VDPAU"); + return VLC_EGENERIC; + } + + vlc_va_sys_t *sys = malloc(sizeof (*sys)); + if (unlikely(sys == NULL)) + return VLC_ENOMEM; + + sys->profile = profile; + memset(&sys->context, 0, sizeof (sys->context)); + sys->context.decoder = VDP_INVALID_HANDLE; + for (unsigned i = 0; i < MAX_SURFACES; i++) + sys->surfaces[i] = VDP_INVALID_HANDLE; + + err = vdp_get_x11(NULL, -1, &sys->vdp, &sys->device); + if (err != VDP_STATUS_OK) + { + free(sys); + return VLC_EGENERIC; + } + + void *func; + err = vdp_get_proc_address(sys->vdp, sys->device, + VDP_FUNC_ID_DECODER_RENDER, &func); + if (err != VDP_STATUS_OK) + goto error; + sys->context.render = func; + + /* Check capabilities */ + VdpBool support; + uint32_t lvl, mb, width, height; + + if (vdp_video_surface_query_capabilities(sys->vdp, sys->device, + VDP_CHROMA_TYPE_420, &support, &width, &height) != VDP_STATUS_OK) + support = VDP_FALSE; + if (!support || width < fmt->video.i_width || height < fmt->video.i_height) + { + msg_Err(va, "video surface not supported: %s %ux%u", + "YUV 4:2:0", fmt->video.i_width, fmt->video.i_height); + goto error; + } + msg_Dbg(va, "video surface supported maximum: %s %"PRIu32"x%"PRIu32, + "YUV 4:2:0", width, height); + + if (vdp_video_surface_query_get_put_bits_y_cb_cr_capabilities(sys->vdp, + sys->device, VDP_CHROMA_TYPE_420, VDP_YCBCR_FORMAT_YV12, &support) + == VDP_STATUS_OK && support == VDP_TRUE) + { + sys->format = VDP_YCBCR_FORMAT_YV12; + sys->chroma = VLC_CODEC_YV12; + } + else + if (vdp_video_surface_query_get_put_bits_y_cb_cr_capabilities(sys->vdp, + sys->device, VDP_CHROMA_TYPE_420, VDP_YCBCR_FORMAT_NV12, &support) + == VDP_STATUS_OK && support == VDP_TRUE) + { + sys->format = VDP_YCBCR_FORMAT_NV12; + sys->chroma = VLC_CODEC_NV12; + } + else + { + msg_Err(va, "video surface reading not supported: %s", "YUV 4:2:0"); + goto error; + } + + if (vdp_decoder_query_capabilities(sys->vdp, sys->device, profile, + &support, &lvl, &mb, &width, &height) != VDP_STATUS_OK) + support = VDP_FALSE; + if (!support || (int)lvl < level + || width < fmt->video.i_width || height < fmt->video.i_height) + { + msg_Err(va, "decoding profile not supported: %"PRIu32".%d %ux%u", + profile, lvl, fmt->video.i_width, fmt->video.i_height); + goto error; + } + msg_Dbg(va, "decoding profile supported maximum: %"PRIu32".%"PRIu32" mb %" + PRIu32", %"PRIu32"x%"PRIu32, profile, lvl, mb, width, height); + + const char *infos; + if (vdp_get_information_string(sys->vdp, &infos) != VDP_STATUS_OK) + infos = "VDPAU"; + + va->sys = sys; + va->description = (char *)infos; + va->pix_fmt = AV_PIX_FMT_VDPAU; + va->setup = Setup; + va->get = Lock; + va->release = Unlock; + va->extract = Copy; + return VLC_SUCCESS; + +error: + vdp_release_x11(sys->vdp); + free(sys); + return VLC_EGENERIC; +} + +static void Close(vlc_va_t *va) +{ + vlc_va_sys_t *sys = va->sys; + + if (sys->context.decoder != VDP_INVALID_HANDLE) + Deinit(va); + vdp_release_x11(sys->vdp); + free(sys); +}