forked from Foxyz/sync_replay_project
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
+106
@@ -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_
|
||||
+30
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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, ¶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 (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, ¶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 (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
@@ -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_
|
||||
Reference in new issue
Block a user