From 4fce2292bb77ecac6130725418f9b82fd305ce75 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 16 Aug 2015 21:05:16 +0200 Subject: [PATCH] Add support for VDPAU video acceleration --- CMakeLists.txt | 15 ++++ src/video/ffmpeg.c | 51 +++++++++--- src/video/ffmpeg_vdpau.c | 176 +++++++++++++++++++++++++++++++++++++++ src/video/ffmpeg_vdpau.h | 23 +++++ 4 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 src/video/ffmpeg_vdpau.c create mode 100644 src/video/ffmpeg_vdpau.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 540e6e8..5bb78fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,9 +26,16 @@ pkg_check_modules(SDL sdl2>=2.0.4) pkg_check_modules(AVCODEC libavcodec) pkg_check_modules(AVUTIL libavutil) pkg_check_modules(SWSCALE libswscale) +pkg_check_modules(XLIB x11-xcb) +pkg_check_modules(LIBVA vdpau) if(AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND AND SDL_FOUND) set(SOFTWARE_FOUND TRUE) + if(XLIB_FOUND AND LIBVA_FOUND) + set(VDPAU_FOUND TRUE) + else() + set(VDPAU_FOUND FALSE) + endif() else() set(SOFTWARE_FOUND FALSE) endif() @@ -46,6 +53,10 @@ endif() if (SOFTWARE_FOUND) list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c ./src/audio/sdl.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) + if(VDPAU_FOUND) + list(APPEND SRC_LIST ./src/video/ffmpeg_vdpau.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_VDPAU) + endif() endif() if (BROADCOM_FOUND OR FREESCALE_FOUND OR CMAKE_BUILD_TYPE MATCHES Debug) @@ -87,6 +98,10 @@ endif() if (SOFTWARE_FOUND) target_include_directories(moonlight PRIVATE ${SDL_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS}) target_link_libraries(moonlight ${SDL_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES}) + if(VDPAU_FOUND) + target_include_directories(moonlight PRIVATE ${XLIB_INCLUDE_DIRS} ${LIBVA_INCLUDE_DIRS}) + target_link_libraries(moonlight ${XLIB_LIBRARIES} ${LIBVA_LIBRARIES}) + endif() endif() set_property(TARGET moonlight PROPERTY COMPILE_DEFINITIONS ${MOONLIGHT_DEFINITIONS}) diff --git a/src/video/ffmpeg.c b/src/video/ffmpeg.c index cfd6f35..39f04ce 100644 --- a/src/video/ffmpeg.c +++ b/src/video/ffmpeg.c @@ -19,10 +19,15 @@ #include "ffmpeg.h" +#ifdef HAVE_VDPAU +#include "ffmpeg_vdpau.h" +#endif + #include #include #include #include +#include // General decoder and renderer state static AVPacket pkt; @@ -31,6 +36,9 @@ static AVCodecContext* decoder_ctx; static AVFrame* dec_frame; static struct SwsContext* scaler_ctx; +enum decoders {SOFTWARE, VDPAU}; +enum decoders decoder_system; + #define BYTES_PER_PIXEL 4 // This function must be called before @@ -42,10 +50,19 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) { av_init_packet(&pkt); - decoder = avcodec_find_decoder(AV_CODEC_ID_H264); + #ifdef HAVE_VDPAU + decoder = avcodec_find_decoder_by_name("h264_vdpau"); + if (decoder != NULL) + decoder_system = VDPAU; + #endif + if (decoder == NULL) { - printf("Couldn't find H264 decoder"); - return -1; + decoder_system = SOFTWARE; + decoder = avcodec_find_decoder_by_name("h264"); + if (decoder == NULL) { + printf("Couldn't find decoder\n"); + return -1; + } } decoder_ctx = avcodec_alloc_context3(decoder); @@ -84,7 +101,12 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) { printf("Couldn't allocate frame"); return -1; } - + + #ifdef HAVE_VDPAU + if (decoder_system == VDPAU) + vdpau_init(decoder_ctx, width, height); + #endif + int filtering; if (perf_lvl & FAST_BILINEAR_FILTERING) filtering = SWS_FAST_BILINEAR; @@ -93,10 +115,12 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) { else filtering = SWS_BICUBIC; - scaler_ctx = sws_getContext(decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, decoder_ctx->width, decoder_ctx->height, PIX_FMT_YUV420P, filtering, NULL, NULL, NULL); - if (scaler_ctx == NULL) { - printf("Couldn't get scaler context"); - return -1; + if (decoder_system == SOFTWARE) { + scaler_ctx = sws_getContext(decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, decoder_ctx->width, decoder_ctx->height, PIX_FMT_YUV420P, filtering, NULL, NULL, NULL); + if (scaler_ctx == NULL) { + printf("Couldn't get scaler context\n"); + return -1; + } } return 0; @@ -132,7 +156,12 @@ int ffmpeg_draw_frame(AVFrame *pict) { } AVFrame* ffmpeg_get_frame() { - return dec_frame; + if (decoder_system == SOFTWARE) + return dec_frame; + #ifdef HAVE_VDPAU + else if (decoder_system == VDPAU) + return vdpau_get_frame(dec_frame); + #endif } // packets must be decoded in order @@ -148,7 +177,9 @@ int ffmpeg_decode(unsigned char* indata, int inlen) { got_pic = 0; err = avcodec_decode_video2(decoder_ctx, dec_frame, &got_pic, &pkt); if (err < 0) { - fprintf(stderr, "Decode failed\n"); + char errorstring[512]; + av_strerror(err, errorstring, sizeof(errorstring)); + fprintf(stderr, "Decode failed - %s\n", errorstring); got_pic = 0; break; } diff --git a/src/video/ffmpeg_vdpau.c b/src/video/ffmpeg_vdpau.c new file mode 100644 index 0000000..c8c9cc2 --- /dev/null +++ b/src/video/ffmpeg_vdpau.c @@ -0,0 +1,176 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2003-2014 Ulrich von Zadow + * + * Based on Libavg VDPAU implementation + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include "ffmpeg_vdpau.h" + +#include +#include +#include +#include + +#define MAX_RENDER_STATES 32 + +static AVFrame* cpu_frame; + +static VdpDevice vdp_device; +static VdpDecoder vdp_decoder; +static VdpVideoMixer vdp_mixer; +static struct vdpau_render_state* vdp_render_state[MAX_RENDER_STATES]; +static int vdp_render_states = 0; + +static VdpGetProcAddress* vdp_get_proc_address; +static VdpDecoderCreate* vdp_decoder_create; +static VdpDecoderRender* vdp_decoder_render; +static VdpVideoSurfaceGetBitsYCbCr* vdp_video_surface_get_bits_y_cb_cr; +static VdpVideoSurfaceCreate* vdp_video_surface_create; +static VdpVideoMixerCreate* vdp_video_mixer_create; + +struct vdpau_render_state* vdp_get_free_render_state() { + for (unsigned i = 0; i < vdp_render_states; i++) { + struct vdpau_render_state* render_state = vdp_render_state[i]; + if (!render_state->state) + return vdp_render_state[i]; + } + + if (vdp_render_states == MAX_RENDER_STATES) + return NULL; + + // No free surfaces available + struct vdpau_render_state* render_state = malloc(sizeof(struct vdpau_render_state)); + vdp_render_state[vdp_render_states] = render_state; + vdp_render_states++; + memset(render_state, 0, sizeof(struct vdpau_render_state)); + render_state->surface = VDP_INVALID_HANDLE; + VdpStatus status = vdp_video_surface_create(vdp_device, VDP_CHROMA_TYPE_420, 1280, 720, &render_state->surface); + return render_state; +} + +static int vdp_get_buffer(AVCodecContext* context, AVFrame* frame) { + struct vdpau_render_state* pRenderState = vdp_get_free_render_state(); + frame->data[0] = (uint8_t*) pRenderState; + frame->type = FF_BUFFER_TYPE_USER; + + pRenderState->state |= FF_VDPAU_STATE_USED_FOR_RENDER; + return 0; +} + +static void vdp_release_buffer(AVCodecContext* context, AVFrame* frame) { + struct vdpau_render_state *render_state = (struct vdpau_render_state *)frame->data[0]; + render_state->state = 0; + frame->data[0] = 0; +} + +static enum AVPixelFormat vdp_get_format(AVCodecContext* context, const enum AVPixelFormat* pixel_format) { + return PIX_FMT_VDPAU_H264; +} + +static void vdp_draw_horiz_band(struct AVCodecContext* context, const AVFrame* frame, int offset[4], int y, int type, int height) { + struct vdpau_render_state* render_state = (struct vdpau_render_state*)frame->data[0]; + + VdpStatus status = vdp_decoder_render(vdp_decoder, render_state->surface, (VdpPictureInfo const*)&(render_state->info), render_state->bitstream_buffers_used, render_state->bitstream_buffers); + status = vdp_decoder_render(vdp_decoder, render_state->surface, (VdpPictureInfo const*)&(render_state->info), render_state->bitstream_buffers_used, render_state->bitstream_buffers); +} + +int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) { + if (vdp_device) + return vdp_device; + + Display* xdisplay = XOpenDisplay(0); + if (!xdisplay) + return -1; + + VdpStatus status = vdp_device_create_x11(xdisplay, DefaultScreen(xdisplay), &vdp_device, &vdp_get_proc_address); + if (status != VDP_STATUS_OK) + return -1; + + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, (void**)&vdp_video_surface_get_bits_y_cb_cr); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_SURFACE_CREATE, (void**)&vdp_video_surface_create); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DECODER_RENDER, (void**)&vdp_decoder_render); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DECODER_CREATE, (void**)&vdp_decoder_create); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_MIXER_CREATE, (void**)&vdp_video_mixer_create); + + decoder_ctx->get_buffer = vdp_get_buffer; + decoder_ctx->release_buffer = vdp_release_buffer; + decoder_ctx->draw_horiz_band = vdp_draw_horiz_band; + decoder_ctx->get_format = vdp_get_format; + decoder_ctx->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; + + cpu_frame = av_frame_alloc(); + if (cpu_frame == NULL) { + printf("Couldn't allocate frame\n"); + return -1; + } + cpu_frame->format = PIX_FMT_YUV420P; + cpu_frame->width = width; + cpu_frame->height = height; + av_frame_get_buffer(cpu_frame, 32); + + if (!vdp_device) { + printf("Can't get VDPAU device\n"); + return -1; + } + + status = vdp_decoder_create(vdp_device, VDP_DECODER_PROFILE_H264_HIGH, width, height, 16, &vdp_decoder); + if (status != VDP_STATUS_OK) { + printf("Can't create VDPAU decoder\n"); + return -1; + } + + VdpVideoMixerFeature features[] = { + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL, + }; + VdpVideoMixerParameter params[] = { + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT, + VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE, + VDP_VIDEO_MIXER_PARAMETER_LAYERS + }; + VdpChromaType chroma = VDP_CHROMA_TYPE_420; + int numLayers = 0; + void const* paramValues[] = { &width, &height, &chroma, &numLayers }; + + status = vdp_video_mixer_create(vdp_device, 0, features, 4, params, paramValues, &vdp_mixer); + if (status != VDP_STATUS_OK) { + printf("Can't create VDPAU mixer\n"); + return -1; + } + + return vdp_device; +} + +AVFrame* vdpau_get_frame(AVFrame* dec_frame) { + struct vdpau_render_state *render_state = (struct vdpau_render_state *)dec_frame->data[0]; + void *dest[3] = { + cpu_frame->data[0], + cpu_frame->data[2], + cpu_frame->data[1] + }; + uint32_t pitches[3] = { + cpu_frame->linesize[0], + cpu_frame->linesize[2], + cpu_frame->linesize[1] + }; + + VdpStatus status = vdp_video_surface_get_bits_y_cb_cr(render_state->surface, VDP_YCBCR_FORMAT_YV12, dest, pitches); + return cpu_frame; +} diff --git a/src/video/ffmpeg_vdpau.h b/src/video/ffmpeg_vdpau.h new file mode 100644 index 0000000..13f2b69 --- /dev/null +++ b/src/video/ffmpeg_vdpau.h @@ -0,0 +1,23 @@ +/* + * This file is part of Moonlight Embedded. + * + * Based on Moonlight Pc implementation + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include + +int vdpau_init(AVCodecContext* decoder_ctx, int width, int height); +AVFrame* vdpau_get_frame(AVFrame* dec_frame);