From 3c5fe76f7c31deaaa7cc211dd29b3db5cb866326 Mon Sep 17 00:00:00 2001 From: peijiankang Date: Tue, 9 Apr 2024 14:37:35 +0800 Subject: [PATCH] add opengl api for mpv-0.35 --- libmpv/client.h | 53 ++++++ libmpv/mpv.def | 7 + libmpv/opengl_cb.h | 339 ++++++++++++++++++++++++++++++++++++++ libmpv/qthelper.hpp | 386 ++++++++++++++++++++++++++++++++++++++++++++ player/client.c | 125 ++++++++++++++ player/command.c | 3 + player/playloop.c | 4 + wscript_build.py | 2 +- 8 files changed, 918 insertions(+), 1 deletion(-) create mode 100644 libmpv/opengl_cb.h create mode 100644 libmpv/qthelper.hpp diff --git a/libmpv/client.h b/libmpv/client.h index fb10e5e..afb7510 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -1276,6 +1276,35 @@ typedef enum mpv_event_id { * start of the program), while the property behaves correctly. */ MPV_EVENT_IDLE = 11, + /** + * Playback was paused. This indicates the user pause state. + * + * The user pause state is the state the user requested (changed with the + * "pause" property). There is an internal pause state too, which is entered + * if e.g. the network is too slow (the "core-idle" property generally + * indicates whether the core is playing or waiting). + * + * This event is sent whenever any pause states change, not only the user + * state. You might get multiple events in a row while these states change + * independently. But the event ID sent always indicates the user pause + * state. + * + * If you don't want to deal with this, use mpv_observe_property() on the + * "pause" property and ignore MPV_EVENT_PAUSE/UNPAUSE. Likewise, the + * "core-idle" property tells you whether video is actually playing or not. + * + * @deprecated The event is redundant with mpv_observe_property() as + * mentioned above, and might be removed in the far future. + */ + MPV_EVENT_PAUSE = 12, + /** + * Playback was unpaused. See MPV_EVENT_PAUSE for not so obvious details. + * + * @deprecated The event is redundant with mpv_observe_property() as + * explained in the MPV_EVENT_PAUSE comments, and might be + * removed in the far future. + */ + MPV_EVENT_UNPAUSE = 13, /** * Sent every time after a video frame is displayed. Note that currently, * this will be sent in lower frequency if there is no video, or playback @@ -1877,6 +1906,30 @@ MPV_EXPORT int mpv_hook_continue(mpv_handle *ctx, uint64_t id); */ MPV_EXPORT int mpv_get_wakeup_pipe(mpv_handle *ctx); +/** + * @deprecated use render.h + */ +typedef enum mpv_sub_api { + /** + * For using mpv's OpenGL renderer on an external OpenGL context. + * mpv_get_sub_api(MPV_SUB_API_OPENGL_CB) returns mpv_opengl_cb_context*. + * This context can be used with mpv_opengl_cb_* functions. + * Will return NULL if unavailable (if OpenGL support was not compiled in). + * See opengl_cb.h for details. + * + * @deprecated use render.h + */ + MPV_SUB_API_OPENGL_CB = 1 +} mpv_sub_api; + +/** + * This is used for additional APIs that are not strictly part of the core API. + * See the individual mpv_sub_api member values. + * + * @deprecated use render.h + */ +void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api); + #endif #ifdef __cplusplus diff --git a/libmpv/mpv.def b/libmpv/mpv.def index 232490d..2b0808d 100644 --- a/libmpv/mpv.def +++ b/libmpv/mpv.def @@ -21,6 +21,7 @@ mpv_get_property mpv_get_property_async mpv_get_property_osd_string mpv_get_property_string +mpv_get_sub_api mpv_get_time_us mpv_get_wakeup_pipe mpv_hook_add @@ -28,6 +29,12 @@ mpv_hook_continue mpv_initialize mpv_load_config_file mpv_observe_property +mpv_opengl_cb_draw +mpv_opengl_cb_init_gl +mpv_opengl_cb_report_flip +mpv_opengl_cb_render +mpv_opengl_cb_set_update_callback +mpv_opengl_cb_uninit_gl mpv_render_context_create mpv_render_context_free mpv_render_context_get_info diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h new file mode 100644 index 0000000..6820681 --- /dev/null +++ b/libmpv/opengl_cb.h @@ -0,0 +1,339 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MPV_CLIENT_API_OPENGL_CB_H_ +#define MPV_CLIENT_API_OPENGL_CB_H_ + +#include "client.h" + +#if !MPV_ENABLE_DEPRECATED +#error "This header and all API provided by it is deprecated. Use render.h instead." +#else + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * + * Overview + * -------- + * + * Warning: this API is deprecated. A very similar API is provided by render.h + * and render_gl.h. The deprecated API is emulated with the new API. + * + * This API can be used to make mpv render into a foreign OpenGL context. It + * can be used to handle video display. + * + * The renderer needs to be explicitly initialized with mpv_opengl_cb_init_gl(), + * and then video can be drawn with mpv_opengl_cb_draw(). The user thread can + * be notified by new frames with mpv_opengl_cb_set_update_callback(). + * + * You can output and embed video without this API by setting the mpv "wid" + * option to a native window handle (see "Embedding the video window" section + * in the client.h header). In general, using the opengl-cb API is recommended, + * because window embedding can cause various issues, especially with GUI + * toolkits and certain platforms. + * + * OpenGL interop + * -------------- + * + * This assumes the OpenGL context lives on a certain thread controlled by the + * API user. The following functions require access to the OpenGL context: + * mpv_opengl_cb_init_gl + * mpv_opengl_cb_draw + * mpv_opengl_cb_uninit_gl + * + * The OpenGL context is indirectly accessed through the OpenGL function + * pointers returned by the get_proc_address callback in mpv_opengl_cb_init_gl. + * Generally, mpv will not load the system OpenGL library when using this API. + * + * Only "desktop" OpenGL version 2.1 and later and OpenGL ES version 2.0 and + * later are supported. With OpenGL 2.1, the GL_ARB_texture_rg is required. The + * renderer was written for the OpenGL 3.x core profile, with additional support + * for OpenGL 2.1 and OpenGL ES 2.0. + * + * Note that some hardware decoding interop API (as set with the "hwdec" option) + * may actually access some sort of host API, such as EGL. + * + * OpenGL state + * ------------ + * + * OpenGL has a large amount of implicit state. All the mpv functions mentioned + * above expect that the OpenGL state is reasonably set to OpenGL standard + * defaults. Likewise, mpv will attempt to leave the OpenGL context with + * standard defaults. The following state is excluded from this: + * + * - the glViewport state + * - the glScissor state (but GL_SCISSOR_TEST is in its default value) + * - glBlendFuncSeparate() state (but GL_BLEND is in its default value) + * - glClearColor() state + * - mpv may overwrite the callback set with glDebugMessageCallback() + * - mpv always disables GL_DITHER at init + * + * Messing with the state could be avoided by creating shared OpenGL contexts, + * but this is avoided for the sake of compatibility and interoperability. + * + * On OpenGL 2.1, mpv will strictly call functions like glGenTextures() to + * create OpenGL objects. You will have to do the same. This ensures that + * objects created by mpv and the API users don't clash. Also, legacy state + * must be either in its defaults, or not interfere with core state. + * + * Threading + * --------- + * + * The mpv_opengl_cb_* functions can be called from any thread, under the + * following conditions: + * - only one of the mpv_opengl_cb_* functions can be called at the same time + * (unless they belong to different mpv cores created by mpv_create()) + * - for functions which need an OpenGL context (see above) the OpenGL context + * must be "current" in the current thread, and it must be the same context + * as used with mpv_opengl_cb_init_gl() + * - never can be called from within the callbacks set with + * mpv_set_wakeup_callback() or mpv_opengl_cb_set_update_callback() + * + * Context and handle lifecycle + * ---------------------------- + * + * Video initialization will fail if the OpenGL context was not initialized yet + * (with mpv_opengl_cb_init_gl()). Likewise, mpv_opengl_cb_uninit_gl() will + * disable video. + * + * When the mpv core is destroyed (e.g. via mpv_terminate_destroy()), the OpenGL + * context must have been uninitialized. If this doesn't happen, undefined + * behavior will result. + * + * Hardware decoding + * ----------------- + * + * Hardware decoding via opengl_cb is fully supported, but requires some + * additional setup. (At least if direct hardware decoding modes are wanted, + * instead of copying back surface data from GPU to CPU RAM.) + * + * While "normal" mpv loads the OpenGL hardware decoding interop on demand, + * this can't be done with opengl_cb for internal technical reasons. Instead, + * it loads them by default, even if hardware decoding is not going to be used. + * In older mpv releases, this had to be done by setting the + * "opengl-hwdec-interop" or "hwdec-preload" options before calling + * mpv_opengl_cb_init_gl(). You can still use the newer "gpu-hwdec-interop" + * option to prevent loading of interop, or to load only a specific interop. + * + * There may be certain requirements on the OpenGL implementation: + * - Windows: ANGLE is required (although in theory GL/DX interop could be used) + * - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback + * must be provided (see sections below) + * - nVidia/Linux: Both GLX and EGL should work (GLX is required if vdpau is + * used, e.g. due to old drivers.) + * - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL) + * - iOS: EAGL is required (EAGLContext.currentContext returning non-nil) + * + * Once these things are setup, hardware decoding can be enabled/disabled at + * any time by setting the "hwdec" property. + * + * Special windowing system interop considerations + * ------------------------------------------------ + * + * In some cases, libmpv needs to have access to the windowing system's handles. + * This can be a pointer to a X11 "Display" for example. Usually this is needed + * only for hardware decoding. + * + * You can communicate these handles to libmpv by adding a pseudo-OpenGL + * extension "GL_MP_MPGetNativeDisplay" to the additional extension string when + * calling mpv_opengl_cb_init_gl(). The get_proc_address callback should resolve + * a function named "glMPGetNativeDisplay", which has the signature: + * + * void* GLAPIENTRY glMPGetNativeDisplay(const char* name) + * + * See below what names are defined. Usually, libmpv will use the native handle + * up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything + * you know/expected, return NULL from the function. + */ + +// Legacy - not supported anymore. +struct mpv_opengl_cb_window_pos { + int x; // left coordinates of window (usually 0) + int y; // top coordinates of window (usually 0) + int width; // width of GL window + int height; // height of GL window +}; + +// Legacy - not supported anymore. +struct mpv_opengl_cb_drm_params { + // DRM fd (int). set this to -1 if invalid. + int fd; + + // currently used crtc id + int crtc_id; + + // currently used connector id + int connector_id; + + // pointer to the drmModeAtomicReq that is being used for the renderloop. + // This atomic request pointer should be usually created at every renderloop. + struct _drmModeAtomicReq *atomic_request; +}; + +/** + * nVidia/Linux via VDPAU requires GLX, which does not have this problem (the + * GLX API can return the current X11 Display). + * + * Windowing system interop on MS win32 + * ------------------------------------ + * + * You should use ANGLE, and make sure your application and libmpv are linked + * to the same ANGLE DLLs. libmpv will pick the device context (needed for + * hardware decoding) from the current ANGLE EGL context. + */ + +/** + * Opaque context, returned by mpv_get_sub_api(MPV_SUB_API_OPENGL_CB). + * + * A context is bound to the mpv_handle it was retrieved from. The context + * will always be the same (for the same mpv_handle), and is valid until the + * mpv_handle it belongs to is released. + */ +typedef struct mpv_opengl_cb_context mpv_opengl_cb_context; + +typedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx); +typedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, const char *name); + +/** + * Set the callback that notifies you when a new video frame is available, or + * if the video display configuration somehow changed and requires a redraw. + * Similar to mpv_set_wakeup_callback(), you must not call any mpv API from + * the callback, and all the other listed restrictions apply (such as not + * exiting the callback by throwing exceptions). + * + * @param callback callback(callback_ctx) is called if the frame should be + * redrawn + * @param callback_ctx opaque argument to the callback + */ +void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx, + mpv_opengl_cb_update_fn callback, + void *callback_ctx); + +/** + * Initialize the mpv OpenGL state. This retrieves OpenGL function pointers via + * get_proc_address, and creates OpenGL objects needed by mpv internally. It + * will also call APIs needed for rendering hardware decoded video in OpenGL, + * according to the mpv "hwdec" option. + * + * You must free the associated state at some point by calling the + * mpv_opengl_cb_uninit_gl() function. Not doing so may result in memory leaks + * or worse. + * + * @param exts optional _additional_ extension string, can be NULL + * @param get_proc_address callback used to retrieve function pointers to OpenGL + * functions. This is used for both standard functions + * and extension functions. (The extension string is + * checked whether extensions are really available.) + * The callback will be called from this function only + * (it is not stored and never used later). + * Usually, GL context APIs do this for you (e.g. with + * glXGetProcAddressARB or wglGetProcAddress), but + * some APIs do not always return pointers for all + * standard functions (even if present); in this case + * you have to compensate by looking up these functions + * yourself. + * @param get_proc_address_ctx arbitrary opaque user context passed to the + * get_proc_address callback + * @return error code (same as normal mpv_* API), including but not limited to: + * MPV_ERROR_UNSUPPORTED: the OpenGL version is not supported + * (or required extensions are missing) + * MPV_ERROR_INVALID_PARAMETER: the OpenGL state was already initialized + */ +int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts, + mpv_opengl_cb_get_proc_address_fn get_proc_address, + void *get_proc_address_ctx); + +/** + * Render video. Requires that the OpenGL state is initialized. + * + * The video will use the full provided framebuffer. Options like "panscan" are + * applied to determine which part of the video should be visible and how the + * video should be scaled. You can change these options at runtime by using the + * mpv property API. + * + * The renderer will reconfigure itself every time the output rectangle/size + * is changed. (If you want to do animations, it might be better to do the + * animation on a FBO instead.) + * + * This function implicitly pulls a video frame from the internal queue and + * renders it. If no new frame is available, the previous frame is redrawn. + * The update callback set with mpv_opengl_cb_set_update_callback() notifies + * you when a new frame was added. + * + * @param fbo The framebuffer object to render on. Because the renderer might + * manage multiple FBOs internally for the purpose of video + * postprocessing, it will always bind and unbind FBOs itself. If + * you want mpv to render on the main framebuffer, pass 0. + * @param w Width of the framebuffer. This is either the video size if the fbo + * parameter is 0, or the allocated size of the texture backing the + * fbo. The renderer will always use the full size of the fbo. + * @param h Height of the framebuffer. Same as with the w parameter, except + * that this parameter can be negative. In this case, the video + * frame will be rendered flipped. + * @return 0 + */ +int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h); + +/** + * Deprecated. Use mpv_opengl_cb_draw(). This function is equivalent to: + * + * int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]) + * { return mpv_opengl_cb_draw(ctx, fbo, vp[2], vp[3]); } + * + * vp[0] and vp[1] used to have a meaning, but are ignored in newer versions. + * + * This function will be removed in the future without version bump (this API + * was never marked as stable). + */ +int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]); + +/** + * Tell the renderer that a frame was flipped at the given time. This is + * optional, but can help the player to achieve better timing. + * + * Note that calling this at least once informs libmpv that you will use this + * function. If you use it inconsistently, expect bad video playback. + * + * If this is called while no video or no OpenGL is initialized, it is ignored. + * + * @param time The mpv time (using mpv_get_time_us()) at which the flip call + * returned. If 0 is passed, mpv_get_time_us() is used instead. + * Currently, this parameter is ignored. + * @return error code + */ +int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time); + +/** + * Destroy the mpv OpenGL state. + * + * If video is still active (e.g. a file playing), video will be disabled + * forcefully. + * + * Calling this multiple times is ok. + * + * @return error code + */ +int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* else #if MPV_ENABLE_DEPRECATED */ + +#endif diff --git a/libmpv/qthelper.hpp b/libmpv/qthelper.hpp new file mode 100644 index 0000000..3af86e3 --- /dev/null +++ b/libmpv/qthelper.hpp @@ -0,0 +1,386 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MPV_CLIENT_API_QTHELPER_H_ +#define MPV_CLIENT_API_QTHELPER_H_ + +#include + +#if !MPV_ENABLE_DEPRECATED +#error "This helper is deprecated. Copy it into your project instead." +#else + +/** + * Note: these helpers are provided for convenience for C++/Qt applications. + * This is based on the public API in client.h, and it does not encode any + * knowledge that is not known or guaranteed outside of the C client API. You + * can even copy and modify this code as you like, or implement similar things + * for other languages. + */ + +#include + +#include +#include +#include +#include +#include +#include + +namespace mpv { +namespace qt { + +// Wrapper around mpv_handle. Does refcounting under the hood. +class Handle +{ + struct container { + container(mpv_handle *h) : mpv(h) {} + ~container() { mpv_terminate_destroy(mpv); } + mpv_handle *mpv; + }; + QSharedPointer sptr; +public: + // Construct a new Handle from a raw mpv_handle with refcount 1. If the + // last Handle goes out of scope, the mpv_handle will be destroyed with + // mpv_terminate_destroy(). + // Never destroy the mpv_handle manually when using this wrapper. You + // will create dangling pointers. Just let the wrapper take care of + // destroying the mpv_handle. + // Never create multiple wrappers from the same raw mpv_handle; copy the + // wrapper instead (that's what it's for). + static Handle FromRawHandle(mpv_handle *handle) { + Handle h; + h.sptr = QSharedPointer(new container(handle)); + return h; + } + + // Return the raw handle; for use with the libmpv C API. + operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; } +}; + +static inline QVariant node_to_variant(const mpv_node *node) +{ + switch (node->format) { + case MPV_FORMAT_STRING: + return QVariant(QString::fromUtf8(node->u.string)); + case MPV_FORMAT_FLAG: + return QVariant(static_cast(node->u.flag)); + case MPV_FORMAT_INT64: + return QVariant(static_cast(node->u.int64)); + case MPV_FORMAT_DOUBLE: + return QVariant(node->u.double_); + case MPV_FORMAT_NODE_ARRAY: { + mpv_node_list *list = node->u.list; + QVariantList qlist; + for (int n = 0; n < list->num; n++) + qlist.append(node_to_variant(&list->values[n])); + return QVariant(qlist); + } + case MPV_FORMAT_NODE_MAP: { + mpv_node_list *list = node->u.list; + QVariantMap qmap; + for (int n = 0; n < list->num; n++) { + qmap.insert(QString::fromUtf8(list->keys[n]), + node_to_variant(&list->values[n])); + } + return QVariant(qmap); + } + default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions) + return QVariant(); + } +} + +struct node_builder { + node_builder(const QVariant& v) { + set(&node_, v); + } + ~node_builder() { + free_node(&node_); + } + mpv_node *node() { return &node_; } +private: + Q_DISABLE_COPY(node_builder) + mpv_node node_; + mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) { + dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY; + mpv_node_list *list = new mpv_node_list(); + dst->u.list = list; + if (!list) + goto err; + list->values = new mpv_node[num](); + if (!list->values) + goto err; + if (is_map) { + list->keys = new char*[num](); + if (!list->keys) + goto err; + } + return list; + err: + free_node(dst); + return NULL; + } + char *dup_qstring(const QString &s) { + QByteArray b = s.toUtf8(); + char *r = new char[b.size() + 1]; + if (r) + std::memcpy(r, b.data(), b.size() + 1); + return r; + } + bool test_type(const QVariant &v, QMetaType::Type t) { + // The Qt docs say: "Although this function is declared as returning + // "QVariant::Type(obsolete), the return value should be interpreted + // as QMetaType::Type." + // So a cast really seems to be needed to avoid warnings (urgh). + return static_cast(v.type()) == static_cast(t); + } + void set(mpv_node *dst, const QVariant &src) { + if (test_type(src, QMetaType::QString)) { + dst->format = MPV_FORMAT_STRING; + dst->u.string = dup_qstring(src.toString()); + if (!dst->u.string) + goto fail; + } else if (test_type(src, QMetaType::Bool)) { + dst->format = MPV_FORMAT_FLAG; + dst->u.flag = src.toBool() ? 1 : 0; + } else if (test_type(src, QMetaType::Int) || + test_type(src, QMetaType::LongLong) || + test_type(src, QMetaType::UInt) || + test_type(src, QMetaType::ULongLong)) + { + dst->format = MPV_FORMAT_INT64; + dst->u.int64 = src.toLongLong(); + } else if (test_type(src, QMetaType::Double)) { + dst->format = MPV_FORMAT_DOUBLE; + dst->u.double_ = src.toDouble(); + } else if (src.canConvert()) { + QVariantList qlist = src.toList(); + mpv_node_list *list = create_list(dst, false, qlist.size()); + if (!list) + goto fail; + list->num = qlist.size(); + for (int n = 0; n < qlist.size(); n++) + set(&list->values[n], qlist[n]); + } else if (src.canConvert()) { + QVariantMap qmap = src.toMap(); + mpv_node_list *list = create_list(dst, true, qmap.size()); + if (!list) + goto fail; + list->num = qmap.size(); + for (int n = 0; n < qmap.size(); n++) { + list->keys[n] = dup_qstring(qmap.keys()[n]); + if (!list->keys[n]) { + free_node(dst); + goto fail; + } + set(&list->values[n], qmap.values()[n]); + } + } else { + goto fail; + } + return; + fail: + dst->format = MPV_FORMAT_NONE; + } + void free_node(mpv_node *dst) { + switch (dst->format) { + case MPV_FORMAT_STRING: + delete[] dst->u.string; + break; + case MPV_FORMAT_NODE_ARRAY: + case MPV_FORMAT_NODE_MAP: { + mpv_node_list *list = dst->u.list; + if (list) { + for (int n = 0; n < list->num; n++) { + if (list->keys) + delete[] list->keys[n]; + if (list->values) + free_node(&list->values[n]); + } + delete[] list->keys; + delete[] list->values; + } + delete list; + break; + } + default: ; + } + dst->format = MPV_FORMAT_NONE; + } +}; + +/** + * RAII wrapper that calls mpv_free_node_contents() on the pointer. + */ +struct node_autofree { + mpv_node *ptr; + node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {} + ~node_autofree() { mpv_free_node_contents(ptr); } +}; + +#if MPV_ENABLE_DEPRECATED + +/** + * Return the given property as mpv_node converted to QVariant, or QVariant() + * on error. + * + * @deprecated use get_property() instead + * + * @param name the property name + */ +static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name) +{ + mpv_node node; + if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) + return QVariant(); + node_autofree f(&node); + return node_to_variant(&node); +} + +/** + * Set the given property as mpv_node converted from the QVariant argument. + + * @deprecated use set_property() instead + */ +static inline int set_property_variant(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * Set the given option as mpv_node converted from the QVariant argument. + * + * @deprecated use set_property() instead + */ +static inline int set_option_variant(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * mpv_command_node() equivalent. Returns QVariant() on error (and + * unfortunately, the same on success). + * + * @deprecated use command() instead + */ +static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args) +{ + node_builder node(args); + mpv_node res; + if (mpv_command_node(ctx, node.node(), &res) < 0) + return QVariant(); + node_autofree f(&res); + return node_to_variant(&res); +} + +#endif + +/** + * This is used to return error codes wrapped in QVariant for functions which + * return QVariant. + * + * You can use get_error() or is_error() to extract the error status from a + * QVariant value. + */ +struct ErrorReturn +{ + /** + * enum mpv_error value (or a value outside of it if ABI was extended) + */ + int error; + + ErrorReturn() : error(0) {} + explicit ErrorReturn(int err) : error(err) {} +}; + +/** + * Return the mpv error code packed into a QVariant, or 0 (success) if it's not + * an error value. + * + * @return error code (<0) or success (>=0) + */ +static inline int get_error(const QVariant &v) +{ + if (!v.canConvert()) + return 0; + return v.value().error; +} + +/** + * Return whether the QVariant carries a mpv error code. + */ +static inline bool is_error(const QVariant &v) +{ + return get_error(v) < 0; +} + +/** + * Return the given property as mpv_node converted to QVariant, or QVariant() + * on error. + * + * @param name the property name + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant get_property(mpv_handle *ctx, const QString &name) +{ + mpv_node node; + int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&node); + return node_to_variant(&node); +} + +/** + * Set the given property as mpv_node converted from the QVariant argument. + * + * @return mpv error code (<0 on error, >= 0 on success) + */ +static inline int set_property(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * mpv_command_node() equivalent. + * + * @param args command arguments, with args[0] being the command name as string + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant command(mpv_handle *ctx, const QVariant &args) +{ + node_builder node(args); + mpv_node res; + int err = mpv_command_node(ctx, node.node(), &res); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&res); + return node_to_variant(&res); +} + +} +} + +Q_DECLARE_METATYPE(mpv::qt::ErrorReturn) + +#endif /* else #if MPV_ENABLE_DEPRECATED */ + +#endif diff --git a/player/client.c b/player/client.c index dd81cdf..3f0306d 100644 --- a/player/client.c +++ b/player/client.c @@ -83,6 +83,7 @@ struct mp_client_api { int num_custom_protocols; struct mpv_render_context *render_context; + struct mpv_opengl_cb_context *gl_cb_ctx; }; struct observe_property { @@ -2075,6 +2076,8 @@ static const char *const event_table[] = { [MPV_EVENT_END_FILE] = "end-file", [MPV_EVENT_FILE_LOADED] = "file-loaded", [MPV_EVENT_IDLE] = "idle", + [MPV_EVENT_PAUSE] = "pause", + [MPV_EVENT_UNPAUSE] = "unpause", [MPV_EVENT_TICK] = "tick", [MPV_EVENT_CLIENT_MESSAGE] = "client-message", [MPV_EVENT_VIDEO_RECONFIG] = "video-reconfig", @@ -2153,6 +2156,128 @@ mp_client_api_acquire_render_context(struct mp_client_api *ca) return res; } +// Emulation of old opengl_cb API. + +#include "libmpv/opengl_cb.h" +#include "libmpv/render_gl.h" + +struct mpv_opengl_cb_context { + struct mp_client_api *client_api; + mpv_opengl_cb_update_fn callback; + void *callback_ctx; +}; + +static mpv_opengl_cb_context *opengl_cb_get_context(mpv_handle *ctx) +{ + pthread_mutex_lock(&ctx->clients->lock); + mpv_opengl_cb_context *cb = ctx->clients->gl_cb_ctx; + if (!cb) { + cb = talloc_zero(NULL, struct mpv_opengl_cb_context); + cb->client_api = ctx->clients; + cb->client_api->gl_cb_ctx = cb; + } + pthread_mutex_unlock(&ctx->clients->lock); + return cb; +} + +void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx, + mpv_opengl_cb_update_fn callback, + void *callback_ctx) +{ + // This was probably supposed to be thread-safe, but we don't care. It's + // compatibility code, and if you have problems, use the new API. + if (ctx->client_api->render_context) { + mpv_render_context_set_update_callback(ctx->client_api->render_context, + callback, callback_ctx); + } + // Nasty thing: could set this even while not initialized, so we need to + // preserve it. + ctx->callback = callback; + ctx->callback_ctx = callback_ctx; +} + +int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts, + mpv_opengl_cb_get_proc_address_fn get_proc_address, + void *get_proc_address_ctx) +{ + if (ctx->client_api->render_context) + return MPV_ERROR_INVALID_PARAMETER; + + // mpv_render_context_create() only calls mp_client_get_global() on it. + mpv_handle dummy = {.mpctx = ctx->client_api->mpctx}; + + mpv_render_param params[] = { + {MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){ + .get_proc_address = get_proc_address, + .get_proc_address_ctx = get_proc_address_ctx, + }}, + // Hack for explicit legacy hwdec loading. We really want to make it + // impossible for proper render API users to trigger this. + {(mpv_render_param_type)-1, ctx->client_api->mpctx->global}, + {0} + }; + int err = mpv_render_context_create(&ctx->client_api->render_context, + &dummy, params); + if (err >= 0) { + mpv_render_context_set_update_callback(ctx->client_api->render_context, + ctx->callback, ctx->callback_ctx); + } + return err; +} + +int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h) +{ + if (!ctx->client_api->render_context) + return MPV_ERROR_INVALID_PARAMETER; + + mpv_render_param params[] = { + {MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){ + .fbo = fbo, + .w = w, + .h = abs(h), + }}, + {MPV_RENDER_PARAM_FLIP_Y, &(int){h < 0}}, + {0} + }; + return mpv_render_context_render(ctx->client_api->render_context, params); +} + +int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time) +{ + if (!ctx->client_api->render_context) + return MPV_ERROR_INVALID_PARAMETER; + + mpv_render_context_report_swap(ctx->client_api->render_context); + return 0; +} + +int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx) +{ + if (ctx->client_api->render_context) + mpv_render_context_free(ctx->client_api->render_context); + ctx->client_api->render_context = NULL; + return 0; +} + +int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]) +{ + return mpv_opengl_cb_draw(ctx, fbo, vp[2], vp[3]); +} + +void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api) +{ + if (!ctx->mpctx->initialized) + return NULL; + void *res = NULL; + switch (sub_api) { + case MPV_SUB_API_OPENGL_CB: + res = opengl_cb_get_context(ctx); + break; + default:; + } + return res; +} // stream_cb struct mp_custom_protocol { diff --git a/player/command.c b/player/command.c index 2e6b987..cf71b9d 100644 --- a/player/command.c +++ b/player/command.c @@ -6606,6 +6606,9 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (co) mp_notify_property(mpctx, co->name); + if (opt_ptr == &opts->pause) + mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0); + if (self_update) return; diff --git a/player/playloop.c b/player/playloop.c index 91badf0..27b5da6 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -180,6 +180,10 @@ void set_pause_state(struct MPContext *mpctx, bool user_pause) } else { (void)get_relative_time(mpctx); // ignore time that passed during pause } + + // For some reason, these events are supposed to be sent even if only + // the internal pause state changed (and "pause" property didn't)... OK. + mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0); } update_core_idle_state(mpctx); diff --git a/wscript_build.py b/wscript_build.py index 16c8cf0..3235afb 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -737,7 +737,7 @@ def build(ctx): PRIV_LIBS = get_deps(), ) - headers = ["client.h", "render.h", + headers = ["client.h", "qthelper.hpp", "opengl_cb.h", "render.h", "render_gl.h", "stream_cb.h"] for f in headers: ctx.install_as(ctx.env.INCLUDEDIR + '/mpv/' + f, 'libmpv/' + f) -- 2.41.0