Vendor media_kit_video 2.0.1 with the X11/EGL fix (hardware rendering on X11 with Flutter >= 3.38)
ci / core (push) Successful in 28s
ci / server (push) Successful in 51s
ci / updater-windows (push) Failing after 5m59s

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:
foxyzandClaude Opus 5.5 committed 2026-10-16 01:13:14 +03:00
1 parent 25b19f2d6a
commit a87ee98faa
131 files changed
+15532 -6

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
# 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.14)
set(PROJECT_NAME "media_kit_video")
set(CMAKE_CXX_STANDARD 17)
project(${PROJECT_NAME} LANGUAGES CXX)
set(PLUGIN_NAME "media_kit_video_plugin")
# This is shipped as part of package:media_kit_libs_*** package(s).
# Must be built before this target.
set(LIBMPV_SRC "${CMAKE_BINARY_DIR}/libmpv")
set(ANGLE_SRC "${CMAKE_BINARY_DIR}/ANGLE")
# Deal with MSVC incompatiblity
add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
if(MEDIA_KIT_LIBS_AVAILABLE)
include_directories(
"${LIBMPV_SRC}/include"
"${ANGLE_SRC}/include"
)
add_library(
${PLUGIN_NAME} SHARED
"angle_surface_manager.cc"
"media_kit_video_plugin_c_api.cc"
"media_kit_video_plugin.cc"
"video_output_manager.cc"
"video_output.cc"
"utils.cc"
${PLUGIN_SOURCES}
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(
${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden
)
target_compile_definitions(
${PLUGIN_NAME} PRIVATE
FLUTTER_PLUGIN_IMPL
)
target_include_directories(
${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
)
target_link_libraries(
${PLUGIN_NAME} PRIVATE
flutter
flutter_wrapper_plugin
# Link to libmpv & ANGLE.
"${LIBMPV_SRC}/libmpv.dll.a"
"${ANGLE_SRC}/lib/libEGL.dll.lib"
"${ANGLE_SRC}/lib/libGLESv2.dll.lib"
)
else()
message(NOTICE "media_kit: WARNING: package:media_kit_libs_*** not found.")
add_library(
${PLUGIN_NAME} SHARED
"media_kit_video_plugin_c_api.cc"
${PLUGIN_SOURCES}
)
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 libmpv & ANGLE from package:media_kit_libs_*** are not found.
"MEDIA_KIT_LIBS_NOT_FOUND=1"
)
target_include_directories(
${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
)
target_link_libraries(
${PLUGIN_NAME} PRIVATE
flutter
flutter_wrapper_plugin
)
endif()
set(
media_kit_video_bundled_libraries
""
PARENT_SCOPE
)
@@ -0,0 +1,307 @@
// 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 "angle_surface_manager.h"
#include <iostream>
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#define FAIL(message) \
std::cout << "media_kit: ANGLESurfaceManager: Failure: " << message \
<< std::endl; \
return false
#define CHECK_HRESULT(message) \
if (FAILED(hr)) { \
FAIL(message); \
}
int ANGLESurfaceManager::instance_count_ = 0;
ANGLESurfaceManager::ANGLESurfaceManager(int32_t width, int32_t height)
: width_(width), height_(height) {
mutex_ = ::CreateMutex(NULL, FALSE, NULL);
Create();
instance_count_++;
}
ANGLESurfaceManager::~ANGLESurfaceManager() {
CleanUp(true);
::ReleaseMutex(mutex_);
::CloseHandle(mutex_);
instance_count_--;
}
void ANGLESurfaceManager::SetSize(int32_t width, int32_t height) {
if (width == width_ && height == height_) {
return;
}
width_ = width;
height_ = height;
Create();
}
void ANGLESurfaceManager::Draw(std::function<void()> callback) {
::WaitForSingleObject(mutex_, INFINITE);
MakeCurrent(true);
callback();
SwapBuffers();
MakeCurrent(false);
::ReleaseMutex(mutex_);
}
void ANGLESurfaceManager::Read() {
::WaitForSingleObject(mutex_, INFINITE);
if (d3d_11_device_context_ != nullptr) {
d3d_11_device_context_->CopyResource(d3d_11_texture_2D_.Get(),
internal_d3d_11_texture_2D_.Get());
d3d_11_device_context_->Flush();
}
::ReleaseMutex(mutex_);
}
void ANGLESurfaceManager::MakeCurrent(bool value) {
if (value) {
eglMakeCurrent(display_, surface_, surface_, context_);
} else {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
}
void ANGLESurfaceManager::SwapBuffers() {
glFinish();
}
void ANGLESurfaceManager::Create() {
CleanUp(false);
if (!CreateD3DTexture()) {
throw std::runtime_error("Unable to create Windows Direct3D device.");
return;
}
if (!CreateEGLDisplay()) {
throw std::runtime_error("Unable to create ANGLE EGL display.");
return;
}
if (!CreateAndBindEGLSurface()) {
throw std::runtime_error("Unable to create ANGLE EGL surface.");
return;
}
if (internal_handle_ == nullptr || handle_ == nullptr) {
throw std::runtime_error("Unable to retrieve Direct3D shared HANDLE.");
return;
}
}
void ANGLESurfaceManager::CleanUp(bool release_context) {
if (release_context) {
if (display_ != EGL_NO_DISPLAY && surface_ != EGL_NO_SURFACE) {
eglReleaseTexImage(display_, surface_, EGL_BACK_BUFFER);
}
if (display_ != EGL_NO_DISPLAY && context_ != EGL_NO_CONTEXT) {
eglDestroyContext(display_, context_);
context_ = EGL_NO_CONTEXT;
}
if (surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
if (instance_count_ == 1) {
eglTerminate(display_);
}
display_ = EGL_NO_DISPLAY;
// Release D3D device & context if the instance is being destroyed.
if (d3d_11_device_context_) {
d3d_11_device_context_->Release();
d3d_11_device_context_ = nullptr;
}
if (d3d_11_device_) {
d3d_11_device_->Release();
d3d_11_device_ = nullptr;
}
} else {
// Clear context & destroy existing |surface_|.
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_);
if (display_ != EGL_NO_DISPLAY && surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
}
surface_ = EGL_NO_SURFACE;
}
// Release D3D 11 texture(s).
if (internal_d3d_11_texture_2D_) {
internal_d3d_11_texture_2D_->Release();
internal_d3d_11_texture_2D_ = nullptr;
}
if (d3d_11_texture_2D_) {
d3d_11_texture_2D_->Release();
d3d_11_texture_2D_ = nullptr;
}
}
bool ANGLESurfaceManager::CreateD3DTexture() {
if (d3d_11_device_ == nullptr) {
// NOTE: Not enabling Feature Level 12. It crashes directly on Windows 7.
const auto feature_levels = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
};
IDXGIAdapter* adapter = nullptr;
D3D_DRIVER_TYPE driver_type = D3D_DRIVER_TYPE_UNKNOWN;
// NOTE: Automatically selecting adapter on Windows 10 RTM or greater.
if (Utils::IsWindows10RTMOrGreater()) {
adapter = NULL;
driver_type = D3D_DRIVER_TYPE_HARDWARE;
} else {
IDXGIFactory* dxgi = nullptr;
::CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgi);
// As far as my experience goes, this is the safest approach. Passing NULL
// (so-called default) seems to cause issues on Windows 7 or maybe some
// older graphics drivers. First adapter is the default.
// D3D_DRIVER_TYPE_UNKNOWN| must be passed with manual adapter selection.
dxgi->EnumAdapters(0, &adapter);
dxgi->Release();
}
auto hr = ::D3D11CreateDevice(
adapter, driver_type, 0, 0, feature_levels.begin(),
static_cast<UINT>(feature_levels.size()), D3D11_SDK_VERSION,
&d3d_11_device_, 0, &d3d_11_device_context_);
CHECK_HRESULT("D3D11CreateDevice");
}
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device = nullptr;
auto dxgi_device_success = d3d_11_device_->QueryInterface(
__uuidof(IDXGIDevice), (void**)&dxgi_device);
if (SUCCEEDED(dxgi_device_success) && dxgi_device != nullptr) {
dxgi_device->SetGPUThreadPriority(5); // Must be in interval [-7, 7].
}
auto level = d3d_11_device_->GetFeatureLevel();
std::cout << "media_kit: ANGLESurfaceManager: Direct3D Feature Level: "
<< (((unsigned)level) >> 12) << "_"
<< ((((unsigned)level) >> 8) & 0xf) << std::endl;
auto d3d11_texture2D_desc = D3D11_TEXTURE2D_DESC{0};
d3d11_texture2D_desc.Width = width_;
d3d11_texture2D_desc.Height = height_;
d3d11_texture2D_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
d3d11_texture2D_desc.MipLevels = 1;
d3d11_texture2D_desc.ArraySize = 1;
d3d11_texture2D_desc.SampleDesc.Count = 1;
d3d11_texture2D_desc.SampleDesc.Quality = 0;
d3d11_texture2D_desc.Usage = D3D11_USAGE_DEFAULT;
d3d11_texture2D_desc.BindFlags =
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
d3d11_texture2D_desc.CPUAccessFlags = 0;
d3d11_texture2D_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
// The general idea is to create two textures, one that is used to |Draw|
// using ANGLE & another one that is used for |Read| the rendered content
// using |handle|.
// The internal texture is copied to the public texture once a frame is
// requested using |ID3D11DeviceContext::CopyResource|. This prevents any kind
// of synchronization issues.
// Internal.
auto hr = d3d_11_device_->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
&internal_d3d_11_texture_2D_);
CHECK_HRESULT("ID3D11Device::CreateTexture2D");
auto resource = Microsoft::WRL::ComPtr<IDXGIResource>{};
hr = internal_d3d_11_texture_2D_.As(&resource);
CHECK_HRESULT("ID3D11Texture2D::As");
// Retrieve the shared |HANDLE| for interop.
hr = resource->GetSharedHandle(&internal_handle_);
CHECK_HRESULT("IDXGIResource::GetSharedHandle");
internal_d3d_11_texture_2D_->AddRef();
// External.
hr = d3d_11_device_->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
&d3d_11_texture_2D_);
CHECK_HRESULT("ID3D11Device::CreateTexture2D");
hr = d3d_11_texture_2D_.As(&resource);
CHECK_HRESULT("ID3D11Texture2D::As");
// Retrieve the shared |HANDLE| for interop.
hr = resource->GetSharedHandle(&handle_);
CHECK_HRESULT("IDXGIResource::GetSharedHandle");
d3d_11_texture_2D_->AddRef();
return true;
}
bool ANGLESurfaceManager::CreateEGLDisplay() {
if (display_ == EGL_NO_DISPLAY) {
auto eglGetPlatformDisplayEXT =
reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
if (eglGetPlatformDisplayEXT) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D11DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D11_9_3DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D9DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kWrapDisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
FAIL("eglGetPlatformDisplayEXT");
}
}
}
}
} else {
FAIL("eglGetProcAddress");
}
}
return true;
}
bool ANGLESurfaceManager::CreateAndBindEGLSurface() {
// Do not create |context_| again, likely due to |Resize|.
if (context_ == EGL_NO_CONTEXT) {
// First time from the constructor itself.
auto count = 0;
auto result = eglChooseConfig(display_, kEGLConfigurationAttributes,
&config_, 1, &count);
if (result == EGL_FALSE || count == 0) {
FAIL("eglChooseConfig");
}
context_ = eglCreateContext(display_, config_, EGL_NO_CONTEXT,
kEGLContextAttributes);
if (context_ == EGL_NO_CONTEXT) {
FAIL("eglCreateContext");
}
}
EGLint buffer_attributes[] = {
EGL_WIDTH, width_, EGL_HEIGHT, height_,
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_NONE,
};
surface_ = eglCreatePbufferFromClientBuffer(
display_, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, internal_handle_,
config_, buffer_attributes);
if (surface_ == EGL_NO_SURFACE) {
FAIL("eglCreatePbufferFromClientBuffer");
}
GLuint t;
glGenTextures(1, &t);
glBindTexture(GL_TEXTURE_2D, t);
eglBindTexImage(display_, surface_, EGL_BACK_BUFFER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return true;
}
@@ -0,0 +1,134 @@
// 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 ANGLE_SURFACE_MANAGER_H_
#define ANGLE_SURFACE_MANAGER_H_
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <Windows.h>
#include <d3d.h>
#include <d3d11.h>
#include <wrl.h>
#include "utils.h"
#include <cstdint>
#include <functional>
// |ANGLESurfaceManager| provides an abstraction around ANGLE to easily draw
// OpenGL ES 2.0 content & read as D3D 11 texture using shared |HANDLE|.
// * |Draw|: Takes callback where OpenGL ES 2.0 calls can be made for rendering.
// * |Read|: Copies the drawn content to D3D 11 texture & makes it available to
// the shared |handle| for access.
// A large part of implementation is inspired from Flutter.
// https://github.com/flutter/engine/blob/master/shell/platform/windows/angle_surface_manager.h
class ANGLESurfaceManager {
public:
const int32_t width() const { return width_; }
const int32_t height() const { return height_; }
const HANDLE handle() const { return handle_; }
ANGLESurfaceManager(int32_t width, int32_t height);
~ANGLESurfaceManager();
void SetSize(int32_t width, int32_t height);
void Draw(std::function<void()> callback);
void Read();
void MakeCurrent(bool value);
private:
void SwapBuffers();
void Create();
void CleanUp(bool release_context);
bool CreateD3DTexture();
bool CreateEGLDisplay();
bool CreateAndBindEGLSurface();
int32_t width_ = 1;
int32_t height_ = 1;
HANDLE internal_handle_ = nullptr;
HANDLE handle_ = nullptr;
// Sync |Draw| & |Read| calls.
HANDLE mutex_ = nullptr;
// D3D 11
ID3D11Device* d3d_11_device_ = nullptr;
ID3D11DeviceContext* d3d_11_device_context_ = nullptr;
Microsoft::WRL::ComPtr<ID3D11Texture2D> internal_d3d_11_texture_2D_;
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d_11_texture_2D_;
// ANGLE
EGLSurface surface_ = EGL_NO_SURFACE;
EGLDisplay display_ = EGL_NO_DISPLAY;
EGLContext context_ = nullptr;
EGLConfig config_ = nullptr;
static constexpr EGLint kEGLConfigurationAttributes[] = {
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,
};
static constexpr EGLint kEGLContextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE,
};
static constexpr EGLint kD3D11DisplayAttributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,
EGL_TRUE,
EGL_NONE,
};
static constexpr EGLint kD3D11_9_3DisplayAttributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE,
9,
EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE,
3,
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,
EGL_TRUE,
EGL_NONE,
};
static constexpr EGLint kD3D9DisplayAttributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
EGL_NONE,
};
static constexpr EGLint kWrapDisplayAttributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,
EGL_TRUE,
EGL_NONE,
};
// Number of active instances of ANGLESurfaceManager.
static int32_t instance_count_;
};
#endif // ANGLE_SURFACE_MANAGER_H_
@@ -0,0 +1,30 @@
// 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 FLUTTER_PLUGIN_MEDIA_KIT_VIDEO_PLUGIN_C_API_H_
#define FLUTTER_PLUGIN_MEDIA_KIT_VIDEO_PLUGIN_C_API_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void MediaKitVideoPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_MEDIA_KIT_VIDEO_PLUGIN_C_API_H_
@@ -0,0 +1,217 @@
// 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 "media_kit_video_plugin.h"
#include "utils.h"
#include <Windows.h>
namespace media_kit_video {
MediaKitVideoPlugin* MediaKitVideoPlugin::instance_ = nullptr;
void MediaKitVideoPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows* registrar) {
auto plugin = std::make_unique<MediaKitVideoPlugin>(registrar);
registrar->AddPlugin(std::move(plugin));
}
MediaKitVideoPlugin::MediaKitVideoPlugin(
flutter::PluginRegistrarWindows* registrar)
: registrar_(registrar),
video_output_manager_(std::make_unique<VideoOutputManager>(registrar)) {
instance_ = this;
flutter_window_ =
::GetAncestor(registrar->GetView()->GetNativeWindow(), GA_ROOT);
original_window_proc_ = reinterpret_cast<WNDPROC>(
::SetWindowLongPtr(flutter_window_, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(WindowProcDelegate)));
channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "com.alexmercerind/media_kit_video",
&flutter::StandardMethodCodec::GetInstance());
channel_->SetMethodCallHandler([&](const auto& call, auto result) {
HandleMethodCall(call, std::move(result));
});
}
MediaKitVideoPlugin::~MediaKitVideoPlugin() {
if (flutter_window_ && original_window_proc_) {
::SetWindowLongPtr(flutter_window_, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(original_window_proc_));
}
if (instance_ == this) {
instance_ = nullptr;
}
}
void MediaKitVideoPlugin::RunOnMainThread(std::function<void()> task) {
if (!flutter_window_) {
return;
}
{
std::lock_guard<std::mutex> lock(main_thread_tasks_mutex_);
main_thread_tasks_.push(std::move(task));
}
::PostMessage(flutter_window_, kMainThreadTaskMessage, 0, 0);
}
LRESULT CALLBACK MediaKitVideoPlugin::WindowProcDelegate(HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam) {
if (message == kMainThreadTaskMessage && instance_) {
instance_->ProcessMainThreadTasks();
return 0;
}
if (instance_ && instance_->original_window_proc_) {
return ::CallWindowProc(instance_->original_window_proc_, hwnd, message,
wParam, lParam);
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
void MediaKitVideoPlugin::ProcessMainThreadTasks() {
std::queue<std::function<void()>> tasks_to_execute;
{
std::lock_guard<std::mutex> lock(main_thread_tasks_mutex_);
tasks_to_execute.swap(main_thread_tasks_);
}
while (!tasks_to_execute.empty()) {
auto task = std::move(tasks_to_execute.front());
tasks_to_execute.pop();
try {
task();
} catch (...) {
}
}
}
void MediaKitVideoPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (method_call.method_name().compare("VideoOutputManager.Create") == 0) {
auto arguments = std::get<flutter::EncodableMap>(*method_call.arguments());
auto handle =
std::get<std::string>(arguments[flutter::EncodableValue("handle")]);
auto configuration = std::get<flutter::EncodableMap>(
arguments[flutter::EncodableValue("configuration")]);
auto handle_value = std::stoll(handle);
auto configuration_value = VideoOutputConfiguration{};
auto configuration_width =
std::get<std::string>(configuration[flutter::EncodableValue("width")]);
auto configuration_height =
std::get<std::string>(configuration[flutter::EncodableValue("height")]);
auto configuration_enable_hardware_acceleration = std::get<bool>(
configuration[flutter::EncodableValue("enableHardwareAcceleration")]);
if (configuration_width.compare("null") != 0) {
configuration_value.width =
static_cast<int64_t>(std::stoll(configuration_width.c_str()));
}
if (configuration_height.compare("null") != 0) {
configuration_value.height =
static_cast<int64_t>(std::stoll(configuration_height.c_str()));
}
configuration_value.enable_hardware_acceleration =
configuration_enable_hardware_acceleration;
video_output_manager_->Create(
handle_value, configuration_value,
[this, handle = handle_value](auto id, auto width, auto height) {
RunOnMainThread([=]() {
channel_->InvokeMethod(
"VideoOutput.Resize",
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
{
flutter::EncodableValue("handle"),
flutter::EncodableValue(handle),
},
{
flutter::EncodableValue("id"),
flutter::EncodableValue(id),
},
{
flutter::EncodableValue("rect"),
flutter::EncodableValue(flutter::EncodableMap{
{
flutter::EncodableValue("left"),
flutter::EncodableValue(0),
},
{
flutter::EncodableValue("top"),
flutter::EncodableValue(0),
},
{
flutter::EncodableValue("width"),
flutter::EncodableValue(width),
},
{
flutter::EncodableValue("height"),
flutter::EncodableValue(height),
},
}),
},
}),
nullptr);
});
});
result->Success(flutter::EncodableValue(std::monostate{}));
} else if (method_call.method_name().compare("VideoOutputManager.Dispose") ==
0) {
auto arguments = std::get<flutter::EncodableMap>(*method_call.arguments());
auto handle =
std::get<std::string>(arguments[flutter::EncodableValue("handle")]);
auto handle_value = static_cast<int64_t>(std::stoll(handle.c_str()));
video_output_manager_->Dispose(handle_value);
result->Success(flutter::EncodableValue(std::monostate{}));
} else if (method_call.method_name().compare("VideoOutputManager.SetSize") ==
0) {
auto arguments = std::get<flutter::EncodableMap>(*method_call.arguments());
auto handle =
std::get<std::string>(arguments[flutter::EncodableValue("handle")]);
auto width =
std::get<std::string>(arguments[flutter::EncodableValue("width")]);
auto height =
std::get<std::string>(arguments[flutter::EncodableValue("height")]);
auto handle_value = static_cast<int64_t>(std::stoll(handle.c_str()));
auto width_value = std::optional<int64_t>{};
auto height_value = std::optional<int64_t>{};
if (width.compare("null") != 0) {
width_value = static_cast<int64_t>(std::stoll(width.c_str()));
}
if (height.compare("null") != 0) {
height_value = static_cast<int64_t>(std::stoll(height.c_str()));
}
video_output_manager_->SetSize(handle_value, width_value, height_value);
result->Success(flutter::EncodableValue(std::monostate{}));
} else if (method_call.method_name().compare("Utils.EnterNativeFullscreen") ==
0) {
auto window =
::GetAncestor(registrar_->GetView()->GetNativeWindow(), GA_ROOT);
Utils::EnterNativeFullscreen(window);
result->Success(flutter::EncodableValue(std::monostate{}));
} else if (method_call.method_name().compare("Utils.ExitNativeFullscreen") ==
0) {
auto window =
::GetAncestor(registrar_->GetView()->GetNativeWindow(), GA_ROOT);
Utils::ExitNativeFullscreen(window);
result->Success(flutter::EncodableValue(std::monostate{}));
} else {
result->NotImplemented();
}
}
} // namespace media_kit_video
@@ -0,0 +1,57 @@
// 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/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <Windows.h>
#include <functional>
#include <queue>
#include <mutex>
#include "video_output_manager.h"
namespace media_kit_video {
class MediaKitVideoPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);
MediaKitVideoPlugin(flutter::PluginRegistrarWindows* registrar);
virtual ~MediaKitVideoPlugin();
MediaKitVideoPlugin(const MediaKitVideoPlugin&) = delete;
MediaKitVideoPlugin& operator=(const MediaKitVideoPlugin&) = delete;
private:
static constexpr UINT kMainThreadTaskMessage = WM_USER + 1001;
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void RunOnMainThread(std::function<void()> task);
static LRESULT CALLBACK WindowProcDelegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void ProcessMainThreadTasks();
flutter::PluginRegistrarWindows* registrar_ = nullptr;
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel_ =
nullptr;
std::unique_ptr<VideoOutputManager> video_output_manager_ = nullptr;
HWND flutter_window_ = nullptr;
WNDPROC original_window_proc_ = nullptr;
std::queue<std::function<void()>> main_thread_tasks_;
std::mutex main_thread_tasks_mutex_;
static MediaKitVideoPlugin* instance_;
};
} // namespace media_kit_video
#endif // MEDIA_KIT_VIDEO_PLUGIN_H_
@@ -0,0 +1,33 @@
// 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_c_api.h"
#include <flutter/plugin_registrar_windows.h>
#ifndef MEDIA_KIT_LIBS_NOT_FOUND
#include "media_kit_video_plugin.h"
void MediaKitVideoPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
media_kit_video::MediaKitVideoPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}
#else
#include <iostream>
void MediaKitVideoPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
std::cout << "media_kit: WARNING: package:media_kit_libs_*** not found."
<< std::endl;
}
#endif
+91
View File
@@ -0,0 +1,91 @@
// 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 THREAD_POOL_H_
#define THREAD_POOL_H_
#include <functional>
#include <future>
#include <queue>
#ifdef _WIN32
#include <Windows.h>
#endif
class ThreadPool {
public:
explicit ThreadPool(size_t);
template <class F, class... Args>
decltype(auto) Post(F&& f, Args&&... args);
~ThreadPool();
private:
std::vector<std::thread> workers_;
std::queue<std::packaged_task<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable condition_;
std::condition_variable condition_producers_;
bool stop_;
};
inline ThreadPool::ThreadPool(size_t threads) : stop_(false) {
for (size_t i = 0; i < threads; i++) {
workers_.emplace_back([&] {
for (;;) {
std::packaged_task<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
condition_.wait(lock, [&] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty())
return;
task = std::move(tasks_.front());
tasks_.pop();
if (tasks_.empty()) {
condition_producers_.notify_one();
}
}
task();
}
});
#ifdef _WIN32
::SetThreadPriority(workers_.back().native_handle(),
THREAD_PRIORITY_HIGHEST);
#endif
}
}
template <class F, class... Args>
decltype(auto) ThreadPool::Post(F&& f, Args&&... args) {
using return_type = std::invoke_result_t<F, Args...>;
std::packaged_task<return_type()> task(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task.get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (stop_) {
throw std::runtime_error("ThreadPool::Post");
}
tasks_.emplace(std::move(task));
}
condition_.notify_one();
return res;
}
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
condition_producers_.wait(lock, [this] { return tasks_.empty(); });
stop_ = true;
}
condition_.notify_all();
for (std::thread& worker : workers_) {
worker.join();
}
}
#endif // THREAD_POOL_H_
+107
View File
@@ -0,0 +1,107 @@
// 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 "utils.h"
typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
void Utils::EnterNativeFullscreen(HWND window) {
if (fullscreen_) {
return;
}
fullscreen_ = true;
// The primary idea here is to revolve around |WS_OVERLAPPEDWINDOW| &
// detect/set fullscreen based on it. In the window procedure, this is
// separately handled. If there is no |WS_OVERLAPPEDWINDOW| style on the
// window i.e. in fullscreen, then no area is left for |WM_NCHITTEST|,
// accordingly client area is also expanded to fill whole monitor using
// |WM_NCCALCSIZE|.
auto style = ::GetWindowLongPtr(window, GWL_STYLE);
if (style & WS_OVERLAPPEDWINDOW) {
auto monitor = MONITORINFO{};
auto placement = WINDOWPLACEMENT{};
monitor.cbSize = sizeof(MONITORINFO);
placement.length = sizeof(WINDOWPLACEMENT);
::GetWindowPlacement(window, &placement);
rect_before_fullscreen_ = RECT{
placement.rcNormalPosition.left,
placement.rcNormalPosition.top,
placement.rcNormalPosition.right,
placement.rcNormalPosition.bottom,
};
::GetMonitorInfo(::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST),
&monitor);
::SetWindowLongPtr(window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
::SetWindowPos(window, HWND_TOP, monitor.rcMonitor.left,
monitor.rcMonitor.top,
monitor.rcMonitor.right - monitor.rcMonitor.left,
monitor.rcMonitor.bottom - monitor.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
void Utils::ExitNativeFullscreen(HWND window) {
if (!fullscreen_) {
return;
}
fullscreen_ = false;
auto style = ::GetWindowLongPtr(window, GWL_STYLE);
if (!(style & WS_OVERLAPPEDWINDOW)) {
::SetWindowLongPtr(window, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
if (::IsZoomed(window)) {
// Refresh the parent window.
::SetWindowPos(window, nullptr, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_FRAMECHANGED);
auto rect = RECT{};
::GetClientRect(window, &rect);
auto flutter_view =
::FindWindowEx(window, nullptr, kFlutterViewWindowClassName, nullptr);
::SetWindowPos(flutter_view, nullptr, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOZORDER);
} else {
::SetWindowPos(
window, nullptr, rect_before_fullscreen_.left,
rect_before_fullscreen_.top,
rect_before_fullscreen_.right - rect_before_fullscreen_.left,
rect_before_fullscreen_.bottom - rect_before_fullscreen_.top,
SWP_NOACTIVATE | SWP_NOZORDER);
}
}
}
RTL_OSVERSIONINFOW Utils::GetWindowsVersion() {
HMODULE handle = ::LoadLibraryW(L"ntdll.dll");
RTL_OSVERSIONINFOW rtl_os_version_info = {0};
rtl_os_version_info.dwBuildNumber = 0;
rtl_os_version_info.dwOSVersionInfoSize = sizeof(rtl_os_version_info);
if (handle) {
RtlGetVersionPtr rtl_get_version_ptr = reinterpret_cast<RtlGetVersionPtr>(
::GetProcAddress(handle, "RtlGetVersion"));
if (rtl_get_version_ptr != nullptr) {
rtl_get_version_ptr(&rtl_os_version_info);
}
::FreeLibrary(handle);
}
return rtl_os_version_info;
}
bool Utils::IsWindows10RTMOrGreater() {
return GetWindowsVersion().dwBuildNumber >= 10240;
}
bool Utils::fullscreen_ = false;
RECT Utils::rect_before_fullscreen_ = RECT{};
+33
View File
@@ -0,0 +1,33 @@
// 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 UTILS_H_
#define UTILS_H_
#include <cstdint>
#include <Windows.h>
class Utils {
public:
static void EnterNativeFullscreen(HWND window);
static void ExitNativeFullscreen(HWND window);
static RTL_OSVERSIONINFOW GetWindowsVersion();
static bool IsWindows10RTMOrGreater();
private:
static constexpr auto kFlutterViewWindowClassName = L"FLUTTERVIEW";
static bool fullscreen_;
static RECT rect_before_fullscreen_;
};
#endif // UTILS_H_
+462
View File
@@ -0,0 +1,462 @@
// 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 "video_output.h"
#include <algorithm>
// 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)
VideoOutput::VideoOutput(int64_t handle,
VideoOutputConfiguration configuration,
flutter::PluginRegistrarWindows* registrar,
ThreadPool* thread_pool_ref)
: handle_(reinterpret_cast<mpv_handle*>(handle)),
width_(configuration.width),
height_(configuration.height),
configuration_(configuration),
registrar_(registrar),
thread_pool_ref_(thread_pool_ref) {
// The constructor must be invoked through the thread pool, because
// |ANGLESurfaceManager| & libmpv render context creation can conflict with
// the existing |Render| or |Resize| calls from another |VideoOutput|
// instances (which will result in access violation).
auto future = thread_pool_ref_->Post([&]() {
mpv_set_option_string(handle_, "video-sync", "audio");
mpv_set_option_string(handle_, "video-timing-offset", "0");
// First try to initialize video playback with hardware acceleration &
// |ANGLESurfaceManager|, use S/W API as fallback.
auto is_hardware_acceleration_enabled = false;
// Attempt to use H/W rendering.
if (configuration.enable_hardware_acceleration) {
try {
// OpenGL context needs to be set before |mpv_render_context_create|.
surface_manager_ = std::make_unique<ANGLESurfaceManager>(
static_cast<int32_t>(width_.value_or(1)),
static_cast<int32_t>(height_.value_or(1)));
surface_manager_->MakeCurrent(true);
Resize(width_.value_or(1), height_.value_or(1));
mpv_opengl_init_params gl_init_params{
[](auto, auto name) {
return reinterpret_cast<void*>(eglGetProcAddress(name));
},
nullptr,
};
mpv_render_param params[] = {
{MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
// Create render context.
if (mpv_render_context_create(&render_context_, handle_, params) == 0) {
mpv_render_context_set_update_callback(
render_context_,
[](void* context) {
// Notify Flutter that a new frame is available. The actual
// rendering will take place in the |Render| method, which will
// be called by Flutter on the render thread.
auto that = reinterpret_cast<VideoOutput*>(context);
that->NotifyRender();
},
reinterpret_cast<void*>(this));
// Set flag to true, indicating that H/W rendering is supported.
is_hardware_acceleration_enabled = true;
std::cout << "media_kit: VideoOutput: Using H/W rendering."
<< std::endl;
}
} catch (...) {
// Do nothing.
// Likely received an |std::runtime_error| from |ANGLESurfaceManager|,
// which indicates that H/W rendering is not supported.
}
}
if (!is_hardware_acceleration_enabled) {
std::cout << "media_kit: VideoOutput: Using S/W rendering." << std::endl;
// Allocate a "large enough" buffer ahead of time.
pixel_buffer_ =
std::make_unique<uint8_t[]>(SW_RENDERING_PIXEL_BUFFER_SIZE);
Resize(width_.value_or(1), height_.value_or(1));
mpv_render_param params[] = {
{MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_SW},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
if (mpv_render_context_create(&render_context_, handle_, params) == 0) {
mpv_render_context_set_update_callback(
render_context_,
[](void* context) {
// Notify Flutter that a new frame is available. The actual
// rendering will take place in the |Render| method, which will be
// called by Flutter on the render thread.
auto that = reinterpret_cast<VideoOutput*>(context);
that->NotifyRender();
},
reinterpret_cast<void*>(this));
}
}
});
future.wait();
}
VideoOutput::~VideoOutput() {
destroyed_ = true;
auto promise = std::promise<void>();
if (texture_id_) {
registrar_->texture_registrar()->UnregisterTexture(
texture_id_, [&, texture_id = texture_id_]() {
// Add one more task into the thread pool queue & exit the destructor
// only when it gets executed. This will ensure that all the tasks
// posted to the thread pool i.e. render or resize before this are
// executed (and won't reference the dead object anymore), most
// notably |CheckAndResize| & |Render|.
auto future = thread_pool_ref_->Post([&, id = texture_id]() {
std::cout << "media_kit: VideoOutput: Free Texture: " << id
<< std::endl;
std::cout << "VideoOutput::~VideoOutput: "
<< reinterpret_cast<int64_t>(handle_) << std::endl;
std::lock_guard<std::mutex> lock(textures_mutex_);
texture_variants_.clear();
// H/W
textures_.clear();
// S/W
pixel_buffer_textures_.clear();
// Free (call destructor) |ANGLESurfaceManager| through the thread
// pool. This will ensure synchronized EGL or ANGLE usage & won't
// conflict with |Render| or |CheckAndResize| of other
// |VideoOutput|s.
surface_manager_.reset(nullptr);
promise.set_value();
});
});
}
promise.get_future().wait();
texture_id_ = 0;
thread_pool_ref_->Post([render_context = render_context_]() {
mpv_render_context_free(render_context);
});
}
void VideoOutput::NotifyRender() {
if (destroyed_) {
return;
}
thread_pool_ref_->Post(std::bind(&VideoOutput::CheckAndResize, this));
thread_pool_ref_->Post(std::bind(&VideoOutput::Render, this));
}
void VideoOutput::Render() {
if (texture_id_) {
// H/W
if (surface_manager_ != nullptr) {
surface_manager_->Draw([&]() {
mpv_opengl_fbo fbo{
0,
surface_manager_->width(),
surface_manager_->height(),
0,
};
mpv_render_param params[]{
{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
mpv_render_context_render(render_context_, params);
});
}
// S/W
if (pixel_buffer_ != nullptr) {
int32_t size[]{
static_cast<int32_t>(pixel_buffer_textures_.at(texture_id_)->width),
static_cast<int32_t>(pixel_buffer_textures_.at(texture_id_)->height),
};
auto pitch = 4 * size[0];
mpv_render_param params[]{
{MPV_RENDER_PARAM_SW_SIZE, size},
{MPV_RENDER_PARAM_SW_FORMAT, "rgb0"},
{MPV_RENDER_PARAM_SW_STRIDE, &pitch},
{MPV_RENDER_PARAM_SW_POINTER, pixel_buffer_.get()},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
mpv_render_context_render(render_context_, params);
}
try {
// Notify Flutter that a new frame is available.
registrar_->texture_registrar()->MarkTextureFrameAvailable(texture_id_);
} catch (...) {
// Prevent any redundant exceptions if the texture is unregistered etc.
}
}
}
void VideoOutput::SetTextureUpdateCallback(
std::function<void(int64_t, int64_t, int64_t)> callback) {
texture_update_callback_ = callback;
texture_update_callback_(texture_id_, GetVideoWidth(), GetVideoHeight());
}
void VideoOutput::SetSize(std::optional<int64_t> width,
std::optional<int64_t> height) {
thread_pool_ref_->Post([&, width, height]() {
if (width.has_value()) {
// H/W
if (surface_manager_ != nullptr) {
width_ = width.value();
}
// S/W
if (pixel_buffer_ != nullptr) {
// Limit width if software rendering is being used.
width_ = std::clamp(width.value(), static_cast<int64_t>(0),
static_cast<int64_t>(SW_RENDERING_MAX_WIDTH));
}
} else {
width_ = std::nullopt;
}
if (height.has_value()) {
// H/W
if (surface_manager_ != nullptr) {
height_ = height.value();
}
// S/W
if (pixel_buffer_ != nullptr) {
// Limit width if software rendering is being used.
height_ = std::clamp(height.value(), static_cast<int64_t>(0),
static_cast<int64_t>(SW_RENDERING_MAX_HEIGHT));
}
} else {
height_ = std::nullopt;
}
});
}
void VideoOutput::CheckAndResize() {
// Check if a new texture with different dimensions is needed.
auto required_width = GetVideoWidth(), required_height = GetVideoHeight();
if (required_width < 1 || required_height < 1) {
// Invalid.
return;
}
int64_t current_width = -1, current_height = -1;
if (surface_manager_ != nullptr) {
current_width = surface_manager_->width();
current_height = surface_manager_->height();
}
if (pixel_buffer_ != nullptr) {
current_width = pixel_buffer_textures_.at(texture_id_)->width;
current_height = pixel_buffer_textures_.at(texture_id_)->height;
}
// Currently rendered video output dimensions.
// Either H/W or S/W rendered.
assert(current_width > 0);
assert(current_height > 0);
if (required_width == current_width && required_height == current_height) {
// No creation of new texture required.
return;
}
Resize(required_width, required_height);
}
void VideoOutput::Resize(int64_t required_width, int64_t required_height) {
std::cout << required_width << " " << required_height << std::endl;
// Unregister previously registered texture & delete underlying objects.
if (texture_id_) {
registrar_->texture_registrar()->UnregisterTexture(
texture_id_, [&, id = texture_id_]() {
if (id) {
std::cout << "media_kit: VideoOutput: Free Texture: " << id
<< std::endl;
std::lock_guard<std::mutex> lock(textures_mutex_);
if (destroyed_) {
return;
}
if (texture_variants_.find(id) != texture_variants_.end()) {
texture_variants_.erase(id);
}
// H/W
if (textures_.find(id) != textures_.end()) {
textures_.erase(id);
}
// S/W
if (pixel_buffer_textures_.find(id) !=
pixel_buffer_textures_.end()) {
pixel_buffer_textures_.erase(id);
}
}
});
texture_id_ = 0;
}
// H/W
if (surface_manager_ != nullptr) {
// Destroy internal ID3D11Texture2D & EGLSurface & create new with updated
// dimensions while preserving previous EGLDisplay & EGLContext.
surface_manager_->SetSize(static_cast<int32_t>(required_width),
static_cast<int32_t>(required_height));
auto texture = std::make_unique<FlutterDesktopGpuSurfaceDescriptor>();
texture->struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor);
texture->handle = surface_manager_->handle();
texture->width = texture->visible_width = surface_manager_->width();
texture->height = texture->visible_height = surface_manager_->height();
texture->release_context = nullptr;
texture->release_callback = [](void*) {};
texture->format = kFlutterDesktopPixelFormatBGRA8888;
auto texture_variant =
std::make_unique<flutter::TextureVariant>(flutter::GpuSurfaceTexture(
kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, [&](auto, auto) {
std::lock_guard<std::mutex> lock(textures_mutex_);
if (texture_id_) {
surface_manager_->Read();
return textures_.at(texture_id_).get();
} else {
return (FlutterDesktopGpuSurfaceDescriptor*)nullptr;
}
}));
// Register new texture.
texture_id_ =
registrar_->texture_registrar()->RegisterTexture(texture_variant.get());
std::cout << "media_kit: VideoOutput: Create Texture: " << texture_id_
<< std::endl;
std::lock_guard<std::mutex> lock(textures_mutex_);
textures_.emplace(std::make_pair(texture_id_, std::move(texture)));
texture_variants_.emplace(
std::make_pair(texture_id_, std::move(texture_variant)));
// Notify public texture update callback.
texture_update_callback_(texture_id_, required_width, required_height);
}
// S/W
if (pixel_buffer_ != nullptr) {
auto pixel_buffer_texture = std::make_unique<FlutterDesktopPixelBuffer>();
pixel_buffer_texture->buffer = pixel_buffer_.get();
pixel_buffer_texture->width = required_width;
pixel_buffer_texture->height = required_height;
pixel_buffer_texture->release_context = nullptr;
pixel_buffer_texture->release_callback = [](void*) {};
auto texture_variant = std::make_unique<flutter::TextureVariant>(
flutter::PixelBufferTexture([&](auto, auto) {
std::lock_guard<std::mutex> lock(textures_mutex_);
if (texture_id_) {
return pixel_buffer_textures_.at(texture_id_).get();
} else {
return (FlutterDesktopPixelBuffer*)nullptr;
}
}));
// Register new texture.
texture_id_ =
registrar_->texture_registrar()->RegisterTexture(texture_variant.get());
std::cout << "media_kit: VideoOutput: Create Texture: " << texture_id_
<< std::endl;
std::lock_guard<std::mutex> lock(textures_mutex_);
pixel_buffer_textures_.emplace(
std::make_pair(texture_id_, std::move(pixel_buffer_texture)));
texture_variants_.emplace(
std::make_pair(texture_id_, std::move(texture_variant)));
// Notify public texture update callback.
texture_update_callback_(texture_id_, required_width, required_height);
}
}
int64_t VideoOutput::GetVideoWidth() {
// Fixed width.
if (width_) {
return width_.value();
}
// Video resolution dependent width.
int64_t width = 0;
int64_t height = 0;
mpv_node params;
mpv_get_property(handle_, "video-out-params", MPV_FORMAT_NODE, &params);
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(&params);
}
width = rotate == 0 || rotate == 180 ? dw : dh;
height = rotate == 0 || rotate == 180 ? dh : dw;
if (pixel_buffer_ != nullptr) {
// 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;
}
int64_t VideoOutput::GetVideoHeight() {
// Fixed height.
if (height_) {
return height_.value();
}
// Video resolution dependent height.
int64_t width = 0;
int64_t height = 0;
mpv_node params;
mpv_get_property(handle_, "video-out-params", MPV_FORMAT_NODE, &params);
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(&params);
}
width = rotate == 0 || rotate == 180 ? dw : dh;
height = rotate == 0 || rotate == 180 ? dh : dw;
if (pixel_buffer_ != 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;
}
+130
View File
@@ -0,0 +1,130 @@
// 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 <optional>
#include <client.h>
#include <render.h>
#include <render_gl.h>
#include <future>
#include <memory>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include "angle_surface_manager.h"
#include "thread_pool.h"
typedef struct _VideoOutputConfiguration {
std::optional<int64_t> width;
std::optional<int64_t> height;
bool enable_hardware_acceleration;
_VideoOutputConfiguration(std::optional<int64_t> width = std::nullopt,
std::optional<int64_t> height = std::nullopt,
bool enable_hardware_acceleration = true)
: width(width),
height(height),
enable_hardware_acceleration(enable_hardware_acceleration) {}
} VideoOutputConfiguration;
class VideoOutput {
public:
int64_t texture_id() const { return texture_id_; }
int64_t width() const {
// H/W
if (surface_manager_ != nullptr && texture_id_) {
return surface_manager_->width();
}
// S/W
if (pixel_buffer_ != nullptr && texture_id_) {
return pixel_buffer_textures_.at(texture_id_)->width;
}
return width_.value_or(1);
}
int64_t height() const {
// H/W
if (surface_manager_ != nullptr && texture_id_) {
return surface_manager_->height();
}
// S/W
if (pixel_buffer_ != nullptr && texture_id_) {
return pixel_buffer_textures_.at(texture_id_)->height;
}
return height_.value_or(1);
}
VideoOutput(int64_t handle,
VideoOutputConfiguration configuration,
flutter::PluginRegistrarWindows* registrar,
ThreadPool* thread_pool_ref);
~VideoOutput();
void SetTextureUpdateCallback(
std::function<void(int64_t, int64_t, int64_t)> callback);
void SetSize(std::optional<int64_t> width, std::optional<int64_t> height);
private:
void NotifyRender();
void Render();
void CheckAndResize();
void Resize(int64_t required_width, int64_t required_height);
int64_t GetVideoWidth();
int64_t GetVideoHeight();
std::optional<int64_t> height_ = std::nullopt;
std::optional<int64_t> width_ = std::nullopt;
VideoOutputConfiguration configuration_ = VideoOutputConfiguration{};
mpv_handle* handle_ = nullptr;
mpv_render_context* render_context_ = nullptr;
int64_t texture_id_ = 0;
flutter::PluginRegistrarWindows* registrar_ = nullptr;
ThreadPool* thread_pool_ref_ = nullptr;
// For preventing any asynchronous operations (primarily texture objects
// deletion after unregister in |Resize|) access this object after
// destruction.
bool destroyed_ = false;
std::mutex textures_mutex_ = std::mutex();
std::unordered_map<int64_t, std::unique_ptr<flutter::TextureVariant>>
texture_variants_ = {};
// H/W rendering.
std::unique_ptr<ANGLESurfaceManager> surface_manager_ = nullptr;
std::unordered_map<int64_t,
std::unique_ptr<FlutterDesktopGpuSurfaceDescriptor>>
textures_ = {};
// S/W rendering.
std::unique_ptr<uint8_t[]> pixel_buffer_ = nullptr;
std::unordered_map<int64_t, std::unique_ptr<FlutterDesktopPixelBuffer>>
pixel_buffer_textures_ = {};
// Public notifier. This is called when a new texture is registered & texture
// ID is changed. Only happens when video output resolution changes.
std::function<void(int64_t, int64_t, int64_t)> texture_update_callback_ =
[](int64_t, int64_t, int64_t) {};
};
#endif // VIDEO_OUTPUT_H_
@@ -0,0 +1,56 @@
// 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 "video_output_manager.h"
VideoOutputManager::VideoOutputManager(
flutter::PluginRegistrarWindows* registrar)
: registrar_(registrar) {}
void VideoOutputManager::Create(
int64_t handle,
VideoOutputConfiguration configuration,
std::function<void(int64_t, int64_t, int64_t)> texture_update_callback) {
std::thread([=]() {
std::lock_guard<std::mutex> lock(mutex_);
if (video_outputs_.find(handle) == video_outputs_.end()) {
auto instance = std::make_unique<VideoOutput>(
handle, configuration, registrar_, thread_pool_.get());
instance->SetTextureUpdateCallback(texture_update_callback);
video_outputs_.insert(std::make_pair(handle, std::move(instance)));
}
}).detach();
}
void VideoOutputManager::SetSize(int64_t handle,
std::optional<int64_t> width,
std::optional<int64_t> height) {
std::thread([=]() {
std::lock_guard<std::mutex> lock(mutex_);
if (video_outputs_.find(handle) != video_outputs_.end()) {
video_outputs_[handle]->SetSize(width, height);
}
}).detach();
}
void VideoOutputManager::Dispose(int64_t handle) {
std::thread([=]() {
std::lock_guard<std::mutex> lock(mutex_);
if (video_outputs_.find(handle) != video_outputs_.end()) {
video_outputs_.erase(handle);
}
}).detach();
}
VideoOutputManager::~VideoOutputManager() {
std::lock_guard<std::mutex> lock(mutex_);
// |VideoOutput| destructor will do the relevant cleanup.
video_outputs_.clear();
// This destructor is only called when the plugin is being destroyed i.e. the
// application is being closed. So, doesn't really matter on the other hand.
}
@@ -0,0 +1,85 @@
// 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 <flutter/plugin_registrar_windows.h>
#include <unordered_map>
#include "thread_pool.h"
#include "video_output.h"
// Creates & disposes |VideoOutput| instances for video embedding.
//
// The methods in this class are thread-safe & run on separate worker thread so
// that they don't block Flutter's UI thread while platform channels are being
// invoked.
class VideoOutputManager {
public:
VideoOutputManager(flutter::PluginRegistrarWindows* registrar);
// Creates a new |VideoOutput| instance. It's texture ID may be used to render
// the video. The changes in it's texture ID & video dimensions will be
// notified via the |texture_update_callback|.
void Create(
int64_t handle,
VideoOutputConfiguration configuration,
std::function<void(int64_t, int64_t, int64_t)> texture_update_callback);
// Sets the required video output size.
// This forces |VideoOutput| to resize the internal OpenGL surface / D3D
// texture.
void SetSize(int64_t handle,
std::optional<int64_t> width,
std::optional<int64_t> height);
// Destroys the |VideoOutput| with given handle.
void Dispose(int64_t handle);
~VideoOutputManager();
private:
std::mutex mutex_ = std::mutex();
// All the operations involving ANGLE or EGL or libmpv must be performed on
// same single thread to prevent any race conditions or invalid ANGLE usage.
// Not doing so results in access violations & crashes.
//
// Technically, the correct place to do all the video rendering (& thus
// resize) etc. is on Flutter's render thread itself (exposed as callback in
// |flutter::GpuSurfaceTexture| & |flutter::PixelBufferTexture|). However,
// this slows down the UI too much. So, a good idea seemed to have a separate
// worker thread which queues all the rendering related jobs & performs them
// orderly. |ThreadPool| is exactly that.
//
// All of the following tasks involve ANGLE or OpenGL context etc. etc.
// Following operations are performed through the |ThreadPool|:
//
// * Rendering of video frame i.e. |mpv_render_context_render| (also involves
// |eglMakeCurrent| etc.) after being notified by
// |mpv_render_context_set_update_callback|.
// * Creation / Disposal of new |VideoOutput|.
// * For creation, |mpv_render_context_create| & instantiation of a new
// |ANGLESurfaceManager| is done through |ThreadPool| (in |VideoOutput|
// constructor).
// * For disposal, |ThreadPool| ensures that all the pending |Render| or
// |Resize| tasks are completed before freeing the |ANGLESurfaceManager|
// & |mpv_render_context| etc.
// * Resizing of |ANGLESurfaceManager| & creation of newly sized Flutter
// textures (|flutter::GpuSurfaceTexture| & |flutter::PixelBufferTexture|).
//
// Creating a |ThreadPool| with maximum number of worker threads as 1, ensures
// that all the posted tasks are performed on a single thread orderly. This
// also makes usage of any |std::mutex| unnecessary (for the good).
std::unique_ptr<ThreadPool> thread_pool_ = std::make_unique<ThreadPool>(1);
flutter::PluginRegistrarWindows* registrar_ = nullptr;
std::unordered_map<int64_t, std::unique_ptr<VideoOutput>> video_outputs_ = {};
};
#endif // VIDEO_OUTPUT_MANAGER_H_