Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ For more information, please visit <http://www.openshot.org/>.
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")

################ PROJECT VERSION ####################
set(PROJECT_VERSION_FULL "0.7.0")
set(PROJECT_SO_VERSION 30)
set(PROJECT_VERSION_FULL "1.0.0")
set(PROJECT_SO_VERSION 31)

# Remove the dash and anything following, to get the #.#.# version for project()
STRING(REGEX REPLACE "\-.*$" "" VERSION_NUM "${PROJECT_VERSION_FULL}")
Expand Down
28 changes: 23 additions & 5 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ if(ENABLE_WAYLAND_CAPTURE AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
pkg_check_modules(PC_GIO_UNIX QUIET IMPORTED_TARGET gio-unix-2.0)
if(PC_PIPEWIRE_FOUND AND PC_LIBSPA_FOUND AND PC_GIO_FOUND AND PC_GIO_UNIX_FOUND)
set(OPENSHOT_WAYLAND_CAPTURE TRUE)
list(APPEND OPENSHOT_SOURCES WaylandScreenCaptureReader.cpp)
endif()
endif()
endif()
Expand Down Expand Up @@ -212,20 +211,33 @@ target_include_directories(openshot
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/libopenshot>)

if(OPENSHOT_WAYLAND_CAPTURE)
target_compile_definitions(openshot PRIVATE HAVE_WAYLAND_CAPTURE=1)
target_link_libraries(openshot PRIVATE
target_compile_definitions(openshot PRIVATE HAVE_WAYLAND_CAPTURE_PLUGIN=1)
target_link_libraries(openshot PRIVATE ${CMAKE_DL_LIBS})

# PipeWire is optional at runtime. Keeping it in a module prevents the
# dynamic loader from rejecting libopenshot on X11 or older Linux systems
# that do not provide the PipeWire 0.3 client ABI.
add_library(openshot-wayland-capture MODULE WaylandScreenCaptureReader.cpp)
target_include_directories(openshot-wayland-capture PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(openshot-wayland-capture PRIVATE
openshot
PkgConfig::PC_PIPEWIRE
PkgConfig::PC_LIBSPA
PkgConfig::PC_GIO
PkgConfig::PC_GIO_UNIX)
set_target_properties(openshot-wayland-capture PROPERTIES
BUILD_RPATH "$ORIGIN"
INSTALL_RPATH "$ORIGIN")
endif()
add_feature_info("Wayland screen capture" OPENSHOT_WAYLAND_CAPTURE "Use xdg-desktop-portal ScreenCast and PipeWire")
add_feature_info("Wayland screen capture" OPENSHOT_WAYLAND_CAPTURE "Use the optional xdg-desktop-portal and PipeWire capture module")

################# LIBOPENSHOT-AUDIO ###################
# Find JUCE-based openshot Audio libraries
if(NOT TARGET OpenShot::Audio)
# Only load if necessary (not for integrated builds)
find_package(OpenShotAudio 0.6.0 REQUIRED)
find_package(OpenShotAudio 1.0.0 REQUIRED)
endif()
target_link_libraries(openshot PUBLIC OpenShot::Audio)

Expand Down Expand Up @@ -633,6 +645,12 @@ install(TARGETS openshot
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libopenshot)

if(OPENSHOT_WAYLAND_CAPTURE)
install(TARGETS openshot-wayland-capture
COMPONENT runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

install(DIRECTORY .
COMPONENT devel
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libopenshot
Expand Down
100 changes: 85 additions & 15 deletions src/ScreenCaptureReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include <thread>
#include <vector>

#if defined(__linux__)
#include <dlfcn.h>
#endif

extern "C" {
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
Expand All @@ -43,6 +47,40 @@ extern "C" {

using namespace openshot;

#if defined(HAVE_WAYLAND_CAPTURE_PLUGIN) && defined(__linux__)
namespace
{
using WaylandBackendFactory = std::unique_ptr<ScreenCaptureReader::CaptureBackendReader> (*) (
const ScreenCaptureSettings&, ReaderInfo&);

void* load_wayland_capture_backend(std::string& error_message)
{
Dl_info library_info {};
if (dladdr(reinterpret_cast<void*>(load_wayland_capture_backend), &library_info)
&& library_info.dli_fname) {
std::string library_path(library_info.dli_fname);
const auto separator = library_path.find_last_of('/');
if (separator != std::string::npos) {
const std::string module_path = library_path.substr(0, separator + 1)
+ "libopenshot-wayland-capture.so";
if (void* module = dlopen(module_path.c_str(), RTLD_NOW | RTLD_LOCAL)) {
return module;
}
const char* error = dlerror();
error_message = error ? error : "unable to load PipeWire support.";
return nullptr;
}
}
void* module = dlopen("libopenshot-wayland-capture.so", RTLD_NOW | RTLD_LOCAL);
if (!module) {
const char* error = dlerror();
error_message = error ? error : "unable to load PipeWire support.";
}
return module;
}
}
#endif

#if defined(__linux__)
class ScreenCaptureReader::SystemAudioCapture
{
Expand Down Expand Up @@ -560,15 +598,10 @@ namespace
}
}

#if defined(HAVE_WAYLAND_CAPTURE)
std::unique_ptr<ScreenCaptureReader::CaptureBackendReader> CreateWaylandScreenCaptureReader(
const ScreenCaptureSettings& settings,
ReaderInfo& info);
#endif

ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settings)
: settings(new_settings)
, backend_reader(nullptr)
, backend_module(nullptr)
, is_open(false)
, video_stream(-1)
, frames_read(0)
Expand All @@ -593,10 +626,33 @@ ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settin
}
#endif
if (UsesWaylandPortal()) {
#if defined(HAVE_WAYLAND_CAPTURE)
backend_reader = CreateWaylandScreenCaptureReader(settings, info);
#if defined(HAVE_WAYLAND_CAPTURE_PLUGIN) && defined(__linux__)
std::string module_error;
backend_module = load_wayland_capture_backend(module_error);
if (!backend_module) {
throw InvalidOptions("Wayland screen capture backend is unavailable: "
+ module_error);
}
auto factory = reinterpret_cast<WaylandBackendFactory>(
dlsym(backend_module, "OpenShotCreateWaylandScreenCaptureReader"));
if (!factory) {
const char* error = dlerror();
dlclose(backend_module);
backend_module = nullptr;
throw InvalidOptions("Wayland screen capture backend is invalid: "
+ std::string(error ? error : "factory function is missing."));
}
try {
backend_reader = factory(settings, info);
} catch (...) {
dlclose(backend_module);
backend_module = nullptr;
throw;
}
if (!backend_reader) {
throw InvalidOptions("Wayland screen capture backend is unavailable in this build.");
dlclose(backend_module);
backend_module = nullptr;
throw InvalidOptions("Wayland screen capture backend is unavailable.");
}
#else
throw InvalidOptions("Wayland screen capture backend is unavailable in this build.");
Expand All @@ -607,10 +663,18 @@ ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settin
ScreenCaptureReader::~ScreenCaptureReader()
{
Close();
#if defined(__linux__)
backend_reader.reset();
if (backend_module) {
dlclose(backend_module);
backend_module = nullptr;
}
#endif
}

bool ScreenCaptureReader::IsOpen()
{
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
return backend_reader ? backend_reader->IsOpen() : is_open;
}

Expand All @@ -620,7 +684,7 @@ bool ScreenCaptureReader::IsBackendSupported(ScreenCaptureBackend backend)
if (backend == SCREEN_CAPTURE_X11 || backend == SCREEN_CAPTURE_AUTO) {
return true;
}
#if defined(HAVE_WAYLAND_CAPTURE)
#if defined(HAVE_WAYLAND_CAPTURE_PLUGIN)
if (backend == SCREEN_CAPTURE_WAYLAND) {
return true;
}
Expand Down Expand Up @@ -947,11 +1011,11 @@ void ScreenCaptureReader::OpenDecoder()

std::shared_ptr<Frame> ScreenCaptureReader::GetFrame(int64_t number)
{
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
if (backend_reader) {
if (!backend_reader->IsOpen()) {
throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
}
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
auto frame = backend_reader->GetFrame(number);
if (system_audio && !manual_system_audio) system_audio->AddFrameAudio(frame, number, info.fps);
return frame;
Expand All @@ -960,7 +1024,6 @@ std::shared_ptr<Frame> ScreenCaptureReader::GetFrame(int64_t number)
throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
}

const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
auto frame = DecodeNextFrame(number);
if (system_audio && !manual_system_audio) system_audio->AddFrameAudio(frame, number, info.fps);
return frame;
Expand Down Expand Up @@ -1101,13 +1164,20 @@ std::shared_ptr<Frame> ScreenCaptureReader::DecodeNextFrame(int64_t number)

void ScreenCaptureReader::Close()
{
// Signal blocking native reads before waiting for GetFrame(). The FFmpeg
// interrupt callback observes close_requested, while backend readers use
// Close() to wake their own blocking waits.
close_requested = true;
if (system_audio) {
system_audio->Close();
}
if (backend_reader) {
backend_reader->Close();
}

// GetFrame() owns all decoder and system-audio use under this mutex. Do not
// release those resources until an interrupted read has completely exited.
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
if (system_audio) {
system_audio->Close();
}
if (packet) {
av_packet_free(&packet);
}
Expand Down
3 changes: 3 additions & 0 deletions src/ScreenCaptureReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ namespace openshot
ScreenCaptureSettings settings;
#ifndef SWIG
std::unique_ptr<CaptureBackendReader> backend_reader;
// The Wayland backend is a runtime-loaded module. Keep it loaded until its
// reader is destroyed, since the reader's vtable lives in that module.
void* backend_module = nullptr;
#endif
bool is_open;
int video_stream;
Expand Down
22 changes: 22 additions & 0 deletions src/Timeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,28 @@ void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
// is clip already in list?
bool clip_found = open_clips.count(clip);

// The registry, Clip, and nested Reader can become inconsistent after a
// transient reader close. Trust the actual objects over open_clips and run
// them through a clean Close/Open cycle while this clip still intersects.
// Otherwise FrameMapper converts ReaderClosed into a black frame which can
// be repeatedly cached on every still-intersecting timing update.
if (clip_found && does_clip_intersect)
{
bool clip_reader_open = false;
try {
clip_reader_open = clip->Reader() && clip->Reader()->IsOpen();
} catch (const ReaderClosed & e) {
// A missing/replaced reader is equivalent to a closed reader here.
clip_reader_open = false;
}
if (!clip->IsOpen() || !clip_reader_open)
{
open_clips.erase(clip);
clip->Close();
clip_found = false;
}
}

if (clip_found && !does_clip_intersect)
{
// Remove clip from 'opened' list, because it's closed now
Expand Down
Loading
Loading