Vendor media_kit_video 2.0.1 with the X11/EGL fix (hardware rendering on X11 with Flutter >= 3.38)
Flutter >= 3.38 keeps its EGL contexts on the raster thread, so media_kit's video output found none on the platform thread and fell back to software rendering (no shaders, ~5 fps) on X11. The patched output opens Flutter's EGL display itself, steps GDK's GLX context aside on X11 and releases its own context. Patch from a friend's agent, kept in third_party/media_kit_video/starsync/. Wayland/NVIDIA still renders on the GPU (checked); the X11 path was verified on the friend's machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
25b19f2d6a
commit
a87ee98faa
131 files changed
+15532
-6
No files matched your search
@@ -0,0 +1,99 @@
|
||||
# This file is a part of media_kit (https://github.com/media-kit/media-kit).
|
||||
#
|
||||
# Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
# All rights reserved.
|
||||
# Use of this source code is governed by MIT license that can be found in the LICENSE file.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(PROJECT_NAME "media_kit_video")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
set(PLUGIN_NAME "media_kit_video_plugin")
|
||||
|
||||
if(MEDIA_KIT_LIBS_AVAILABLE)
|
||||
add_library(
|
||||
${PLUGIN_NAME} SHARED
|
||||
"media_kit_video_plugin.cc"
|
||||
"texture_gl.cc"
|
||||
"texture_sw.cc"
|
||||
"video_output_manager.cc"
|
||||
"video_output.cc"
|
||||
"utils.cc"
|
||||
)
|
||||
|
||||
apply_standard_settings(${PLUGIN_NAME})
|
||||
|
||||
# Check for libmpv & epoxy headers & libraries.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(mpv IMPORTED_TARGET mpv)
|
||||
pkg_check_modules(epoxy IMPORTED_TARGET epoxy)
|
||||
|
||||
set_target_properties(
|
||||
${PLUGIN_NAME} PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
${PLUGIN_NAME} PRIVATE
|
||||
FLUTTER_PLUGIN_IMPL
|
||||
)
|
||||
|
||||
target_compile_options(${PLUGIN_NAME} PRIVATE "${mpv_CFLAGS_OTHER}")
|
||||
target_compile_options(${PLUGIN_NAME} PRIVATE "${epoxy_CFLAGS_OTHER}")
|
||||
|
||||
target_include_directories(
|
||||
${PLUGIN_NAME} INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${mpv_INCLUDE_DIRS}"
|
||||
"${epoxy_INCLUDE_DIRS}"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${PLUGIN_NAME} PRIVATE
|
||||
flutter
|
||||
PkgConfig::GTK
|
||||
PkgConfig::mpv
|
||||
PkgConfig::epoxy
|
||||
)
|
||||
else()
|
||||
message(NOTICE "media_kit: WARNING: package:media_kit_libs_*** not found.")
|
||||
|
||||
add_library(
|
||||
${PLUGIN_NAME} SHARED
|
||||
"media_kit_video_plugin.cc"
|
||||
)
|
||||
|
||||
apply_standard_settings(${PLUGIN_NAME})
|
||||
|
||||
set_target_properties(
|
||||
${PLUGIN_NAME} PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
${PLUGIN_NAME} PRIVATE
|
||||
FLUTTER_PLUGIN_IMPL
|
||||
|
||||
# Add macro to indicate that package:media_kit_libs_*** is not available.
|
||||
"MEDIA_KIT_LIBS_NOT_FOUND=1"
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
${PLUGIN_NAME} INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${PLUGIN_NAME} PRIVATE
|
||||
flutter
|
||||
PkgConfig::GTK
|
||||
)
|
||||
endif()
|
||||
|
||||
set(
|
||||
media_kit_video_bundled_libraries
|
||||
""
|
||||
PARENT_SCOPE
|
||||
)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#ifndef MEDIA_KIT_VIDEO_PLUGIN_H_
|
||||
#define MEDIA_KIT_VIDEO_PLUGIN_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#ifdef FLUTTER_PLUGIN_IMPL
|
||||
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define FLUTTER_PLUGIN_EXPORT
|
||||
#endif
|
||||
|
||||
typedef struct _MediaKitVideoPlugin MediaKitVideoPlugin;
|
||||
typedef struct {
|
||||
GObjectClass parent_class;
|
||||
} MediaKitVideoPluginClass;
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT GType media_kit_video_plugin_get_type();
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void media_kit_video_plugin_register_with_registrar(
|
||||
FlPluginRegistrar* registrar);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_PLUGIN_MEDIA_KIT_VIDEO_PLUGIN_H_
|
||||
@@ -0,0 +1,35 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#ifndef TEXTURE_GL_H_
|
||||
#define TEXTURE_GL_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
#include "video_output.h"
|
||||
|
||||
#define TEXTURE_GL_TYPE (texture_gl_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE(TextureGL, texture_gl, TEXTURE_GL, TEXTURE_GL, FlTextureGL)
|
||||
|
||||
#define TEXTURE_GL(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), texture_gl_get_type(), TextureGL))
|
||||
|
||||
TextureGL* texture_gl_new(VideoOutput* video_output);
|
||||
|
||||
/**
|
||||
* @brief Populates texture with video frame.
|
||||
*/
|
||||
gboolean texture_gl_populate_texture(FlTextureGL* texture,
|
||||
guint32* target,
|
||||
guint32* name,
|
||||
guint32* width,
|
||||
guint32* height,
|
||||
GError** error);
|
||||
|
||||
#endif // TEXTURE_GL_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#ifndef TEXTURE_SW_H_
|
||||
#define TEXTURE_SW_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
#include "video_output.h"
|
||||
|
||||
#define TEXTURE_SW_TYPE (texture_sw_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE(TextureSW,
|
||||
texture_sw,
|
||||
TEXTURE_SW,
|
||||
TEXTURE_SW,
|
||||
FlPixelBufferTexture)
|
||||
|
||||
#define TEXTURE_SW(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), texture_sw_get_type(), TextureSW))
|
||||
|
||||
TextureSW* texture_sw_new(VideoOutput* video_output);
|
||||
|
||||
gboolean texture_sw_copy_pixels(FlPixelBufferTexture* texture,
|
||||
const guint8** buffer,
|
||||
guint32* width,
|
||||
guint32* height,
|
||||
GError** error);
|
||||
|
||||
// Limit the frame size to 1080p in software rendering.
|
||||
// This is for performance reasons & to avoid allocating too much memory.
|
||||
#define SW_RENDERING_MAX_WIDTH 1920
|
||||
#define SW_RENDERING_MAX_HEIGHT 1080
|
||||
#define SW_RENDERING_PIXEL_BUFFER_SIZE \
|
||||
(SW_RENDERING_MAX_WIDTH) * (SW_RENDERING_MAX_HEIGHT) * (4)
|
||||
|
||||
#endif // TEXTURE_SW_H_
|
||||
@@ -0,0 +1,13 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
void utils_enter_native_fullscreen(GtkWidget* window);
|
||||
|
||||
void utils_exit_native_fullscreen(GtkWidget* window);
|
||||
@@ -0,0 +1,133 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#ifndef VIDEO_OUTPUT_H_
|
||||
#define VIDEO_OUTPUT_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#include <epoxy/egl.h>
|
||||
#include <epoxy/glx.h>
|
||||
|
||||
#include "mpv/client.h"
|
||||
#include "mpv/render.h"
|
||||
#include "mpv/render_gl.h"
|
||||
|
||||
typedef struct _VideoOutputConfiguration {
|
||||
gint64 width;
|
||||
gint64 height;
|
||||
bool enable_hardware_acceleration;
|
||||
|
||||
_VideoOutputConfiguration(gint64 width = NULL,
|
||||
gint64 height = NULL,
|
||||
bool enable_hardware_acceleration = true)
|
||||
: width(width),
|
||||
height(height),
|
||||
enable_hardware_acceleration(enable_hardware_acceleration) {}
|
||||
} VideoOutputConfiguration;
|
||||
|
||||
// Callback invoked when the texture ID updates i.e. video dimensions changes.
|
||||
typedef void (*TextureUpdateCallback)(gint64 id,
|
||||
gint64 width,
|
||||
gint64 height,
|
||||
gpointer context);
|
||||
|
||||
#define VIDEO_OUTPUT_TYPE (video_output_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE(VideoOutput,
|
||||
video_output,
|
||||
VIDEO_OUTPUT,
|
||||
VIDEO_OUTPUT,
|
||||
GObject)
|
||||
|
||||
#define VIDEO_OUTPUT(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), video_output_get_type(), VideoOutput))
|
||||
|
||||
/**
|
||||
* @brief Creates a new |VideoOutput| instance for given |handle|.
|
||||
*
|
||||
* @param texture_registrar |FlTextureRegistrar| reference.
|
||||
* @param handle |mpv_handle| reference casted to gint64.
|
||||
* @param width Preferred width of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param height Preferred height of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param enable_hardware_acceleration Whether to enable hardware acceleration.
|
||||
* @return VideoOutput*
|
||||
*/
|
||||
VideoOutput* video_output_new(FlTextureRegistrar* texture_registrar,
|
||||
FlView* view,
|
||||
gint64 handle,
|
||||
VideoOutputConfiguration configuration);
|
||||
|
||||
/**
|
||||
* @brief Sets the callback invoked when the texture ID updates i.e. video
|
||||
* dimensions changes.
|
||||
*
|
||||
* @param self |VideoOutput| reference.
|
||||
* @param texture_update_callback Callback.
|
||||
* @param texture_update_callback_context Callback context.
|
||||
*/
|
||||
void video_output_set_texture_update_callback(
|
||||
VideoOutput* self,
|
||||
TextureUpdateCallback texture_update_callback,
|
||||
gpointer texture_update_callback_context);
|
||||
|
||||
/**
|
||||
* @brief Sets the required video output size. This forces |VideoOutput| to
|
||||
* resize the internal OpenGL surface / texture.
|
||||
*
|
||||
* @param texture_registrar |FlTextureRegistrar| reference.
|
||||
* @param width Preferred width of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param height Preferred height of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
*/
|
||||
void video_output_set_size(VideoOutput* self, gint64 width, gint64 height);
|
||||
|
||||
mpv_render_context* video_output_get_render_context(VideoOutput* self);
|
||||
|
||||
GdkGLContext* video_output_get_gdk_gl_context(VideoOutput* self);
|
||||
|
||||
EGLDisplay video_output_get_egl_display(VideoOutput* self);
|
||||
|
||||
// GDK's GLX context of the calling thread, set aside while an EGL context
|
||||
// is used there.
|
||||
typedef struct {
|
||||
Display* display;
|
||||
GLXDrawable draw;
|
||||
GLXDrawable read;
|
||||
GLXContext context;
|
||||
} VideoOutputGlxState;
|
||||
|
||||
void video_output_glx_release(VideoOutputGlxState* state);
|
||||
|
||||
void video_output_glx_restore(VideoOutputGlxState* state);
|
||||
|
||||
// Makes |context| current again, or, when it is EGL_NO_CONTEXT, releases
|
||||
// whatever context of |own_display| this thread holds.
|
||||
void video_output_restore_egl_context(EGLDisplay own_display,
|
||||
EGLDisplay display,
|
||||
EGLSurface draw,
|
||||
EGLSurface read,
|
||||
EGLContext context);
|
||||
|
||||
EGLContext video_output_get_egl_context(VideoOutput* self);
|
||||
|
||||
EGLSurface video_output_get_egl_surface(VideoOutput* self);
|
||||
|
||||
guint8* video_output_get_pixel_buffer(VideoOutput* self);
|
||||
|
||||
gint64 video_output_get_width(VideoOutput* self);
|
||||
|
||||
gint64 video_output_get_height(VideoOutput* self);
|
||||
|
||||
gint64 video_output_get_texture_id(VideoOutput* self);
|
||||
|
||||
void video_output_notify_texture_update(VideoOutput* self);
|
||||
|
||||
#endif // VIDEO_OUTPUT_H_
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#ifndef VIDEO_OUTPUT_MANAGER_H_
|
||||
#define VIDEO_OUTPUT_MANAGER_H_
|
||||
|
||||
#include "video_output.h"
|
||||
|
||||
#define VIDEO_OUTPUT_MANAGER_TYPE (video_output_manager_get_type())
|
||||
|
||||
// Creates & disposes |VideoOutput| instances for video embedding.
|
||||
G_DECLARE_FINAL_TYPE(VideoOutputManager,
|
||||
video_output_manager,
|
||||
VIDEO_OUTPUT_MANAGER,
|
||||
VIDEO_OUTPUT_MANAGER,
|
||||
GObject)
|
||||
|
||||
#define VIDEO_OUTPUT_MANAGER(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), video_output_manager_get_type(), \
|
||||
VideoOutputManager))
|
||||
|
||||
VideoOutputManager* video_output_manager_new(
|
||||
FlTextureRegistrar* texture_registrar,
|
||||
FlView* view);
|
||||
|
||||
/**
|
||||
* @brief Creates a new |VideoOutput| instance for given |handle|.
|
||||
*
|
||||
* @param self |VideoOutputManager| reference.
|
||||
* @param handle |mpv_handle| reference casted to gint64.
|
||||
* @param width Preferred width of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param height Preferred height of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param enable_hardware_acceleration Whether to enable hardware acceleration.
|
||||
* @param texture_update_callback Callback invoked when the texture ID updates
|
||||
* i.e. video dimensions changes.
|
||||
* @param texture_update_callback_context Context passed to
|
||||
* |texture_update_callback|.
|
||||
*/
|
||||
void video_output_manager_create(VideoOutputManager* self,
|
||||
gint64 handle,
|
||||
VideoOutputConfiguration configuration,
|
||||
TextureUpdateCallback texture_update_callback,
|
||||
gpointer texture_update_callback_context);
|
||||
|
||||
/**
|
||||
* @brief Sets the required video output size. This forces |VideoOutput| to
|
||||
* resize the internal OpenGL surface / texture.
|
||||
*
|
||||
* @param texture_registrar |FlTextureRegistrar| reference.
|
||||
* @param width Preferred width of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
* @param height Preferred height of the video. Pass `NULL` for using texture
|
||||
* dimensions based on video's resolution.
|
||||
*/
|
||||
void video_output_manager_set_size(VideoOutputManager* self,
|
||||
gint64 handle,
|
||||
gint64 width,
|
||||
gint64 height);
|
||||
|
||||
/**
|
||||
* @brief Disposes |VideoOutput| instance for given |handle|.
|
||||
*
|
||||
* @param self |VideoOutputManager| reference.
|
||||
* @param handle |mpv_handle| reference casted to gint64.
|
||||
*/
|
||||
void video_output_manager_dispose(VideoOutputManager* self, gint64 handle);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,208 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/media_kit_video_plugin.h"
|
||||
|
||||
#ifndef MEDIA_KIT_LIBS_NOT_FOUND
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "include/media_kit_video/utils.h"
|
||||
#include "include/media_kit_video/video_output_manager.h"
|
||||
|
||||
#define MEDIA_KIT_VIDEO_PLUGIN(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), media_kit_video_plugin_get_type(), \
|
||||
MediaKitVideoPlugin))
|
||||
|
||||
struct _MediaKitVideoPlugin {
|
||||
GObject parent_instance;
|
||||
FlMethodChannel* channel;
|
||||
FlView* view;
|
||||
VideoOutputManager* video_output_manager;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MediaKitVideoPlugin, media_kit_video_plugin, g_object_get_type())
|
||||
|
||||
static void media_kit_video_plugin_handle_method_call(
|
||||
MediaKitVideoPlugin* self,
|
||||
FlMethodCall* method_call) {
|
||||
g_autoptr(FlMethodResponse) response = NULL;
|
||||
const gchar* method = fl_method_call_get_name(method_call);
|
||||
if (g_strcmp0(method, "VideoOutputManager.Create") == 0) {
|
||||
FlValue* arguments = fl_method_call_get_args(method_call);
|
||||
FlValue* handle = fl_value_lookup_string(arguments, "handle");
|
||||
FlValue* configuration = fl_value_lookup_string(arguments, "configuration");
|
||||
|
||||
gint64 handle_value =
|
||||
g_ascii_strtoll(fl_value_get_string(handle), NULL, 10);
|
||||
VideoOutputConfiguration configuration_value = {};
|
||||
|
||||
const gchar* configuration_width =
|
||||
fl_value_get_string(fl_value_lookup_string(configuration, "width"));
|
||||
const gchar* configuration_height =
|
||||
fl_value_get_string(fl_value_lookup_string(configuration, "height"));
|
||||
const bool configuration_enable_hardware_acceleration = fl_value_get_bool(
|
||||
fl_value_lookup_string(configuration, "enableHardwareAcceleration"));
|
||||
|
||||
if (g_strcmp0(configuration_width, "null") != 0) {
|
||||
configuration_value.width =
|
||||
g_ascii_strtoll(configuration_width, NULL, 10);
|
||||
}
|
||||
if (g_strcmp0(configuration_height, "null") != 0) {
|
||||
configuration_value.height =
|
||||
g_ascii_strtoll(configuration_height, NULL, 10);
|
||||
}
|
||||
configuration_value.enable_hardware_acceleration =
|
||||
configuration_enable_hardware_acceleration;
|
||||
|
||||
typedef struct _VideoOutputTextureUpdateCallbackData {
|
||||
FlMethodChannel* channel;
|
||||
gint64 handle;
|
||||
} VideoOutputTextureUpdateCallbackData;
|
||||
// TODO(@alexmercerind): Fix memory leak.
|
||||
VideoOutputTextureUpdateCallbackData* data =
|
||||
g_new0(VideoOutputTextureUpdateCallbackData, 1);
|
||||
data->channel = self->channel;
|
||||
data->handle = handle_value;
|
||||
video_output_manager_create(
|
||||
self->video_output_manager, handle_value, configuration_value,
|
||||
[](gint64 id, gint64 width, gint64 height, gpointer context) {
|
||||
auto data = (VideoOutputTextureUpdateCallbackData*)context;
|
||||
FlMethodChannel* channel = data->channel;
|
||||
gint64 handle = data->handle;
|
||||
FlValue* rect = fl_value_new_map();
|
||||
fl_value_set_string_take(rect, "left", fl_value_new_int(0));
|
||||
fl_value_set_string_take(rect, "top", fl_value_new_int(0));
|
||||
fl_value_set_string_take(rect, "width", fl_value_new_int(width));
|
||||
fl_value_set_string_take(rect, "height", fl_value_new_int(height));
|
||||
FlValue* result = fl_value_new_map();
|
||||
fl_value_set_string_take(result, "handle", fl_value_new_int(handle));
|
||||
fl_value_set_string_take(result, "id", fl_value_new_int(id));
|
||||
fl_value_set_string_take(result, "rect", rect);
|
||||
|
||||
typedef struct {
|
||||
FlMethodChannel* channel;
|
||||
FlValue* result;
|
||||
} IdleCallbackData;
|
||||
|
||||
IdleCallbackData* idle_data = g_new0(IdleCallbackData, 1);
|
||||
idle_data->channel = channel;
|
||||
idle_data->result = result;
|
||||
|
||||
// `fl_method_channel_invoke_method` should be called from PlatformThread.
|
||||
g_idle_add([](gpointer user_data) -> gboolean {
|
||||
IdleCallbackData* idle_data = (IdleCallbackData*)user_data;
|
||||
fl_method_channel_invoke_method(idle_data->channel, "VideoOutput.Resize",
|
||||
idle_data->result, NULL, NULL, NULL);
|
||||
g_free(idle_data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}, idle_data);
|
||||
},
|
||||
data);
|
||||
FlValue* result = fl_value_new_null();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else if (g_strcmp0(method, "VideoOutputManager.SetSize") == 0) {
|
||||
FlValue* arguments = fl_method_call_get_args(method_call);
|
||||
FlValue* handle = fl_value_lookup_string(arguments, "handle");
|
||||
FlValue* width = fl_value_lookup_string(arguments, "width");
|
||||
FlValue* height = fl_value_lookup_string(arguments, "height");
|
||||
gint64 handle_value =
|
||||
g_ascii_strtoll(fl_value_get_string(handle), NULL, 10);
|
||||
gint64 width_value = 0;
|
||||
gint64 height_value = 0;
|
||||
if (g_strcmp0(fl_value_get_string(width), "null") != 0) {
|
||||
width_value = g_ascii_strtoll(fl_value_get_string(width), NULL, 10);
|
||||
}
|
||||
if (g_strcmp0(fl_value_get_string(height), "null") != 0) {
|
||||
height_value = g_ascii_strtoll(fl_value_get_string(height), NULL, 10);
|
||||
}
|
||||
video_output_manager_set_size(self->video_output_manager, handle_value,
|
||||
width_value, height_value);
|
||||
FlValue* result = fl_value_new_null();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else if (g_strcmp0(method, "VideoOutputManager.Dispose") == 0) {
|
||||
FlValue* arguments = fl_method_call_get_args(method_call);
|
||||
FlValue* handle = fl_value_lookup_string(arguments, "handle");
|
||||
gint64 handle_value =
|
||||
g_ascii_strtoll(fl_value_get_string(handle), NULL, 10);
|
||||
video_output_manager_dispose(self->video_output_manager, handle_value);
|
||||
FlValue* result = fl_value_new_null();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
|
||||
} else if (g_strcmp0(method, "Utils.EnterNativeFullscreen") == 0) {
|
||||
utils_enter_native_fullscreen(
|
||||
gtk_widget_get_toplevel(GTK_WIDGET(self->view)));
|
||||
FlValue* result = fl_value_new_null();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else if (g_strcmp0(method, "Utils.ExitNativeFullscreen") == 0) {
|
||||
utils_exit_native_fullscreen(
|
||||
gtk_widget_get_toplevel(GTK_WIDGET(self->view)));
|
||||
FlValue* result = fl_value_new_null();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else {
|
||||
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
|
||||
}
|
||||
fl_method_call_respond(method_call, response, nullptr);
|
||||
}
|
||||
|
||||
static void media_kit_video_plugin_dispose(GObject* object) {
|
||||
G_OBJECT_CLASS(media_kit_video_plugin_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void media_kit_video_plugin_class_init(MediaKitVideoPluginClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = media_kit_video_plugin_dispose;
|
||||
}
|
||||
|
||||
static void media_kit_video_plugin_init(MediaKitVideoPlugin* self) {
|
||||
self->channel = NULL;
|
||||
self->video_output_manager = NULL;
|
||||
}
|
||||
|
||||
static void method_call_cb(FlMethodChannel* channel,
|
||||
FlMethodCall* method_call,
|
||||
gpointer user_data) {
|
||||
MediaKitVideoPlugin* plugin = MEDIA_KIT_VIDEO_PLUGIN(user_data);
|
||||
media_kit_video_plugin_handle_method_call(plugin, method_call);
|
||||
}
|
||||
|
||||
static MediaKitVideoPlugin* media_kit_video_plugin_new(
|
||||
FlPluginRegistrar* registrar) {
|
||||
MediaKitVideoPlugin* self = MEDIA_KIT_VIDEO_PLUGIN(
|
||||
g_object_new(media_kit_video_plugin_get_type(), nullptr));
|
||||
g_autoptr(FlMethodCodec) codec =
|
||||
FL_METHOD_CODEC(fl_standard_method_codec_new());
|
||||
self->channel =
|
||||
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
|
||||
"com.alexmercerind/media_kit_video", codec);
|
||||
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
|
||||
g_object_unref);
|
||||
FlTextureRegistrar* texture_registrar =
|
||||
fl_plugin_registrar_get_texture_registrar(registrar);
|
||||
FlView* view = fl_plugin_registrar_get_view(registrar);
|
||||
self->view = view;
|
||||
self->video_output_manager =
|
||||
video_output_manager_new(texture_registrar, view);
|
||||
return self;
|
||||
}
|
||||
|
||||
void media_kit_video_plugin_register_with_registrar(
|
||||
FlPluginRegistrar* registrar) {
|
||||
media_kit_video_plugin_new(registrar);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void media_kit_video_plugin_register_with_registrar(
|
||||
FlPluginRegistrar* registrar) {
|
||||
std::cout << "media_kit: WARNING: package:media_kit_libs_*** not found."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/texture_gl.h"
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
#include <epoxy/egl.h>
|
||||
|
||||
// EGLImage extension function pointers
|
||||
typedef EGLImageKHR (*PFNEGLCREATEIMAGEKHRPROC)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
|
||||
typedef EGLBoolean (*PFNEGLDESTROYIMAGEKHRPROC)(EGLDisplay dpy, EGLImageKHR image);
|
||||
typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)(GLenum target, GLeglImageOES image);
|
||||
|
||||
// Define the extension functions
|
||||
#ifndef eglCreateImageKHR
|
||||
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = NULL;
|
||||
#endif
|
||||
#ifndef eglDestroyImageKHR
|
||||
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = NULL;
|
||||
#endif
|
||||
#ifndef glEGLImageTargetTexture2DOES
|
||||
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = NULL;
|
||||
#endif
|
||||
|
||||
static void init_egl_image_extensions() {
|
||||
static gboolean initialized = FALSE;
|
||||
if (!initialized) {
|
||||
eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
|
||||
eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
|
||||
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
initialized = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
struct _TextureGL {
|
||||
FlTextureGL parent_instance;
|
||||
guint32 name; // Flutter's texture name
|
||||
guint32 fbo; // mpv's FBO
|
||||
guint32 mpv_texture; // mpv's texture
|
||||
EGLImageKHR egl_image; // EGLImage for sharing between contexts
|
||||
guint32 current_width;
|
||||
guint32 current_height;
|
||||
VideoOutput* video_output;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(TextureGL, texture_gl, fl_texture_gl_get_type())
|
||||
|
||||
static void texture_gl_init(TextureGL* self) {
|
||||
self->name = 0;
|
||||
self->fbo = 0;
|
||||
self->mpv_texture = 0;
|
||||
self->egl_image = EGL_NO_IMAGE_KHR;
|
||||
self->current_width = 1;
|
||||
self->current_height = 1;
|
||||
self->video_output = NULL;
|
||||
}
|
||||
|
||||
static void texture_gl_dispose(GObject* object) {
|
||||
TextureGL* self = TEXTURE_GL(object);
|
||||
VideoOutput* video_output = self->video_output;
|
||||
|
||||
VideoOutputGlxState glx_state;
|
||||
video_output_glx_release(&glx_state);
|
||||
|
||||
// Save current context
|
||||
EGLDisplay current_display = eglGetCurrentDisplay();
|
||||
EGLContext current_context = eglGetCurrentContext();
|
||||
EGLSurface current_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||
EGLSurface current_read = eglGetCurrentSurface(EGL_READ);
|
||||
|
||||
// Clean up Flutter's texture (in Flutter's context)
|
||||
if (self->name != 0) {
|
||||
glDeleteTextures(1, &self->name);
|
||||
self->name = 0;
|
||||
}
|
||||
|
||||
// Clean up EGLImage
|
||||
if (self->egl_image != EGL_NO_IMAGE_KHR && video_output != NULL) {
|
||||
EGLDisplay egl_display = video_output_get_egl_display(video_output);
|
||||
eglDestroyImageKHR(egl_display, self->egl_image);
|
||||
self->egl_image = EGL_NO_IMAGE_KHR;
|
||||
}
|
||||
|
||||
// Clean up mpv's OpenGL resources (in mpv's isolated context)
|
||||
if (video_output != NULL) {
|
||||
EGLDisplay egl_display = video_output_get_egl_display(video_output);
|
||||
EGLContext egl_context = video_output_get_egl_context(video_output);
|
||||
|
||||
if (egl_context != EGL_NO_CONTEXT) {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
|
||||
|
||||
if (self->mpv_texture != 0) {
|
||||
glDeleteTextures(1, &self->mpv_texture);
|
||||
self->mpv_texture = 0;
|
||||
}
|
||||
if (self->fbo != 0) {
|
||||
glDeleteFramebuffers(1, &self->fbo);
|
||||
self->fbo = 0;
|
||||
}
|
||||
|
||||
// Restore previous context
|
||||
video_output_restore_egl_context(egl_display, current_display,
|
||||
current_draw, current_read,
|
||||
current_context);
|
||||
}
|
||||
}
|
||||
|
||||
video_output_glx_restore(&glx_state);
|
||||
|
||||
self->current_width = 1;
|
||||
self->current_height = 1;
|
||||
self->video_output = NULL;
|
||||
G_OBJECT_CLASS(texture_gl_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void texture_gl_class_init(TextureGLClass* klass) {
|
||||
FL_TEXTURE_GL_CLASS(klass)->populate = texture_gl_populate_texture;
|
||||
G_OBJECT_CLASS(klass)->dispose = texture_gl_dispose;
|
||||
}
|
||||
|
||||
TextureGL* texture_gl_new(VideoOutput* video_output) {
|
||||
init_egl_image_extensions();
|
||||
TextureGL* self = TEXTURE_GL(g_object_new(texture_gl_get_type(), NULL));
|
||||
self->video_output = video_output;
|
||||
return self;
|
||||
}
|
||||
|
||||
gboolean texture_gl_populate_texture(FlTextureGL* texture,
|
||||
guint32* target,
|
||||
guint32* name,
|
||||
guint32* width,
|
||||
guint32* height,
|
||||
GError** error) {
|
||||
TextureGL* self = TEXTURE_GL(texture);
|
||||
VideoOutput* video_output = self->video_output;
|
||||
|
||||
gint32 required_width = (guint32)video_output_get_width(video_output);
|
||||
gint32 required_height = (guint32)video_output_get_height(video_output);
|
||||
|
||||
if (required_width > 0 && required_height > 0) {
|
||||
gboolean first_frame = self->name == 0 || self->fbo == 0 || self->mpv_texture == 0;
|
||||
gboolean resize = self->current_width != required_width ||
|
||||
self->current_height != required_height;
|
||||
|
||||
if (first_frame || resize) {
|
||||
// Save Flutter's current EGL context
|
||||
EGLDisplay flutter_display = eglGetCurrentDisplay();
|
||||
EGLContext flutter_context = eglGetCurrentContext();
|
||||
EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||
EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
|
||||
|
||||
EGLDisplay egl_display = video_output_get_egl_display(video_output);
|
||||
EGLContext egl_context = video_output_get_egl_context(video_output);
|
||||
|
||||
// Switch to mpv's isolated context to create/resize mpv's texture and FBO
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
|
||||
|
||||
// Free previous resources in mpv's context
|
||||
if (!first_frame) {
|
||||
glDeleteTextures(1, &self->mpv_texture);
|
||||
glDeleteFramebuffers(1, &self->fbo);
|
||||
if (self->egl_image != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(egl_display, self->egl_image);
|
||||
}
|
||||
}
|
||||
|
||||
// Create mpv's FBO and texture
|
||||
glGenFramebuffers(1, &self->fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, self->fbo);
|
||||
|
||||
glGenTextures(1, &self->mpv_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, self->mpv_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, required_width, required_height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
// Attach mpv's texture to FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, self->mpv_texture, 0);
|
||||
|
||||
// Create EGLImage from mpv's texture
|
||||
EGLint egl_image_attribs[] = { EGL_NONE };
|
||||
self->egl_image = eglCreateImageKHR(
|
||||
egl_display,
|
||||
egl_context,
|
||||
EGL_GL_TEXTURE_2D_KHR,
|
||||
(EGLClientBuffer)(guintptr)self->mpv_texture,
|
||||
egl_image_attribs);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// Flush to ensure mpv's texture is ready
|
||||
glFlush();
|
||||
|
||||
// Switch back to Flutter's context to create/update Flutter's texture
|
||||
eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context);
|
||||
|
||||
// Free previous Flutter texture
|
||||
if (!first_frame && self->name != 0) {
|
||||
glDeleteTextures(1, &self->name);
|
||||
}
|
||||
|
||||
// Create Flutter's texture from EGLImage
|
||||
glGenTextures(1, &self->name);
|
||||
glBindTexture(GL_TEXTURE_2D, self->name);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, self->egl_image);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
self->current_width = required_width;
|
||||
self->current_height = required_height;
|
||||
|
||||
// Notify Flutter about dimension change
|
||||
video_output_notify_texture_update(video_output);
|
||||
|
||||
// Flutter's context is already current, so we're ready to render
|
||||
}
|
||||
|
||||
// Save Flutter's current context
|
||||
EGLDisplay flutter_display = eglGetCurrentDisplay();
|
||||
EGLContext flutter_context = eglGetCurrentContext();
|
||||
EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||
EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
|
||||
|
||||
EGLDisplay egl_display = video_output_get_egl_display(video_output);
|
||||
EGLContext egl_context = video_output_get_egl_context(video_output);
|
||||
mpv_render_context* render_context = video_output_get_render_context(video_output);
|
||||
|
||||
// Switch to mpv's isolated context for rendering
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
|
||||
|
||||
// Bind mpv's FBO
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, self->fbo);
|
||||
|
||||
// Render mpv frame to mpv's texture
|
||||
mpv_opengl_fbo fbo{(gint32)self->fbo, required_width, required_height, 0};
|
||||
int flip_y = 0;
|
||||
mpv_render_param params[] = {
|
||||
{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
|
||||
{MPV_RENDER_PARAM_FLIP_Y, &flip_y},
|
||||
{MPV_RENDER_PARAM_INVALID, NULL},
|
||||
};
|
||||
mpv_render_context_render(render_context, params);
|
||||
|
||||
// Unbind FBO
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
// Flush to ensure rendering is complete
|
||||
glFlush();
|
||||
|
||||
// Restore Flutter's context
|
||||
eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context);
|
||||
}
|
||||
|
||||
*target = GL_TEXTURE_2D;
|
||||
*name = self->name;
|
||||
*width = self->current_width;
|
||||
*height = self->current_height;
|
||||
|
||||
if (self->name == 0) {
|
||||
// First frame not yet available - create dummy texture in Flutter's context
|
||||
glGenTextures(1, &self->name);
|
||||
glBindTexture(GL_TEXTURE_2D, self->name);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
*name = self->name;
|
||||
*width = 1;
|
||||
*height = 1;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/texture_sw.h"
|
||||
|
||||
struct _TextureSW {
|
||||
FlPixelBufferTexture parent_instance;
|
||||
guint32 current_width;
|
||||
guint32 current_height;
|
||||
VideoOutput* video_output;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(TextureSW, texture_sw, fl_pixel_buffer_texture_get_type())
|
||||
|
||||
static void texture_sw_init(TextureSW* self) {
|
||||
self->current_width = 1;
|
||||
self->current_height = 1;
|
||||
self->video_output = NULL;
|
||||
}
|
||||
|
||||
static void texture_sw_dispose(GObject* object) {
|
||||
G_OBJECT_CLASS(texture_sw_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void texture_sw_class_init(TextureSWClass* klass) {
|
||||
FL_PIXEL_BUFFER_TEXTURE_CLASS(klass)->copy_pixels = texture_sw_copy_pixels;
|
||||
G_OBJECT_CLASS(klass)->dispose = texture_sw_dispose;
|
||||
}
|
||||
|
||||
TextureSW* texture_sw_new(VideoOutput* video_output) {
|
||||
TextureSW* self = TEXTURE_SW(g_object_new(texture_sw_get_type(), NULL));
|
||||
self->video_output = video_output;
|
||||
return self;
|
||||
}
|
||||
|
||||
gboolean texture_sw_copy_pixels(FlPixelBufferTexture* texture,
|
||||
const guint8** buffer,
|
||||
guint32* width,
|
||||
guint32* height,
|
||||
GError** error) {
|
||||
TextureSW* self = TEXTURE_SW(texture);
|
||||
VideoOutput* video_output = self->video_output;
|
||||
gint32 required_width = (guint32)video_output_get_width(video_output);
|
||||
gint32 required_height = (guint32)video_output_get_height(video_output);
|
||||
if (required_width > 0 && required_height > 0) {
|
||||
guint8* pixel_buffer = video_output_get_pixel_buffer(video_output);
|
||||
if (self->current_width != required_width ||
|
||||
self->current_height != required_height) {
|
||||
self->current_width = required_width;
|
||||
self->current_height = required_height;
|
||||
// Notify Flutter about the change in texture's dimensions.
|
||||
video_output_notify_texture_update(video_output);
|
||||
}
|
||||
*buffer = pixel_buffer;
|
||||
*width = required_width;
|
||||
*height = required_height;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// // This file is a part of media_kit
|
||||
// // (https://github.com/media-kit/media-kit).
|
||||
// //
|
||||
// // Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// // All rights reserved.
|
||||
// // Use of this source code is governed by MIT license that can be found in
|
||||
// the
|
||||
// // LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/utils.h"
|
||||
|
||||
void utils_enter_native_fullscreen(GtkWidget* window) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(window));
|
||||
}
|
||||
|
||||
void utils_exit_native_fullscreen(GtkWidget* window) {
|
||||
gtk_window_unfullscreen(GTK_WINDOW(window));
|
||||
}
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/video_output.h"
|
||||
#include "include/media_kit_video/texture_gl.h"
|
||||
#include "include/media_kit_video/texture_sw.h"
|
||||
|
||||
#include <epoxy/egl.h>
|
||||
#include <epoxy/glx.h>
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gdk/gdkx.h>
|
||||
|
||||
// Flutter >= 3.38 keeps its EGL contexts on the raster thread, so nothing is
|
||||
// current on the platform thread (where |video_output_new| runs). Open the
|
||||
// display the way Flutter's FlOpenGLManager does: EGL hands back the same
|
||||
// EGLDisplay for the same platform and native display, which the EGLImage
|
||||
// shared in |texture_gl_populate_texture| needs. eglGetPlatformDisplayEXT is
|
||||
// looked up by hand: epoxy's dispatch for it needs a current display.
|
||||
static EGLDisplay flutter_egl_display() {
|
||||
typedef EGLDisplay (*GetPlatformDisplay)(EGLenum, void*, const EGLint*);
|
||||
GetPlatformDisplay get_platform_display =
|
||||
(GetPlatformDisplay)eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
if (get_platform_display == NULL) {
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
EGLDisplay egl_display = EGL_NO_DISPLAY;
|
||||
GdkDisplay* display = gdk_display_get_default();
|
||||
if (GDK_IS_WAYLAND_DISPLAY(display)) {
|
||||
egl_display = get_platform_display(
|
||||
EGL_PLATFORM_WAYLAND_EXT, gdk_wayland_display_get_wl_display(display),
|
||||
NULL);
|
||||
} else if (GDK_IS_X11_DISPLAY(display)) {
|
||||
egl_display = get_platform_display(
|
||||
EGL_PLATFORM_X11_EXT, gdk_x11_display_get_xdisplay(display), NULL);
|
||||
}
|
||||
if (egl_display != EGL_NO_DISPLAY &&
|
||||
!eglInitialize(egl_display, NULL, NULL)) {
|
||||
egl_display = EGL_NO_DISPLAY;
|
||||
}
|
||||
return egl_display;
|
||||
}
|
||||
|
||||
void video_output_glx_release(VideoOutputGlxState* state) {
|
||||
*state = VideoOutputGlxState{};
|
||||
// On X11, GDK draws Flutter's view with GLX on the platform thread, and
|
||||
// libglvnd refuses an EGL context (EGL_BAD_ACCESS) on a thread holding a
|
||||
// GLX one: step it aside while ours is in use.
|
||||
if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()) ||
|
||||
glXGetCurrentContext() == NULL) {
|
||||
return;
|
||||
}
|
||||
state->display = glXGetCurrentDisplay();
|
||||
state->draw = glXGetCurrentDrawable();
|
||||
state->read = glXGetCurrentReadDrawable();
|
||||
state->context = glXGetCurrentContext();
|
||||
glXMakeContextCurrent(state->display, None, None, NULL);
|
||||
}
|
||||
|
||||
void video_output_glx_restore(VideoOutputGlxState* state) {
|
||||
if (state->context != NULL) {
|
||||
glXMakeContextCurrent(state->display, state->draw, state->read,
|
||||
state->context);
|
||||
}
|
||||
}
|
||||
|
||||
void video_output_restore_egl_context(EGLDisplay own_display,
|
||||
EGLDisplay display,
|
||||
EGLSurface draw,
|
||||
EGLSurface read,
|
||||
EGLContext context) {
|
||||
// Nothing was current (the platform thread): release ours, or it stays
|
||||
// current here and the raster thread cannot make it current (BadAccess).
|
||||
if (context == EGL_NO_CONTEXT) {
|
||||
eglMakeCurrent(own_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
EGL_NO_CONTEXT);
|
||||
} else {
|
||||
eglMakeCurrent(display, draw, read, context);
|
||||
}
|
||||
}
|
||||
|
||||
struct _VideoOutput {
|
||||
GObject parent_instance;
|
||||
TextureGL* texture_gl;
|
||||
EGLDisplay egl_display; /* EGL display for mpv rendering (shared with flutter). */
|
||||
EGLContext egl_context; /* Isolated EGL context (non-shared). */
|
||||
EGLSurface egl_surface; /* Place holder surface for activating egl context */
|
||||
guint8* pixel_buffer;
|
||||
TextureSW* texture_sw;
|
||||
GMutex mutex; /* Only used in S/W rendering. */
|
||||
mpv_handle* handle;
|
||||
mpv_render_context* render_context;
|
||||
gint64 width;
|
||||
gint64 height;
|
||||
VideoOutputConfiguration configuration;
|
||||
TextureUpdateCallback texture_update_callback;
|
||||
gpointer texture_update_callback_context;
|
||||
FlTextureRegistrar* texture_registrar;
|
||||
gboolean destroyed;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(VideoOutput, video_output, G_TYPE_OBJECT)
|
||||
|
||||
static void video_output_dispose(GObject* object) {
|
||||
VideoOutput* self = VIDEO_OUTPUT(object);
|
||||
self->destroyed = TRUE;
|
||||
|
||||
// Make sure that no more callbacks are invoked from mpv.
|
||||
if (self->render_context) {
|
||||
mpv_render_context_set_update_callback(self->render_context, NULL, NULL);
|
||||
}
|
||||
|
||||
// H/W
|
||||
if (self->texture_gl) {
|
||||
fl_texture_registrar_unregister_texture(self->texture_registrar,
|
||||
FL_TEXTURE(self->texture_gl));
|
||||
|
||||
// Save Flutter's current context before cleanup
|
||||
EGLDisplay current_display = eglGetCurrentDisplay();
|
||||
EGLContext flutter_context = eglGetCurrentContext();
|
||||
EGLSurface flutter_draw_surface = eglGetCurrentSurface(EGL_DRAW);
|
||||
EGLSurface flutter_read_surface = eglGetCurrentSurface(EGL_READ);
|
||||
|
||||
// Free mpv_render_context with our own isolated EGL context
|
||||
if (self->render_context != NULL) {
|
||||
VideoOutputGlxState glx_state;
|
||||
video_output_glx_release(&glx_state);
|
||||
if (self->egl_context != EGL_NO_CONTEXT) {
|
||||
eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, self->egl_context);
|
||||
}
|
||||
mpv_render_context_free(self->render_context);
|
||||
self->render_context = NULL;
|
||||
|
||||
// Restore Flutter's context
|
||||
video_output_restore_egl_context(self->egl_display, current_display,
|
||||
flutter_draw_surface,
|
||||
flutter_read_surface, flutter_context);
|
||||
video_output_glx_restore(&glx_state);
|
||||
}
|
||||
|
||||
// Clean up EGL resources
|
||||
if (self->egl_context != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(self->egl_display, self->egl_context);
|
||||
self->egl_context = EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
g_object_unref(self->texture_gl);
|
||||
}
|
||||
// S/W
|
||||
if (self->texture_sw) {
|
||||
fl_texture_registrar_unregister_texture(self->texture_registrar,
|
||||
FL_TEXTURE(self->texture_sw));
|
||||
g_free(self->pixel_buffer);
|
||||
g_object_unref(self->texture_sw);
|
||||
if (self->render_context != NULL) {
|
||||
mpv_render_context_free(self->render_context);
|
||||
self->render_context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
g_mutex_clear(&self->mutex);
|
||||
G_OBJECT_CLASS(video_output_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void video_output_class_init(VideoOutputClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = video_output_dispose;
|
||||
}
|
||||
|
||||
static void video_output_init(VideoOutput* self) {
|
||||
self->texture_gl = NULL;
|
||||
self->egl_display = EGL_NO_DISPLAY;
|
||||
self->egl_context = EGL_NO_CONTEXT;
|
||||
self->egl_surface = EGL_NO_SURFACE;
|
||||
self->texture_sw = NULL;
|
||||
self->pixel_buffer = NULL;
|
||||
self->handle = NULL;
|
||||
self->render_context = NULL;
|
||||
self->width = 0;
|
||||
self->height = 0;
|
||||
self->configuration = VideoOutputConfiguration{};
|
||||
self->texture_update_callback = NULL;
|
||||
self->texture_update_callback_context = NULL;
|
||||
self->texture_registrar = NULL;
|
||||
self->destroyed = FALSE;
|
||||
g_mutex_init(&self->mutex);
|
||||
}
|
||||
|
||||
VideoOutput* video_output_new(FlTextureRegistrar* texture_registrar,
|
||||
FlView* view,
|
||||
gint64 handle,
|
||||
VideoOutputConfiguration configuration) {
|
||||
VideoOutput* self = VIDEO_OUTPUT(g_object_new(video_output_get_type(), NULL));
|
||||
self->texture_registrar = texture_registrar;
|
||||
self->handle = (mpv_handle*)handle;
|
||||
self->width = configuration.width;
|
||||
self->height = configuration.height;
|
||||
self->configuration = configuration;
|
||||
#ifndef MPV_RENDER_API_TYPE_SW
|
||||
// MPV_RENDER_API_TYPE_SW must be available for S/W rendering.
|
||||
if (!self->configuration.enable_hardware_acceleration) {
|
||||
g_printerr("media_kit: VideoOutput: S/W rendering is not supported.\n");
|
||||
}
|
||||
self->configuration.enable_hardware_acceleration = TRUE;
|
||||
#endif
|
||||
mpv_set_option_string(self->handle, "video-sync", "audio");
|
||||
// Causes frame drops with `pulse` audio output. (SlotSun/dart_simple_live#42)
|
||||
// mpv_set_option_string(self->handle, "video-timing-offset", "0");
|
||||
gboolean hardware_acceleration_supported = FALSE;
|
||||
if (self->configuration.enable_hardware_acceleration) {
|
||||
VideoOutputGlxState glx_state;
|
||||
video_output_glx_release(&glx_state);
|
||||
// Get Flutter's current EGL display (DO NOT share context)
|
||||
EGLDisplay flutter_display = eglGetCurrentDisplay();
|
||||
EGLContext flutter_context = eglGetCurrentContext();
|
||||
EGLSurface flutter_draw_surface = eglGetCurrentSurface(EGL_DRAW);
|
||||
EGLSurface flutter_read_surface = eglGetCurrentSurface(EGL_READ);
|
||||
|
||||
EGLDisplay egl_display = flutter_context != EGL_NO_CONTEXT
|
||||
? flutter_display
|
||||
: flutter_egl_display();
|
||||
|
||||
if (egl_display != EGL_NO_DISPLAY) {
|
||||
self->egl_display = egl_display;
|
||||
|
||||
// Bind OpenGL ES API (Flutter uses OpenGL ES on Linux)
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
|
||||
// Query Flutter's EGL config and reuse it for compatibility
|
||||
EGLConfig config = NULL;
|
||||
EGLint config_id = 0;
|
||||
|
||||
if (flutter_context == EGL_NO_CONTEXT) {
|
||||
// The attributes FlOpenGLManager chooses Flutter's config with.
|
||||
EGLint num_configs = 0;
|
||||
const EGLint config_attribs[] = {
|
||||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,
|
||||
EGL_NONE,
|
||||
};
|
||||
if (!eglChooseConfig(self->egl_display, config_attribs, &config, 1,
|
||||
&num_configs) ||
|
||||
num_configs == 0) {
|
||||
g_printerr("media_kit: VideoOutput: Failed to choose an EGL config.\n");
|
||||
config = NULL;
|
||||
}
|
||||
} else if (eglQueryContext(self->egl_display, flutter_context, EGL_CONFIG_ID, &config_id)) {
|
||||
g_print("media_kit: VideoOutput: Flutter's EGL config ID: %d\n", config_id);
|
||||
|
||||
// Get Flutter's exact config
|
||||
EGLint num_configs = 0;
|
||||
EGLint config_attribs[] = { EGL_CONFIG_ID, config_id, EGL_NONE };
|
||||
|
||||
if (eglChooseConfig(self->egl_display, config_attribs, &config, 1, &num_configs) && num_configs > 0) {
|
||||
g_print("media_kit: VideoOutput: Using Flutter's EGL config.\n");
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to get Flutter's EGL config by ID.\n");
|
||||
config = NULL;
|
||||
}
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to query Flutter's EGL config ID.\n");
|
||||
}
|
||||
|
||||
if (config != NULL) {
|
||||
// Create an isolated EGL context (NOT shared with Flutter)
|
||||
// This prevents OpenGL state pollution and resource contention
|
||||
EGLint context_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE,
|
||||
};
|
||||
|
||||
self->egl_context = eglCreateContext(self->egl_display, config,
|
||||
EGL_NO_CONTEXT, context_attribs);
|
||||
|
||||
if (self->egl_context != EGL_NO_CONTEXT) {
|
||||
// Make our isolated context current for initialization (surfaceless)
|
||||
if (eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, self->egl_context)) {
|
||||
// Create texture with our isolated context
|
||||
self->texture_gl = texture_gl_new(self);
|
||||
|
||||
if (fl_texture_registrar_register_texture(
|
||||
texture_registrar, FL_TEXTURE(self->texture_gl))) {
|
||||
// Initialize mpv with our isolated EGL context
|
||||
mpv_opengl_init_params gl_init_params{
|
||||
[](auto, auto name) {
|
||||
return (void*)eglGetProcAddress(name);
|
||||
},
|
||||
NULL,
|
||||
};
|
||||
|
||||
mpv_render_param params[] = {
|
||||
{MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_OPENGL},
|
||||
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, (void*)&gl_init_params},
|
||||
{MPV_RENDER_PARAM_INVALID, (void*)0},
|
||||
{MPV_RENDER_PARAM_INVALID, (void*)0},
|
||||
};
|
||||
|
||||
// VAAPI acceleration requires passing X11/Wayland display
|
||||
GdkDisplay* display = gdk_display_get_default();
|
||||
if (GDK_IS_WAYLAND_DISPLAY(display)) {
|
||||
params[2].type = MPV_RENDER_PARAM_WL_DISPLAY;
|
||||
params[2].data = gdk_wayland_display_get_wl_display(display);
|
||||
} else if (GDK_IS_X11_DISPLAY(display)) {
|
||||
params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
|
||||
params[2].data = gdk_x11_display_get_xdisplay(display);
|
||||
}
|
||||
|
||||
if (mpv_render_context_create(&self->render_context, self->handle, params) == 0) {
|
||||
mpv_render_context_set_update_callback(
|
||||
self->render_context,
|
||||
[](void* data) {
|
||||
VideoOutput* self = (VideoOutput*)data;
|
||||
if (self->destroyed) {
|
||||
return;
|
||||
}
|
||||
fl_texture_registrar_mark_texture_frame_available(
|
||||
self->texture_registrar, FL_TEXTURE(self->texture_gl));
|
||||
},
|
||||
self);
|
||||
hardware_acceleration_supported = TRUE;
|
||||
g_print("media_kit: VideoOutput: H/W rendering with isolated EGL context.\n");
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to create mpv_render_context.\n");
|
||||
}
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to register texture.\n");
|
||||
}
|
||||
|
||||
// Restore Flutter's context
|
||||
video_output_restore_egl_context(
|
||||
self->egl_display, flutter_display, flutter_draw_surface,
|
||||
flutter_read_surface, flutter_context);
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to make isolated EGL context current. Error: 0x%x\n", eglGetError());
|
||||
}
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Failed to create isolated EGL context. Error: 0x%x\n", eglGetError());
|
||||
}
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: Could not obtain Flutter's EGL config.\n");
|
||||
}
|
||||
} else {
|
||||
g_printerr("media_kit: VideoOutput: EGL display is invalid.\n");
|
||||
}
|
||||
video_output_glx_restore(&glx_state);
|
||||
}
|
||||
#ifdef MPV_RENDER_API_TYPE_SW
|
||||
if (!hardware_acceleration_supported) {
|
||||
g_printerr("media_kit: VideoOutput: S/W rendering.\n");
|
||||
// H/W rendering failed. Fallback to S/W rendering.
|
||||
self->pixel_buffer = g_new0(guint8, SW_RENDERING_PIXEL_BUFFER_SIZE);
|
||||
self->texture_gl = NULL;
|
||||
self->texture_sw = texture_sw_new(self);
|
||||
if (fl_texture_registrar_register_texture(texture_registrar,
|
||||
FL_TEXTURE(self->texture_sw))) {
|
||||
mpv_render_param params[] = {
|
||||
{MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_SW},
|
||||
{MPV_RENDER_PARAM_INVALID, (void*)0},
|
||||
};
|
||||
if (mpv_render_context_create(&self->render_context, self->handle,
|
||||
params) == 0) {
|
||||
mpv_render_context_set_update_callback(
|
||||
self->render_context,
|
||||
[](void* data) {
|
||||
gdk_threads_add_idle(
|
||||
[](gpointer data) -> gboolean {
|
||||
VideoOutput* self = (VideoOutput*)data;
|
||||
if (self->destroyed) {
|
||||
return FALSE;
|
||||
}
|
||||
g_mutex_lock(&self->mutex);
|
||||
gint64 width = video_output_get_width(self);
|
||||
gint64 height = video_output_get_height(self);
|
||||
if (width > 0 && height > 0) {
|
||||
gint32 size[]{(gint32)width, (gint32)height};
|
||||
gint32 pitch = 4 * (gint32)width;
|
||||
mpv_render_param params[]{
|
||||
{MPV_RENDER_PARAM_SW_SIZE, size},
|
||||
{MPV_RENDER_PARAM_SW_FORMAT, (void*)"rgb0"},
|
||||
{MPV_RENDER_PARAM_SW_STRIDE, &pitch},
|
||||
{MPV_RENDER_PARAM_SW_POINTER, self->pixel_buffer},
|
||||
{MPV_RENDER_PARAM_INVALID, (void*)0},
|
||||
};
|
||||
mpv_render_context_render(self->render_context, params);
|
||||
fl_texture_registrar_mark_texture_frame_available(
|
||||
self->texture_registrar,
|
||||
FL_TEXTURE(self->texture_sw));
|
||||
}
|
||||
g_mutex_unlock(&self->mutex);
|
||||
return FALSE;
|
||||
},
|
||||
data);
|
||||
},
|
||||
self);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return self;
|
||||
}
|
||||
|
||||
void video_output_set_texture_update_callback(
|
||||
VideoOutput* self,
|
||||
TextureUpdateCallback texture_update_callback,
|
||||
gpointer texture_update_callback_context) {
|
||||
self->texture_update_callback = texture_update_callback;
|
||||
self->texture_update_callback_context = texture_update_callback_context;
|
||||
// Notify initial dimensions as (1, 1) if |width| & |height| are 0 i.e.
|
||||
// texture & video frame size is based on playing file's resolution. This
|
||||
// will make sure that `Texture` widget on Flutter's widget tree is actually
|
||||
// mounted & |fl_texture_registrar_mark_texture_frame_available| actually
|
||||
// invokes the |TextureGL| or |TextureSW| callbacks. Otherwise it will be a
|
||||
// never ending deadlock where no video frames are ever rendered.
|
||||
gint64 texture_id = video_output_get_texture_id(self);
|
||||
if (self->width == 0 || self->height == 0) {
|
||||
self->texture_update_callback(texture_id, 1, 1,
|
||||
self->texture_update_callback_context);
|
||||
} else {
|
||||
self->texture_update_callback(texture_id, self->width, self->height,
|
||||
self->texture_update_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void video_output_set_size(VideoOutput* self, gint64 width, gint64 height) {
|
||||
// Ideally, a mutex should be used here & |video_output_get_width| +
|
||||
// |video_output_get_height|. However, that is throwing everything into a
|
||||
// deadlock. Flutter itself seems to have some synchronization mechanism in
|
||||
// rendering & platform channels AFAIK.
|
||||
|
||||
// H/W
|
||||
if (self->texture_gl) {
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
}
|
||||
// S/W
|
||||
if (self->texture_sw) {
|
||||
self->width = CLAMP(width, 0, SW_RENDERING_MAX_WIDTH);
|
||||
self->height = CLAMP(height, 0, SW_RENDERING_MAX_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
mpv_render_context* video_output_get_render_context(VideoOutput* self) {
|
||||
return self->render_context;
|
||||
}
|
||||
|
||||
EGLDisplay video_output_get_egl_display(VideoOutput* self) {
|
||||
return self->egl_display;
|
||||
}
|
||||
|
||||
EGLContext video_output_get_egl_context(VideoOutput* self) {
|
||||
return self->egl_context;
|
||||
}
|
||||
|
||||
EGLSurface video_output_get_egl_surface(VideoOutput* self) {
|
||||
return self->egl_surface;
|
||||
}
|
||||
|
||||
guint8* video_output_get_pixel_buffer(VideoOutput* self) {
|
||||
return self->pixel_buffer;
|
||||
}
|
||||
|
||||
gint64 video_output_get_width(VideoOutput* self) {
|
||||
// Fixed width.
|
||||
if (self->width) {
|
||||
return self->width;
|
||||
}
|
||||
|
||||
// Video resolution dependent width.
|
||||
gint64 width = 0;
|
||||
gint64 height = 0;
|
||||
|
||||
mpv_node params;
|
||||
mpv_get_property(self->handle, "video-out-params", MPV_FORMAT_NODE, ¶ms);
|
||||
|
||||
int64_t dw = 0, dh = 0, rotate = 0;
|
||||
if (params.format == MPV_FORMAT_NODE_MAP) {
|
||||
for (int32_t i = 0; i < params.u.list->num; i++) {
|
||||
char* key = params.u.list->keys[i];
|
||||
auto value = params.u.list->values[i];
|
||||
if (value.format == MPV_FORMAT_INT64) {
|
||||
if (strcmp(key, "dw") == 0) {
|
||||
dw = value.u.int64;
|
||||
}
|
||||
if (strcmp(key, "dh") == 0) {
|
||||
dh = value.u.int64;
|
||||
}
|
||||
if (strcmp(key, "rotate") == 0) {
|
||||
rotate = value.u.int64;
|
||||
}
|
||||
}
|
||||
}
|
||||
mpv_free_node_contents(¶ms);
|
||||
}
|
||||
|
||||
width = rotate == 0 || rotate == 180 ? dw : dh;
|
||||
height = rotate == 0 || rotate == 180 ? dh : dw;
|
||||
|
||||
if (self->texture_sw != NULL) {
|
||||
// Make sure |width| & |height| fit between |SW_RENDERING_MAX_WIDTH| &
|
||||
// |SW_RENDERING_MAX_HEIGHT| while maintaining aspect ratio.
|
||||
if (width >= SW_RENDERING_MAX_WIDTH) {
|
||||
return SW_RENDERING_MAX_WIDTH;
|
||||
}
|
||||
if (height >= SW_RENDERING_MAX_HEIGHT) {
|
||||
return width / height * SW_RENDERING_MAX_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
gint64 video_output_get_height(VideoOutput* self) {
|
||||
// Fixed height.
|
||||
if (self->width) {
|
||||
return self->height;
|
||||
}
|
||||
|
||||
// Video resolution dependent height.
|
||||
gint64 width = 0;
|
||||
gint64 height = 0;
|
||||
|
||||
mpv_node params;
|
||||
mpv_get_property(self->handle, "video-out-params", MPV_FORMAT_NODE, ¶ms);
|
||||
|
||||
int64_t dw = 0, dh = 0, rotate = 0;
|
||||
if (params.format == MPV_FORMAT_NODE_MAP) {
|
||||
for (int32_t i = 0; i < params.u.list->num; i++) {
|
||||
char* key = params.u.list->keys[i];
|
||||
auto value = params.u.list->values[i];
|
||||
if (value.format == MPV_FORMAT_INT64) {
|
||||
if (strcmp(key, "dw") == 0) {
|
||||
dw = value.u.int64;
|
||||
}
|
||||
if (strcmp(key, "dh") == 0) {
|
||||
dh = value.u.int64;
|
||||
}
|
||||
if (strcmp(key, "rotate") == 0) {
|
||||
rotate = value.u.int64;
|
||||
}
|
||||
}
|
||||
}
|
||||
mpv_free_node_contents(¶ms);
|
||||
}
|
||||
|
||||
width = rotate == 0 || rotate == 180 ? dw : dh;
|
||||
height = rotate == 0 || rotate == 180 ? dh : dw;
|
||||
|
||||
if (self->texture_sw != NULL) {
|
||||
// Make sure |width| & |height| fit between |SW_RENDERING_MAX_WIDTH| &
|
||||
// |SW_RENDERING_MAX_HEIGHT| while maintaining aspect ratio.
|
||||
if (height >= SW_RENDERING_MAX_HEIGHT) {
|
||||
return SW_RENDERING_MAX_HEIGHT;
|
||||
}
|
||||
if (width >= SW_RENDERING_MAX_WIDTH) {
|
||||
return height / width * SW_RENDERING_MAX_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
gint64 video_output_get_texture_id(VideoOutput* self) {
|
||||
// H/W
|
||||
if (self->texture_gl) {
|
||||
return (gint64)self->texture_gl;
|
||||
}
|
||||
// S/W
|
||||
if (self->texture_sw) {
|
||||
return (gint64)self->texture_sw;
|
||||
}
|
||||
g_assert_not_reached();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void video_output_notify_texture_update(VideoOutput* self) {
|
||||
gint64 id = video_output_get_texture_id(self);
|
||||
gint64 width = video_output_get_width(self);
|
||||
gint64 height = video_output_get_height(self);
|
||||
gpointer context = self->texture_update_callback_context;
|
||||
if (self->texture_update_callback != NULL) {
|
||||
self->texture_update_callback(id, width, height, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// This file is a part of media_kit
|
||||
// (https://github.com/media-kit/media-kit).
|
||||
//
|
||||
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by MIT license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
#include "include/media_kit_video/video_output_manager.h"
|
||||
|
||||
struct _VideoOutputManager {
|
||||
GObject parent_instance;
|
||||
GHashTable* video_outputs;
|
||||
FlTextureRegistrar* texture_registrar;
|
||||
FlView* view;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(VideoOutputManager, video_output_manager, G_TYPE_OBJECT)
|
||||
|
||||
static void video_output_manager_init(VideoOutputManager* self) {
|
||||
self->video_outputs = g_hash_table_new_full(g_direct_hash, g_direct_equal,
|
||||
nullptr, g_object_unref);
|
||||
}
|
||||
|
||||
static void video_output_manager_dispose(GObject* object) {
|
||||
VideoOutputManager* self = VIDEO_OUTPUT_MANAGER(object);
|
||||
g_hash_table_unref(self->video_outputs);
|
||||
G_OBJECT_CLASS(video_output_manager_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void video_output_manager_class_init(VideoOutputManagerClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = video_output_manager_dispose;
|
||||
}
|
||||
|
||||
VideoOutputManager* video_output_manager_new(
|
||||
FlTextureRegistrar* texture_registrar,
|
||||
FlView* view) {
|
||||
VideoOutputManager* video_output_manager = VIDEO_OUTPUT_MANAGER(
|
||||
g_object_new(video_output_manager_get_type(), nullptr));
|
||||
video_output_manager->texture_registrar = texture_registrar;
|
||||
video_output_manager->view = view;
|
||||
return video_output_manager;
|
||||
}
|
||||
|
||||
void video_output_manager_create(VideoOutputManager* self,
|
||||
gint64 handle,
|
||||
VideoOutputConfiguration configuration,
|
||||
TextureUpdateCallback texture_update_callback,
|
||||
gpointer texture_update_callback_context) {
|
||||
if (!g_hash_table_contains(self->video_outputs, GINT_TO_POINTER(handle))) {
|
||||
g_autoptr(VideoOutput) video_output = video_output_new(
|
||||
self->texture_registrar, self->view, handle, configuration);
|
||||
video_output_set_texture_update_callback(
|
||||
video_output, texture_update_callback, texture_update_callback_context);
|
||||
g_hash_table_insert(self->video_outputs, GINT_TO_POINTER(handle),
|
||||
g_object_ref(video_output));
|
||||
}
|
||||
}
|
||||
|
||||
void video_output_manager_set_size(VideoOutputManager* self,
|
||||
gint64 handle,
|
||||
gint64 width,
|
||||
gint64 height) {
|
||||
if (g_hash_table_contains(self->video_outputs, GINT_TO_POINTER(handle))) {
|
||||
VideoOutput* video_output = VIDEO_OUTPUT(
|
||||
g_hash_table_lookup(self->video_outputs, GINT_TO_POINTER(handle)));
|
||||
video_output_set_size(video_output, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void video_output_manager_dispose(VideoOutputManager* self, gint64 handle) {
|
||||
if (g_hash_table_contains(self->video_outputs, GINT_TO_POINTER(handle))) {
|
||||
g_hash_table_remove(self->video_outputs, GINT_TO_POINTER(handle));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user