blob: 9aa50e19ee2df794f67426a94411bfdc1157bf6c [file] [log] [blame]
/*
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
#include <fcntl.h>
#include <libdrm/drm_fourcc.h>
#include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h>
#include <vector>
#include "absl/memory/memory.h"
#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
#include "modules/portal/pipewire_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/sanitizer.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
const int kBytesPerPixel = 4;
const int kVideoDamageRegionCount = 16;
constexpr int kCursorBpp = 4;
constexpr int CursorMetaSize(int w, int h) {
return (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) +
w * h * kCursorBpp);
}
constexpr PipeWireVersion kDmaBufModifierMinVersion = {0, 3, 33};
constexpr PipeWireVersion kDropSingleModifierMinVersion = {0, 3, 40};
class SharedScreenCastStreamPrivate {
public:
SharedScreenCastStreamPrivate();
~SharedScreenCastStreamPrivate();
bool StartScreenCastStream(uint32_t stream_node_id,
int fd,
uint32_t width = 0,
uint32_t height = 0,
bool is_cursor_embedded = false,
DesktopCapturer::Callback* callback = nullptr);
void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
void UpdateScreenCastStreamFrameRate(uint32_t frame_rate);
void SetUseDamageRegion(bool use_damage_region) {
use_damage_region_ = use_damage_region;
}
void SetObserver(SharedScreenCastStream::Observer* observer) {
observer_ = observer;
}
void StopScreenCastStream();
std::unique_ptr<SharedDesktopFrame> CaptureFrame();
std::unique_ptr<MouseCursor> CaptureCursor();
DesktopVector CaptureCursorPosition();
private:
// Stops the streams and cleans up any in-use elements.
void StopAndCleanupStream();
SharedScreenCastStream::Observer* observer_ = nullptr;
// Track damage region updates that were reported since the last time
// frame was captured
DesktopRegion damage_region_ RTC_GUARDED_BY(&latest_frame_lock_);
uint32_t pw_stream_node_id_ = 0;
DesktopSize stream_size_ = {};
DesktopSize frame_size_;
webrtc::Mutex queue_lock_;
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_
RTC_GUARDED_BY(&queue_lock_);
webrtc::Mutex latest_frame_lock_ RTC_ACQUIRED_AFTER(queue_lock_);
SharedDesktopFrame* latest_available_frame_
RTC_GUARDED_BY(&latest_frame_lock_) = nullptr;
std::unique_ptr<MouseCursor> mouse_cursor_;
DesktopVector mouse_cursor_position_ = DesktopVector(-1, -1);
int64_t modifier_;
std::unique_ptr<EglDmaBuf> egl_dmabuf_;
// List of modifiers we query as supported by the graphics card/driver
std::vector<uint64_t> modifiers_;
// PipeWire types
struct pw_context* pw_context_ = nullptr;
struct pw_core* pw_core_ = nullptr;
struct pw_stream* pw_stream_ = nullptr;
struct pw_thread_loop* pw_main_loop_ = nullptr;
struct spa_source* renegotiate_ = nullptr;
spa_hook spa_core_listener_;
spa_hook spa_stream_listener_;
// A number used to verify all previous methods and the resulting
// events have been handled.
int server_version_sync_ = 0;
// Version of the running PipeWire server we communicate with
PipeWireVersion pw_server_version_;
// Version of the library used to run our code
PipeWireVersion pw_client_version_;
// Resolution parameters.
uint32_t width_ = 0;
uint32_t height_ = 0;
// Frame rate.
uint32_t frame_rate_ = 60;
bool use_damage_region_ = true;
// Specifies whether the pipewire stream has been initialized with a request
// to embed cursor into the captured frames.
bool is_cursor_embedded_ = false;
// event handlers
pw_core_events pw_core_events_ = {};
pw_stream_events pw_stream_events_ = {};
struct spa_video_info_raw spa_video_format_;
void ProcessBuffer(pw_buffer* buffer);
bool ProcessMemFDBuffer(pw_buffer* buffer,
DesktopFrame& frame,
const DesktopVector& offset);
bool ProcessDMABuffer(pw_buffer* buffer,
DesktopFrame& frame,
const DesktopVector& offset);
void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size);
void UpdateFrameUpdatedRegions(const spa_buffer* spa_buffer,
DesktopFrame& frame);
// PipeWire callbacks
static void OnCoreError(void* data,
uint32_t id,
int seq,
int res,
const char* message);
static void OnCoreDone(void* user_data, uint32_t id, int seq);
static void OnCoreInfo(void* user_data, const pw_core_info* info);
static void OnStreamParamChanged(void* data,
uint32_t id,
const struct spa_pod* format);
static void OnStreamStateChanged(void* data,
pw_stream_state old_state,
pw_stream_state state,
const char* error_message);
static void OnStreamProcess(void* data);
// This will be invoked in case we fail to process DMA-BUF PW buffer using
// negotiated stream parameters (modifier). We will drop the modifier we
// failed to use and try to use a different one or fallback to shared memory
// buffers.
static void OnRenegotiateFormat(void* data, uint64_t);
DesktopCapturer::Callback* callback_;
};
void SharedScreenCastStreamPrivate::OnCoreError(void* data,
uint32_t id,
int seq,
int res,
const char* message) {
SharedScreenCastStreamPrivate* stream =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(stream);
RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message;
pw_thread_loop_signal(stream->pw_main_loop_, false);
}
void SharedScreenCastStreamPrivate::OnCoreInfo(void* data,
const pw_core_info* info) {
SharedScreenCastStreamPrivate* stream =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(stream);
stream->pw_server_version_ = PipeWireVersion::Parse(info->version);
}
void SharedScreenCastStreamPrivate::OnCoreDone(void* data,
uint32_t id,
int seq) {
const SharedScreenCastStreamPrivate* stream =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(stream);
if (id == PW_ID_CORE && stream->server_version_sync_ == seq) {
pw_thread_loop_signal(stream->pw_main_loop_, false);
}
}
// static
void SharedScreenCastStreamPrivate::OnStreamStateChanged(
void* data,
pw_stream_state old_state,
pw_stream_state state,
const char* error_message) {
SharedScreenCastStreamPrivate* that =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(that);
switch (state) {
case PW_STREAM_STATE_ERROR:
RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
break;
case PW_STREAM_STATE_PAUSED:
if (that->observer_ && old_state != PW_STREAM_STATE_STREAMING) {
that->observer_->OnStreamConfigured();
}
break;
case PW_STREAM_STATE_STREAMING:
case PW_STREAM_STATE_UNCONNECTED:
case PW_STREAM_STATE_CONNECTING:
break;
}
}
// static
void SharedScreenCastStreamPrivate::OnStreamParamChanged(
void* data,
uint32_t id,
const struct spa_pod* format) {
SharedScreenCastStreamPrivate* that =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(that);
RTC_LOG(LS_INFO) << "PipeWire stream format changed.";
if (!format || id != SPA_PARAM_Format) {
return;
}
spa_format_video_raw_parse(format, &that->spa_video_format_);
if (that->observer_ && that->spa_video_format_.max_framerate.denom) {
that->observer_->OnFrameRateChanged(
that->spa_video_format_.max_framerate.num /
that->spa_video_format_.max_framerate.denom);
}
auto width = that->spa_video_format_.size.width;
auto height = that->spa_video_format_.size.height;
auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
auto size = height * stride;
that->stream_size_ = DesktopSize(width, height);
uint8_t buffer[2048] = {};
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
// Setup buffers and meta header for new format.
// When SPA_FORMAT_VIDEO_modifier is present we can use DMA-BUFs as
// the server announces support for it.
// See https://github.com/PipeWire/pipewire/blob/master/doc/dma-buf.dox
const bool has_modifier =
spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier);
that->modifier_ =
has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID;
std::vector<const spa_pod*> params;
const int buffer_types = has_modifier
? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd)
: (1 << SPA_DATA_MemFd);
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride,
SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers,
SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_dataType,
SPA_POD_CHOICE_FLAGS_Int(buffer_types))));
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
SPA_POD_Int(sizeof(struct spa_meta_header)))));
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size,
SPA_POD_Int(sizeof(struct spa_meta_region)))));
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size,
SPA_POD_CHOICE_RANGE_Int(CursorMetaSize(64, 64), CursorMetaSize(1, 1),
CursorMetaSize(384, 384)))));
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size,
SPA_POD_CHOICE_RANGE_Int(
sizeof(struct spa_meta_region) * kVideoDamageRegionCount,
sizeof(struct spa_meta_region) * 1,
sizeof(struct spa_meta_region) * kVideoDamageRegionCount))));
pw_stream_update_params(that->pw_stream_, params.data(), params.size());
}
// static
void SharedScreenCastStreamPrivate::OnStreamProcess(void* data) {
SharedScreenCastStreamPrivate* that =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(that);
struct pw_buffer* next_buffer;
struct pw_buffer* buffer = nullptr;
next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
while (next_buffer) {
buffer = next_buffer;
next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
if (next_buffer) {
pw_stream_queue_buffer(that->pw_stream_, buffer);
}
}
if (!buffer) {
return;
}
struct spa_meta_header* header =
static_cast<spa_meta_header*>(spa_buffer_find_meta_data(
buffer->buffer, SPA_META_Header, sizeof(*header)));
if (header && (header->flags & SPA_META_HEADER_FLAG_CORRUPTED)) {
RTC_LOG(LS_INFO) << "Dropping corrupted buffer";
if (that->observer_) {
that->observer_->OnBufferCorruptedMetadata();
}
// Queue buffer for reuse; it will not be processed further.
pw_stream_queue_buffer(that->pw_stream_, buffer);
return;
}
that->ProcessBuffer(buffer);
pw_stream_queue_buffer(that->pw_stream_, buffer);
}
void SharedScreenCastStreamPrivate::OnRenegotiateFormat(void* data, uint64_t) {
SharedScreenCastStreamPrivate* that =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(that);
{
PipeWireThreadLoopLock thread_loop_lock(that->pw_main_loop_);
uint8_t buffer[4096] = {};
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
std::vector<const spa_pod*> params;
struct spa_rectangle resolution =
SPA_RECTANGLE(that->width_, that->height_);
struct spa_fraction frame_rate = SPA_FRACTION(that->frame_rate_, 1);
for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
if (!that->modifiers_.empty()) {
params.push_back(
BuildFormat(&builder, format, that->modifiers_,
that->width_ && that->height_ ? &resolution : nullptr,
&frame_rate));
}
params.push_back(BuildFormat(
&builder, format, /*modifiers=*/{},
that->width_ && that->height_ ? &resolution : nullptr, &frame_rate));
}
pw_stream_update_params(that->pw_stream_, params.data(), params.size());
}
}
SharedScreenCastStreamPrivate::SharedScreenCastStreamPrivate() {}
SharedScreenCastStreamPrivate::~SharedScreenCastStreamPrivate() {
StopAndCleanupStream();
}
RTC_NO_SANITIZE("cfi-icall")
bool SharedScreenCastStreamPrivate::StartScreenCastStream(
uint32_t stream_node_id,
int fd,
uint32_t width,
uint32_t height,
bool is_cursor_embedded,
DesktopCapturer::Callback* callback) {
width_ = width;
height_ = height;
callback_ = callback;
is_cursor_embedded_ = is_cursor_embedded;
if (!InitializePipeWire()) {
RTC_LOG(LS_ERROR) << "Unable to open PipeWire library";
return false;
}
egl_dmabuf_ = std::make_unique<EglDmaBuf>();
pw_stream_node_id_ = stream_node_id;
pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
pw_context_ =
pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
if (!pw_context_) {
RTC_LOG(LS_ERROR) << "Failed to create PipeWire context";
return false;
}
if (pw_thread_loop_start(pw_main_loop_) < 0) {
RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
return false;
}
pw_client_version_ = PipeWireVersion::Parse(pw_get_library_version());
// Initialize event handlers, remote end and stream-related.
pw_core_events_.version = PW_VERSION_CORE_EVENTS;
pw_core_events_.info = &OnCoreInfo;
pw_core_events_.done = &OnCoreDone;
pw_core_events_.error = &OnCoreError;
pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
pw_stream_events_.state_changed = &OnStreamStateChanged;
pw_stream_events_.param_changed = &OnStreamParamChanged;
pw_stream_events_.process = &OnStreamProcess;
{
PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
if (fd != kInvalidPipeWireFd) {
pw_core_ = pw_context_connect_fd(
pw_context_, fcntl(fd, F_DUPFD_CLOEXEC, 0), nullptr, 0);
} else {
pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
}
if (!pw_core_) {
RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context";
return false;
}
pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
// Add an event that can be later invoked by pw_loop_signal_event()
renegotiate_ = pw_loop_add_event(pw_thread_loop_get_loop(pw_main_loop_),
OnRenegotiateFormat, this);
server_version_sync_ =
pw_core_sync(pw_core_, PW_ID_CORE, server_version_sync_);
pw_thread_loop_wait(pw_main_loop_);
pw_properties* reuseProps =
pw_properties_new_string("pipewire.client.reuse=1");
pw_stream_ = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps);
if (!pw_stream_) {
RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream";
return false;
}
pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
&pw_stream_events_, this);
uint8_t buffer[4096] = {};
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
std::vector<const spa_pod*> params;
const bool has_required_pw_client_version =
pw_client_version_ >= kDmaBufModifierMinVersion;
const bool has_required_pw_server_version =
pw_server_version_ >= kDmaBufModifierMinVersion;
struct spa_rectangle resolution;
bool set_resolution = false;
if (width && height) {
resolution = SPA_RECTANGLE(width, height);
set_resolution = true;
}
struct spa_fraction default_frame_rate = SPA_FRACTION(frame_rate_, 1);
for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
// Modifiers can be used with PipeWire >= 0.3.33
if (has_required_pw_client_version && has_required_pw_server_version) {
modifiers_ = egl_dmabuf_->QueryDmaBufModifiers(format);
if (!modifiers_.empty()) {
params.push_back(BuildFormat(&builder, format, modifiers_,
set_resolution ? &resolution : nullptr,
&default_frame_rate));
}
}
params.push_back(BuildFormat(&builder, format, /*modifiers=*/{},
set_resolution ? &resolution : nullptr,
&default_frame_rate));
}
if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, pw_stream_node_id_,
PW_STREAM_FLAG_AUTOCONNECT, params.data(),
params.size()) != 0) {
RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
return false;
}
RTC_LOG(LS_INFO) << "PipeWire remote opened.";
}
return true;
}
RTC_NO_SANITIZE("cfi-icall")
void SharedScreenCastStreamPrivate::UpdateScreenCastStreamResolution(
uint32_t width,
uint32_t height) {
if (!width || !height) {
RTC_LOG(LS_WARNING) << "Bad resolution specified: " << width << "x"
<< height;
return;
}
if (!pw_main_loop_) {
RTC_LOG(LS_WARNING) << "No main pipewire loop, ignoring resolution change";
return;
}
if (!renegotiate_) {
RTC_LOG(LS_WARNING) << "Can not renegotiate stream params, ignoring "
<< "resolution change";
return;
}
if (width_ != width || height_ != height) {
width_ = width;
height_ = height;
pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_);
}
}
RTC_NO_SANITIZE("cfi-icall")
void SharedScreenCastStreamPrivate::UpdateScreenCastStreamFrameRate(
uint32_t frame_rate) {
if (!pw_main_loop_) {
RTC_LOG(LS_WARNING) << "No main pipewire loop, ignoring frame rate change";
return;
}
if (!renegotiate_) {
RTC_LOG(LS_WARNING) << "Can not renegotiate stream params, ignoring "
<< "frame rate change";
return;
}
if (frame_rate_ != frame_rate) {
frame_rate_ = frame_rate;
pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_);
}
}
void SharedScreenCastStreamPrivate::StopScreenCastStream() {
StopAndCleanupStream();
}
void SharedScreenCastStreamPrivate::StopAndCleanupStream() {
// We get buffers on the PipeWire thread, but this is called from the capturer
// thread, so we need to wait on and stop the pipewire thread before we
// disconnect the stream so that we can guarantee we aren't in the middle of
// processing a new frame.
// Even if we *do* somehow have the other objects without a pipewire thread,
// destroying them without a thread causes a crash.
if (!pw_main_loop_)
return;
// While we can stop the thread now, we cannot destroy it until we've cleaned
// up the other members.
pw_thread_loop_wait(pw_main_loop_);
pw_thread_loop_stop(pw_main_loop_);
if (pw_stream_) {
pw_stream_disconnect(pw_stream_);
pw_stream_destroy(pw_stream_);
pw_stream_ = nullptr;
{
webrtc::MutexLock lock(&queue_lock_);
queue_.Reset();
}
}
if (pw_core_) {
pw_core_disconnect(pw_core_);
pw_core_ = nullptr;
}
if (pw_context_) {
pw_context_destroy(pw_context_);
pw_context_ = nullptr;
}
pw_thread_loop_destroy(pw_main_loop_);
pw_main_loop_ = nullptr;
}
std::unique_ptr<SharedDesktopFrame>
SharedScreenCastStreamPrivate::CaptureFrame() {
webrtc::MutexLock latest_frame_lock(&latest_frame_lock_);
if (!pw_stream_ || !latest_available_frame_) {
return std::unique_ptr<SharedDesktopFrame>{};
}
std::unique_ptr<SharedDesktopFrame> frame = latest_available_frame_->Share();
if (use_damage_region_) {
frame->mutable_updated_region()->Swap(&damage_region_);
damage_region_.Clear();
}
return frame;
}
std::unique_ptr<MouseCursor> SharedScreenCastStreamPrivate::CaptureCursor() {
if (!mouse_cursor_) {
return nullptr;
}
return std::move(mouse_cursor_);
}
DesktopVector SharedScreenCastStreamPrivate::CaptureCursorPosition() {
return mouse_cursor_position_;
}
void SharedScreenCastStreamPrivate::UpdateFrameUpdatedRegions(
const spa_buffer* spa_buffer,
DesktopFrame& frame) {
latest_frame_lock_.AssertHeld();
if (!use_damage_region_) {
frame.mutable_updated_region()->SetRect(
DesktopRect::MakeSize(frame.size()));
return;
}
const struct spa_meta* video_damage = static_cast<struct spa_meta*>(
spa_buffer_find_meta(spa_buffer, SPA_META_VideoDamage));
if (!video_damage) {
damage_region_.SetRect(DesktopRect::MakeSize(frame.size()));
return;
}
frame.mutable_updated_region()->Clear();
spa_meta_region* meta_region;
spa_meta_for_each(meta_region, video_damage) {
// Skip empty regions
if (meta_region->region.size.width == 0 ||
meta_region->region.size.height == 0) {
continue;
}
damage_region_.AddRect(DesktopRect::MakeXYWH(
meta_region->region.position.x, meta_region->region.position.y,
meta_region->region.size.width, meta_region->region.size.height));
}
}
RTC_NO_SANITIZE("cfi-icall")
void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
int64_t capture_start_time_nanos = rtc::TimeNanos();
if (callback_) {
callback_->OnFrameCaptureStart();
}
spa_buffer* spa_buffer = buffer->buffer;
// Try to update the mouse cursor first, because it can be the only
// information carried by the buffer
{
const struct spa_meta_cursor* cursor =
static_cast<struct spa_meta_cursor*>(spa_buffer_find_meta_data(
spa_buffer, SPA_META_Cursor, sizeof(*cursor)));
if (cursor) {
if (spa_meta_cursor_is_valid(cursor)) {
struct spa_meta_bitmap* bitmap = nullptr;
if (cursor->bitmap_offset)
bitmap =
SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) {
const uint8_t* bitmap_data =
SPA_MEMBER(bitmap, bitmap->offset, uint8_t);
BasicDesktopFrame* mouse_frame = new BasicDesktopFrame(
DesktopSize(bitmap->size.width, bitmap->size.height));
mouse_frame->CopyPixelsFrom(
bitmap_data, bitmap->stride,
DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height));
mouse_cursor_ = std::make_unique<MouseCursor>(
mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y));
if (observer_) {
observer_->OnCursorShapeChanged();
}
}
mouse_cursor_position_.set(cursor->position.x, cursor->position.y);
if (observer_) {
observer_->OnCursorPositionChanged();
}
} else {
// Indicate an invalid cursor
mouse_cursor_position_.set(-1, -1);
}
}
}
if (spa_buffer->datas[0].chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) {
RTC_LOG(LS_INFO) << "Dropping buffer with corrupted or missing data";
if (observer_) {
observer_->OnBufferCorruptedData();
}
return;
}
if (spa_buffer->datas[0].type == SPA_DATA_MemFd &&
spa_buffer->datas[0].chunk->size == 0) {
RTC_LOG(LS_INFO) << "Dropping buffer with empty data";
if (observer_) {
observer_->OnEmptyBuffer();
}
return;
}
// Use SPA_META_VideoCrop metadata to get the frame size. KDE and GNOME do
// handle screen/window sharing differently. KDE/KWin doesn't use
// SPA_META_VideoCrop metadata and when sharing a window, it always sets
// stream size to size of the window. With that we just allocate the
// DesktopFrame using the size of the stream itself. GNOME/Mutter
// always sets stream size to the size of the whole screen, even when sharing
// a window. To get the real window size we have to use SPA_META_VideoCrop
// metadata. This gives us the size we need in order to allocate the
// DesktopFrame.
struct spa_meta_region* videocrop_metadata =
static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data(
spa_buffer, SPA_META_VideoCrop, sizeof(*videocrop_metadata)));
// Video size from metadata is bigger than an actual video stream size.
// The metadata are wrong or we should up-scale the video...in both cases
// just quit now.
if (videocrop_metadata &&
(videocrop_metadata->region.size.width >
static_cast<uint32_t>(stream_size_.width()) ||
videocrop_metadata->region.size.height >
static_cast<uint32_t>(stream_size_.height()))) {
RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!";
if (observer_) {
observer_->OnFailedToProcessBuffer();
}
return;
}
// Use SPA_META_VideoCrop metadata to get the DesktopFrame size in case
// a windows is shared and it represents just a small portion of the
// stream itself. This will be for example used in case of GNOME (Mutter)
// where the stream will have the size of the screen itself, but we care
// only about smaller portion representing the window inside.
bool videocrop_metadata_use = false;
const struct spa_rectangle* videocrop_metadata_size =
videocrop_metadata ? &videocrop_metadata->region.size : nullptr;
if (videocrop_metadata_size && videocrop_metadata_size->width != 0 &&
videocrop_metadata_size->height != 0 &&
(static_cast<int>(videocrop_metadata_size->width) <
stream_size_.width() ||
static_cast<int>(videocrop_metadata_size->height) <
stream_size_.height())) {
videocrop_metadata_use = true;
}
if (videocrop_metadata_use) {
frame_size_ = DesktopSize(videocrop_metadata_size->width,
videocrop_metadata_size->height);
} else {
frame_size_ = stream_size_;
}
// Get the position of the video crop within the stream. Just double-check
// that the position doesn't exceed the size of the stream itself.
// NOTE: Currently it looks there is no implementation using this.
uint32_t y_offset =
videocrop_metadata_use &&
(videocrop_metadata->region.position.y + frame_size_.height() <=
stream_size_.height())
? videocrop_metadata->region.position.y
: 0;
uint32_t x_offset =
videocrop_metadata_use &&
(videocrop_metadata->region.position.x + frame_size_.width() <=
stream_size_.width())
? videocrop_metadata->region.position.x
: 0;
DesktopVector offset = DesktopVector(x_offset, y_offset);
webrtc::MutexLock lock(&queue_lock_);
queue_.MoveToNextFrame();
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared";
if (observer_) {
observer_->OnFailedToProcessBuffer();
}
}
if (!queue_.current_frame() ||
!queue_.current_frame()->size().equals(frame_size_)) {
std::unique_ptr<DesktopFrame> frame(new BasicDesktopFrame(
DesktopSize(frame_size_.width(), frame_size_.height())));
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
}
bool bufferProcessed = false;
if (spa_buffer->datas[0].type == SPA_DATA_MemFd) {
bufferProcessed =
ProcessMemFDBuffer(buffer, *queue_.current_frame(), offset);
} else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) {
bufferProcessed = ProcessDMABuffer(buffer, *queue_.current_frame(), offset);
}
if (!bufferProcessed) {
if (observer_) {
observer_->OnFailedToProcessBuffer();
}
webrtc::MutexLock latest_frame_lock(&latest_frame_lock_);
latest_available_frame_ = nullptr;
return;
}
if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx ||
spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) {
uint8_t* tmp_src = queue_.current_frame()->data();
for (int i = 0; i < frame_size_.height(); ++i) {
// If both sides decided to go with the RGBx format we need to convert
// it to BGRx to match color format expected by WebRTC.
ConvertRGBxToBGRx(tmp_src, queue_.current_frame()->stride());
tmp_src += queue_.current_frame()->stride();
}
}
if (observer_) {
observer_->OnDesktopFrameChanged();
}
std::unique_ptr<SharedDesktopFrame> frame;
{
webrtc::MutexLock latest_frame_lock(&latest_frame_lock_);
UpdateFrameUpdatedRegions(spa_buffer, *queue_.current_frame());
queue_.current_frame()->set_may_contain_cursor(is_cursor_embedded_);
latest_available_frame_ = queue_.current_frame();
if (!callback_) {
return;
}
frame = latest_available_frame_->Share();
frame->set_capturer_id(DesktopCapturerId::kWaylandCapturerLinux);
frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec);
if (use_damage_region_) {
frame->mutable_updated_region()->Swap(&damage_region_);
damage_region_.Clear();
}
}
if (callback_) {
callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
std::move(frame));
}
}
RTC_NO_SANITIZE("cfi-icall")
bool SharedScreenCastStreamPrivate::ProcessMemFDBuffer(
pw_buffer* buffer,
DesktopFrame& frame,
const DesktopVector& offset) {
spa_buffer* spa_buffer = buffer->buffer;
ScopedBuf map;
uint8_t* src = nullptr;
map.initialize(
static_cast<uint8_t*>(
mmap(nullptr,
spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)),
spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
spa_buffer->datas[0].fd);
if (!map) {
RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " << std::strerror(errno);
return false;
}
src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t);
uint32_t buffer_stride = spa_buffer->datas[0].chunk->stride;
uint32_t src_stride = buffer_stride;
uint8_t* updated_src =
src + (src_stride * offset.y()) + (kBytesPerPixel * offset.x());
frame.CopyPixelsFrom(
updated_src, (src_stride - (kBytesPerPixel * offset.x())),
DesktopRect::MakeWH(frame.size().width(), frame.size().height()));
return true;
}
RTC_NO_SANITIZE("cfi-icall")
bool SharedScreenCastStreamPrivate::ProcessDMABuffer(
pw_buffer* buffer,
DesktopFrame& frame,
const DesktopVector& offset) {
spa_buffer* spa_buffer = buffer->buffer;
const uint n_planes = spa_buffer->n_datas;
if (!n_planes) {
return false;
}
std::vector<EglDmaBuf::PlaneData> plane_datas;
for (uint32_t i = 0; i < n_planes; ++i) {
EglDmaBuf::PlaneData data = {
static_cast<int32_t>(spa_buffer->datas[i].fd),
static_cast<uint32_t>(spa_buffer->datas[i].chunk->stride),
static_cast<uint32_t>(spa_buffer->datas[i].chunk->offset)};
plane_datas.push_back(data);
}
const bool imported = egl_dmabuf_->ImageFromDmaBuf(
stream_size_, spa_video_format_.format, plane_datas, modifier_, offset,
frame.size(), frame.data());
if (!imported) {
RTC_LOG(LS_ERROR) << "Dropping DMA-BUF modifier: " << modifier_
<< " and trying to renegotiate stream parameters";
if (pw_server_version_ >= kDropSingleModifierMinVersion) {
modifiers_.erase(
std::remove(modifiers_.begin(), modifiers_.end(), modifier_),
modifiers_.end());
} else {
modifiers_.clear();
}
pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_);
return false;
}
return true;
}
void SharedScreenCastStreamPrivate::ConvertRGBxToBGRx(uint8_t* frame,
uint32_t size) {
for (uint32_t i = 0; i < size; i += 4) {
uint8_t tempR = frame[i];
uint8_t tempB = frame[i + 2];
frame[i] = tempB;
frame[i + 2] = tempR;
}
}
SharedScreenCastStream::SharedScreenCastStream()
: private_(std::make_unique<SharedScreenCastStreamPrivate>()) {}
SharedScreenCastStream::~SharedScreenCastStream() {}
rtc::scoped_refptr<SharedScreenCastStream>
SharedScreenCastStream::CreateDefault() {
// Explicit new, to access non-public constructor.
return rtc::scoped_refptr<SharedScreenCastStream>(
new SharedScreenCastStream());
}
bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id) {
return private_->StartScreenCastStream(stream_node_id, kInvalidPipeWireFd);
}
bool SharedScreenCastStream::StartScreenCastStream(
uint32_t stream_node_id,
int fd,
uint32_t width,
uint32_t height,
bool is_cursor_embedded,
DesktopCapturer::Callback* callback) {
return private_->StartScreenCastStream(stream_node_id, fd, width, height,
is_cursor_embedded, callback);
}
void SharedScreenCastStream::UpdateScreenCastStreamResolution(uint32_t width,
uint32_t height) {
private_->UpdateScreenCastStreamResolution(width, height);
}
void SharedScreenCastStream::UpdateScreenCastStreamFrameRate(
uint32_t frame_rate) {
private_->UpdateScreenCastStreamFrameRate(frame_rate);
}
void SharedScreenCastStream::SetUseDamageRegion(bool use_damage_region) {
private_->SetUseDamageRegion(use_damage_region);
}
void SharedScreenCastStream::SetObserver(
SharedScreenCastStream::Observer* observer) {
private_->SetObserver(observer);
}
void SharedScreenCastStream::StopScreenCastStream() {
private_->StopScreenCastStream();
}
std::unique_ptr<SharedDesktopFrame> SharedScreenCastStream::CaptureFrame() {
return private_->CaptureFrame();
}
std::unique_ptr<MouseCursor> SharedScreenCastStream::CaptureCursor() {
return private_->CaptureCursor();
}
std::optional<DesktopVector> SharedScreenCastStream::CaptureCursorPosition() {
DesktopVector position = private_->CaptureCursorPosition();
// Consider only (x >= 0 and y >= 0) a valid position
if (position.x() < 0 || position.y() < 0) {
return std::nullopt;
}
return position;
}
} // namespace webrtc