Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575/* * This file is part of mpv. * * mpv 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 2 of the License, or * (at your option) any later version. * * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <assert.h>
#include <libavutil/hwcontext.h>#include <libavutil/hwcontext_vdpau.h>
#include "vdpau.h"
#include "osdep/threads.h"#include "osdep/timer.h"
#include "video/out/x11_common.h"#include "img_format.h"#include "mp_image.h"#include "mp_image_pool.h"#include "vdpau_mixer.h"
static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx){ for (int i = 0; i < MAX_VIDEO_SURFACES; i++) { ctx->video_surfaces[i].surface = VDP_INVALID_HANDLE; ctx->video_surfaces[i].osurface = VDP_INVALID_HANDLE; ctx->video_surfaces[i].allocated = false; } ctx->vdp_device = VDP_INVALID_HANDLE; ctx->preemption_obj = VDP_INVALID_HANDLE;}
static void preemption_callback(VdpDevice device, void *context){ struct mp_vdpau_ctx *ctx = context;
mp_mutex_lock(&ctx->preempt_lock); ctx->is_preempted = true; mp_mutex_unlock(&ctx->preempt_lock);}
static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx, bool probing){ Display *x11 = ctx->x11; VdpStatus vdp_st;
// Don't operate on ctx->vdp directly, so that even if init fails, ctx->vdp // will have the function pointers from the previous successful init, and // won't randomly make other code crash on calling NULL pointers. struct vdp_functions vdp = {0};
if (!x11) return -1;
struct vdp_function { const int id; int offset; };
static const struct vdp_function vdp_func[] = {#define VDP_FUNCTION(_, macro_name, mp_name) {macro_name, offsetof(struct vdp_functions, mp_name)},#include "video/vdpau_functions.inc"#undef VDP_FUNCTION {0, -1} };
VdpGetProcAddress *get_proc_address; vdp_st = vdp_device_create_x11(x11, DefaultScreen(x11), &ctx->vdp_device, &get_proc_address); if (vdp_st != VDP_STATUS_OK) { if (ctx->is_preempted) { MP_DBG(ctx, "Error calling vdp_device_create_x11 while preempted: %d\n", vdp_st); } else { int lev = probing ? MSGL_V : MSGL_ERR; mp_msg(ctx->log, lev, "Error when calling vdp_device_create_x11: %d\n", vdp_st); } return -1; }
for (const struct vdp_function *dsc = vdp_func; dsc->offset >= 0; dsc++) { vdp_st = get_proc_address(ctx->vdp_device, dsc->id, (void **)((char *)&vdp + dsc->offset)); if (vdp_st != VDP_STATUS_OK) { MP_ERR(ctx, "Error when calling vdp_get_proc_address(function " "id %d): %s\n", dsc->id, vdp.get_error_string ? vdp.get_error_string(vdp_st) : "?"); return -1; } }
ctx->vdp = vdp; ctx->get_proc_address = get_proc_address;
if (ctx->av_device_ref) { AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data; AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
vdctx->device = ctx->vdp_device; vdctx->get_proc_address = ctx->get_proc_address; }
vdp_st = vdp.output_surface_create(ctx->vdp_device, VDP_RGBA_FORMAT_B8G8R8A8, 1, 1, &ctx->preemption_obj); if (vdp_st != VDP_STATUS_OK) { MP_ERR(ctx, "Could not create dummy object: %s", vdp.get_error_string(vdp_st)); return -1; }
vdp.preemption_callback_register(ctx->vdp_device, preemption_callback, ctx); return 0;}
static int handle_preemption(struct mp_vdpau_ctx *ctx){ if (!ctx->is_preempted) return 0; mark_vdpau_objects_uninitialized(ctx); if (!ctx->preemption_user_notified) { MP_ERR(ctx, "Got display preemption notice! Will attempt to recover.\n"); ctx->preemption_user_notified = true; } /* Trying to initialize seems to be quite slow, so only try once a * second to avoid using 100% CPU. */ if (ctx->last_preemption_retry_fail && mp_time_sec() - ctx->last_preemption_retry_fail < 1.0) return -1; if (win_x11_init_vdpau_procs(ctx, false) < 0) { ctx->last_preemption_retry_fail = mp_time_sec(); return -1; } ctx->preemption_user_notified = false; ctx->last_preemption_retry_fail = 0; ctx->is_preempted = false; ctx->preemption_counter++; MP_INFO(ctx, "Recovered from display preemption.\n"); return 1;}
// Check whether vdpau display preemption happened. The caller provides a// preemption counter, which contains the logical timestamp of the last// preemption handled by the caller. The counter can be 0 for init.// If counter is NULL, only ever return -1 or 1.// Return values:// -1: the display is currently preempted, and vdpau can't be used// 0: a preemption event happened, and the caller must recover// (*counter is updated, and a second call will report status ok)// 1: everything is fine, no preemption happenedint mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter){ int r = 1; mp_mutex_lock(&ctx->preempt_lock);
const void *p[4] = {&(uint32_t){0}}; uint32_t stride[4] = {4}; VdpRect rc = {0}; ctx->vdp.output_surface_put_bits_native(ctx->preemption_obj, p, stride, &rc);
// First time init if (counter && !*counter) *counter = ctx->preemption_counter;
if (handle_preemption(ctx) < 0) r = -1;
if (counter && r > 0 && *counter < ctx->preemption_counter) { *counter = ctx->preemption_counter; r = 0; // signal recovery after preemption }
mp_mutex_unlock(&ctx->preempt_lock); return r;}
struct surface_ref { struct mp_vdpau_ctx *ctx; int index;};
static void release_decoder_surface(void *ptr){ struct surface_ref *r = ptr; struct mp_vdpau_ctx *ctx = r->ctx;
mp_mutex_lock(&ctx->pool_lock); assert(ctx->video_surfaces[r->index].in_use); ctx->video_surfaces[r->index].in_use = false; mp_mutex_unlock(&ctx->pool_lock);
talloc_free(r);}
static struct mp_image *create_ref(struct mp_vdpau_ctx *ctx, int index){ struct surface_entry *e = &ctx->video_surfaces[index]; assert(!e->in_use); e->in_use = true; e->age = ctx->age_counter++; struct surface_ref *ref = talloc_ptrtype(NULL, ref); *ref = (struct surface_ref){ctx, index}; struct mp_image *res = mp_image_new_custom_ref(NULL, ref, release_decoder_surface); if (res) { mp_image_setfmt(res, e->rgb ? IMGFMT_VDPAU_OUTPUT : IMGFMT_VDPAU); mp_image_set_size(res, e->w, e->h); res->planes[0] = (void *)"dummy"; // must be non-NULL, otherwise arbitrary res->planes[3] = (void *)(intptr_t)(e->rgb ? e->osurface : e->surface); } return res;}
static struct mp_image *mp_vdpau_get_surface(struct mp_vdpau_ctx *ctx, VdpChromaType chroma, VdpRGBAFormat rgb_format, bool rgb, int w, int h){ struct vdp_functions *vdp = &ctx->vdp; int surface_index = -1; VdpStatus vdp_st;
if (rgb) { chroma = (VdpChromaType)-1; } else { rgb_format = (VdpChromaType)-1; }
mp_mutex_lock(&ctx->pool_lock);
// Destroy all unused surfaces that don't have matching parameters for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use && e->allocated) { if (e->w != w || e->h != h || e->rgb != rgb || e->chroma != chroma || e->rgb_format != rgb_format) { if (e->rgb) { vdp_st = vdp->output_surface_destroy(e->osurface); } else { vdp_st = vdp->video_surface_destroy(e->surface); } CHECK_VDP_WARNING(ctx, "Error when destroying surface"); e->surface = e->osurface = VDP_INVALID_HANDLE; e->allocated = false; } } }
// Try to find an existing unused surface for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use && e->allocated) { assert(e->w == w && e->h == h); assert(e->chroma == chroma); assert(e->rgb_format == rgb_format); assert(e->rgb == rgb); if (surface_index >= 0) { struct surface_entry *other = &ctx->video_surfaces[surface_index]; if (other->age < e->age) continue; } surface_index = n; } }
if (surface_index >= 0) goto done;
// Allocate new surface for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use) { assert(e->surface == VDP_INVALID_HANDLE); assert(e->osurface == VDP_INVALID_HANDLE); assert(!e->allocated); e->chroma = chroma; e->rgb_format = rgb_format; e->rgb = rgb; e->w = w; e->h = h; if (mp_vdpau_handle_preemption(ctx, NULL) >= 0) { if (rgb) { vdp_st = vdp->output_surface_create(ctx->vdp_device, rgb_format, w, h, &e->osurface); e->allocated = e->osurface != VDP_INVALID_HANDLE; } else { vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma, w, h, &e->surface); e->allocated = e->surface != VDP_INVALID_HANDLE; } CHECK_VDP_WARNING(ctx, "Error when allocating surface"); } else { e->allocated = false; e->osurface = VDP_INVALID_HANDLE; e->surface = VDP_INVALID_HANDLE; } surface_index = n; goto done; } }
done: ; struct mp_image *mpi = NULL; if (surface_index >= 0) mpi = create_ref(ctx, surface_index);
mp_mutex_unlock(&ctx->pool_lock);
if (!mpi) MP_ERR(ctx, "no surfaces available in mp_vdpau_get_video_surface\n"); return mpi;}
struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, VdpChromaType chroma, int w, int h){ return mp_vdpau_get_surface(ctx, chroma, 0, false, w, h);}
static void free_device_ref(struct AVHWDeviceContext *hwctx){ struct mp_vdpau_ctx *ctx = hwctx->user_opaque;
struct vdp_functions *vdp = &ctx->vdp; VdpStatus vdp_st;
for (int i = 0; i < MAX_VIDEO_SURFACES; i++) { // can't hold references past context lifetime assert(!ctx->video_surfaces[i].in_use); if (ctx->video_surfaces[i].surface != VDP_INVALID_HANDLE) { vdp_st = vdp->video_surface_destroy(ctx->video_surfaces[i].surface); CHECK_VDP_WARNING(ctx, "Error when calling vdp_video_surface_destroy"); } if (ctx->video_surfaces[i].osurface != VDP_INVALID_HANDLE) { vdp_st = vdp->output_surface_destroy(ctx->video_surfaces[i].osurface); CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy"); } }
if (ctx->preemption_obj != VDP_INVALID_HANDLE) { vdp_st = vdp->output_surface_destroy(ctx->preemption_obj); CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy"); }
if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) { vdp_st = vdp->device_destroy(ctx->vdp_device); CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy"); }
if (ctx->close_display) XCloseDisplay(ctx->x11);
mp_mutex_destroy(&ctx->pool_lock); mp_mutex_destroy(&ctx->preempt_lock); talloc_free(ctx);}
struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11, bool probing){ AVBufferRef *avref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VDPAU); if (!avref) return NULL;
AVHWDeviceContext *hwctx = (void *)avref->data; AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
struct mp_vdpau_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct mp_vdpau_ctx) { .log = log, .x11 = x11, .preemption_counter = 1, .av_device_ref = avref, .hwctx = { .av_device_ref = avref, }, }; mp_mutex_init_type(&ctx->preempt_lock, MP_MUTEX_RECURSIVE); mp_mutex_init(&ctx->pool_lock);
hwctx->free = free_device_ref; hwctx->user_opaque = ctx;
mark_vdpau_objects_uninitialized(ctx);
if (win_x11_init_vdpau_procs(ctx, probing) < 0) { mp_vdpau_destroy(ctx); return NULL; }
vdctx->device = ctx->vdp_device; vdctx->get_proc_address = ctx->get_proc_address;
if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0) { mp_vdpau_destroy(ctx); return NULL; }
return ctx;}
void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx){ if (!ctx) return;
AVBufferRef *ref = ctx->av_device_ref; av_buffer_unref(&ref); // frees ctx as well}
bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type, VdpYCbCrFormat *out_pixel_format){ VdpChromaType chroma = VDP_CHROMA_TYPE_420; VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1;
switch (imgfmt) { case IMGFMT_420P: ycbcr = VDP_YCBCR_FORMAT_YV12; break; case IMGFMT_NV12: ycbcr = VDP_YCBCR_FORMAT_NV12; break; case IMGFMT_UYVY: ycbcr = VDP_YCBCR_FORMAT_UYVY; chroma = VDP_CHROMA_TYPE_422; break; case IMGFMT_VDPAU: break; default: return false; }
if (out_chroma_type) *out_chroma_type = chroma; if (out_pixel_format) *out_pixel_format = ycbcr; return true;}
bool mp_vdpau_get_rgb_format(int imgfmt, VdpRGBAFormat *out_rgba_format){ VdpRGBAFormat format = (VdpRGBAFormat)-1;
switch (imgfmt) { case IMGFMT_BGRA: format = VDP_RGBA_FORMAT_B8G8R8A8; break; default: return false; }
if (out_rgba_format) *out_rgba_format = format; return true;}
// Use mp_vdpau_get_video_surface, and upload mpi to it. Return NULL on failure.// If the image is already a vdpau video surface, just return a reference.struct mp_image *mp_vdpau_upload_video_surface(struct mp_vdpau_ctx *ctx, struct mp_image *mpi){ struct vdp_functions *vdp = &ctx->vdp; VdpStatus vdp_st;
if (mpi->imgfmt == IMGFMT_VDPAU || mpi->imgfmt == IMGFMT_VDPAU_OUTPUT) return mp_image_new_ref(mpi);
VdpChromaType chroma = (VdpChromaType)-1; VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1; VdpRGBAFormat rgbafmt = (VdpRGBAFormat)-1; bool rgb = !mp_vdpau_get_format(mpi->imgfmt, &chroma, &ycbcr); if (rgb && !mp_vdpau_get_rgb_format(mpi->imgfmt, &rgbafmt)) return NULL;
struct mp_image *hwmpi = mp_vdpau_get_surface(ctx, chroma, rgbafmt, rgb, mpi->w, mpi->h); if (!hwmpi) return NULL;
struct mp_image *src = mpi; if (mpi->stride[0] < 0) src = mp_image_new_copy(mpi); // unflips it when copying
if (hwmpi->imgfmt == IMGFMT_VDPAU) { VdpVideoSurface surface = (intptr_t)hwmpi->planes[3]; const void *destdata[3] = {src->planes[0], src->planes[2], src->planes[1]}; if (src->imgfmt == IMGFMT_NV12) destdata[1] = destdata[2]; vdp_st = vdp->video_surface_put_bits_y_cb_cr(surface, ycbcr, destdata, src->stride); } else { VdpOutputSurface rgb_surface = (intptr_t)hwmpi->planes[3]; vdp_st = vdp->output_surface_put_bits_native(rgb_surface, &(const void *){src->planes[0]}, &(uint32_t){src->stride[0]}, NULL); } CHECK_VDP_WARNING(ctx, "Error when uploading surface");
if (src != mpi) talloc_free(src);
mp_image_copy_attributes(hwmpi, mpi); return hwmpi;}
bool mp_vdpau_guess_if_emulated(struct mp_vdpau_ctx *ctx){ struct vdp_functions *vdp = &ctx->vdp; VdpStatus vdp_st; char const* info = NULL; vdp_st = vdp->get_information_string(&info); CHECK_VDP_WARNING(ctx, "Error when calling vdp_get_information_string"); return vdp_st == VDP_STATUS_OK && info && strstr(info, "VAAPI");}
// (This clearly works only for contexts wrapped by our code.)struct mp_vdpau_ctx *mp_vdpau_get_ctx_from_av(AVBufferRef *hw_device_ctx){ AVHWDeviceContext *hwctx = (void *)hw_device_ctx->data;
if (hwctx->free != free_device_ref) return NULL; // not ours
return hwctx->user_opaque;}
static bool is_emulated(struct AVBufferRef *hw_device_ctx){ struct mp_vdpau_ctx *ctx = mp_vdpau_get_ctx_from_av(hw_device_ctx); if (!ctx) return false;
return mp_vdpau_guess_if_emulated(ctx);}
static struct AVBufferRef *vdpau_create_standalone(struct mpv_global *global, struct mp_log *log, struct hwcontext_create_dev_params *params){ XInitThreads();
Display *display = XOpenDisplay(NULL); if (!display) return NULL;
struct mp_vdpau_ctx *vdp = mp_vdpau_create_device_x11(log, display, params->probing); if (!vdp) { XCloseDisplay(display); return NULL; }
vdp->close_display = true; return vdp->hwctx.av_device_ref;}
const struct hwcontext_fns hwcontext_fns_vdpau = { .av_hwdevice_type = AV_HWDEVICE_TYPE_VDPAU, .create_dev = vdpau_create_standalone, .is_emulated = is_emulated,};