blob: 43ad756bbdb470c4dbff752149fae4bd48b21f69 [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 <libdrm/drm_fourcc.h>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
#include <spa/utils/result.h>
#include <sys/mman.h>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/string_to_number.h"
#include "rtc_base/synchronization/mutex.h"
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
#include "modules/desktop_capture/linux/wayland/pipewire_stubs.h"
using modules_desktop_capture_linux_wayland::InitializeStubs;
using modules_desktop_capture_linux_wayland::kModuleDrm;
using modules_desktop_capture_linux_wayland::kModulePipewire;
using modules_desktop_capture_linux_wayland::StubPathMap;
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
namespace webrtc {
const int kBytesPerPixel = 4;
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
const char kPipeWireLib[] = "libpipewire-0.3.so.0";
const char kDrmLib[] = "libdrm.so.2";
#endif
#if !PW_CHECK_VERSION(0, 3, 29)
#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
#endif
#if !PW_CHECK_VERSION(0, 3, 33)
#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
#endif
struct PipeWireVersion {
int major = 0;
int minor = 0;
int micro = 0;
};
constexpr PipeWireVersion kDmaBufMinVersion = {0, 3, 24};
constexpr PipeWireVersion kDmaBufModifierMinVersion = {0, 3, 33};
PipeWireVersion ParsePipeWireVersion(const char* version) {
std::vector<std::string> parsed_version;
rtc::split(version, '.', &parsed_version);
if (parsed_version.size() != 3) {
return {};
}
absl::optional<int> major = rtc::StringToNumber<int>(parsed_version.at(0));
absl::optional<int> minor = rtc::StringToNumber<int>(parsed_version.at(1));
absl::optional<int> micro = rtc::StringToNumber<int>(parsed_version.at(2));
// Return invalid version if we failed to parse it
if (!major || !minor || !micro) {
return {0, 0, 0};
}
return {major.value(), micro.value(), micro.value()};
}
spa_pod* BuildFormat(spa_pod_builder* builder,
uint32_t format,
const std::vector<uint64_t>& modifiers) {
bool first = true;
spa_pod_frame frames[2];
spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1};
spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX};
spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format,
SPA_PARAM_EnumFormat);
spa_pod_builder_add(builder, SPA_FORMAT_mediaType,
SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype,
SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
if (modifiers.size()) {
spa_pod_builder_prop(
builder, SPA_FORMAT_VIDEO_modifier,
SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0);
// modifiers from the array
for (int64_t val : modifiers) {
spa_pod_builder_long(builder, val);
// Add the first modifier twice as the very first value is the default
// option
if (first) {
spa_pod_builder_long(builder, val);
first = false;
}
}
spa_pod_builder_pop(builder, &frames[1]);
}
spa_pod_builder_add(
builder, SPA_FORMAT_VIDEO_size,
SPA_POD_CHOICE_RANGE_Rectangle(
&pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds),
0);
return static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frames[0]));
}
class PipeWireThreadLoopLock {
public:
explicit PipeWireThreadLoopLock(pw_thread_loop* loop) : loop_(loop) {
pw_thread_loop_lock(loop_);
}
~PipeWireThreadLoopLock() { pw_thread_loop_unlock(loop_); }
private:
pw_thread_loop* const loop_;
};
class ScopedBuf {
public:
ScopedBuf() {}
ScopedBuf(uint8_t* map, int map_size, int fd)
: map_(map), map_size_(map_size), fd_(fd) {}
~ScopedBuf() {
if (map_ != MAP_FAILED) {
munmap(map_, map_size_);
}
}
operator bool() { return map_ != MAP_FAILED; }
void initialize(uint8_t* map, int map_size, int fd) {
map_ = map;
map_size_ = map_size;
fd_ = fd;
}
uint8_t* get() { return map_; }
protected:
uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED);
int map_size_;
int fd_;
};
class SharedScreenCastStreamPrivate {
public:
SharedScreenCastStreamPrivate();
~SharedScreenCastStreamPrivate();
bool StartScreenCastStream(uint32_t stream_node_id, int fd);
void StopScreenCastStream();
std::unique_ptr<BasicDesktopFrame> CaptureFrame();
private:
uint32_t pw_stream_node_id_ = 0;
int pw_fd_ = -1;
DesktopSize desktop_size_ = {};
DesktopSize video_size_;
webrtc::Mutex current_frame_lock_;
std::unique_ptr<BasicDesktopFrame> current_frame_;
int64_t modifier_;
std::unique_ptr<EglDmaBuf> egl_dmabuf_;
// 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;
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_;
// 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);
void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size);
// 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);
};
bool operator>=(const PipeWireVersion& current_pw_version,
const PipeWireVersion& required_pw_version) {
if (!current_pw_version.major && !current_pw_version.minor &&
!current_pw_version.micro) {
return false;
}
return std::tie(current_pw_version.major, current_pw_version.minor,
current_pw_version.micro) >=
std::tie(required_pw_version.major, required_pw_version.minor,
required_pw_version.micro);
}
bool operator<=(const PipeWireVersion& current_pw_version,
const PipeWireVersion& required_pw_version) {
if (!current_pw_version.major && !current_pw_version.minor &&
!current_pw_version.micro) {
return false;
}
return std::tie(current_pw_version.major, current_pw_version.minor,
current_pw_version.micro) <=
std::tie(required_pw_version.major, required_pw_version.minor,
required_pw_version.micro);
}
void SharedScreenCastStreamPrivate::OnCoreError(void* data,
uint32_t id,
int seq,
int res,
const char* message) {
SharedScreenCastStreamPrivate* that =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(that);
RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message;
}
void SharedScreenCastStreamPrivate::OnCoreInfo(void* data,
const pw_core_info* info) {
SharedScreenCastStreamPrivate* stream =
static_cast<SharedScreenCastStreamPrivate*>(data);
RTC_DCHECK(stream);
stream->pw_server_version_ = ParsePipeWireVersion(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:
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_);
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->desktop_size_ = DesktopSize(width, height);
uint8_t buffer[1024] = {};
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 || (that->pw_server_version_ >= kDmaBufMinVersion)
? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) |
(1 << SPA_DATA_MemPtr)
: (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr);
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)))));
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;
}
that->ProcessBuffer(buffer);
pw_stream_queue_buffer(that->pw_stream_, buffer);
}
SharedScreenCastStreamPrivate::SharedScreenCastStreamPrivate() {}
SharedScreenCastStreamPrivate::~SharedScreenCastStreamPrivate() {
if (pw_main_loop_) {
pw_thread_loop_stop(pw_main_loop_);
}
if (pw_stream_) {
pw_stream_destroy(pw_stream_);
}
if (pw_core_) {
pw_core_disconnect(pw_core_);
}
if (pw_context_) {
pw_context_destroy(pw_context_);
}
if (pw_main_loop_) {
pw_thread_loop_destroy(pw_main_loop_);
}
}
bool SharedScreenCastStreamPrivate::StartScreenCastStream(
uint32_t stream_node_id,
int fd) {
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
StubPathMap paths;
// Check if the PipeWire and DRM libraries are available.
paths[kModulePipewire].push_back(kPipeWireLib);
paths[kModuleDrm].push_back(kDrmLib);
if (!InitializeStubs(paths)) {
RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
return false;
}
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
egl_dmabuf_ = std::make_unique<EglDmaBuf>();
pw_stream_node_id_ = stream_node_id;
pw_fd_ = fd;
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_ = ParsePipeWireVersion(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_);
pw_core_ = pw_context_connect_fd(pw_context_, pw_fd_, 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);
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[2048] = {};
std::vector<uint64_t> modifiers;
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;
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));
}
}
params.push_back(BuildFormat(&builder, format, /*modifiers=*/{}));
}
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;
}
void SharedScreenCastStreamPrivate::StopScreenCastStream() {
if (pw_stream_) {
pw_stream_disconnect(pw_stream_);
}
}
std::unique_ptr<BasicDesktopFrame>
SharedScreenCastStreamPrivate::CaptureFrame() {
webrtc::MutexLock lock(&current_frame_lock_);
if (!current_frame_ || !current_frame_->data()) {
return nullptr;
}
return std::move(current_frame_);
}
void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
spa_buffer* spa_buffer = buffer->buffer;
ScopedBuf map;
std::unique_ptr<uint8_t[]> src_unique_ptr;
uint8_t* src = nullptr;
if (spa_buffer->datas[0].chunk->size == 0) {
RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size.";
return;
}
if (spa_buffer->datas[0].type == SPA_DATA_MemFd) {
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;
}
src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t);
} else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) {
const uint n_planes = spa_buffer->n_datas;
if (!n_planes) {
return;
}
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);
}
src_unique_ptr = egl_dmabuf_->ImageFromDmaBuf(
desktop_size_, spa_video_format_.format, plane_datas, modifier_);
src = src_unique_ptr.get();
} else if (spa_buffer->datas[0].type == SPA_DATA_MemPtr) {
src = static_cast<uint8_t*>(spa_buffer->datas[0].data);
}
if (!src) {
return;
}
struct spa_meta_region* video_metadata =
static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data(
spa_buffer, SPA_META_VideoCrop, sizeof(*video_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 (video_metadata && (video_metadata->region.size.width >
static_cast<uint32_t>(desktop_size_.width()) ||
video_metadata->region.size.height >
static_cast<uint32_t>(desktop_size_.height()))) {
RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!";
return;
}
// Use video metadata when video size from metadata is set and smaller than
// video stream size, so we need to adjust it.
bool video_metadata_use = false;
const struct spa_rectangle* video_metadata_size =
video_metadata ? &video_metadata->region.size : nullptr;
if (video_metadata_size && video_metadata_size->width != 0 &&
video_metadata_size->height != 0 &&
(static_cast<int>(video_metadata_size->width) < desktop_size_.width() ||
static_cast<int>(video_metadata_size->height) <
desktop_size_.height())) {
video_metadata_use = true;
}
if (video_metadata_use) {
video_size_ =
DesktopSize(video_metadata_size->width, video_metadata_size->height);
} else {
video_size_ = desktop_size_;
}
uint32_t y_offset = video_metadata_use && (video_metadata->region.position.y +
video_size_.height() <=
desktop_size_.height())
? video_metadata->region.position.y
: 0;
uint32_t x_offset = video_metadata_use && (video_metadata->region.position.x +
video_size_.width() <=
desktop_size_.width())
? video_metadata->region.position.x
: 0;
webrtc::MutexLock lock(&current_frame_lock_);
uint8_t* updated_src = src + (spa_buffer->datas[0].chunk->stride * y_offset) +
(kBytesPerPixel * x_offset);
current_frame_ = std::make_unique<BasicDesktopFrame>(
DesktopSize(video_size_.width(), video_size_.height()));
current_frame_->CopyPixelsFrom(
updated_src,
(spa_buffer->datas[0].chunk->stride - (kBytesPerPixel * x_offset)),
DesktopRect::MakeWH(video_size_.width(), video_size_.height()));
if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx ||
spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) {
uint8_t* tmp_src = current_frame_->data();
for (int i = 0; i < video_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, current_frame_->stride());
tmp_src += current_frame_->stride();
}
}
}
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() {
return new SharedScreenCastStream();
}
bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id,
int fd) {
return private_->StartScreenCastStream(stream_node_id, fd);
}
void SharedScreenCastStream::StopScreenCastStream() {
private_->StopScreenCastStream();
}
std::unique_ptr<BasicDesktopFrame> SharedScreenCastStream::CaptureFrame() {
return private_->CaptureFrame();
}
} // namespace webrtc