PipeWire capturer: add initial test for SharedScreenCastStream

This test created another PipeWire stream we can connect to with
SharedScreenCastStream and recieve frames from there. This is an
initial version, where I test whether we can successfuly connect
and disconnect, receive frames and it also tests DesktopFrameQueue.

In the future I will add tests to test mouse cursor and try to
come up with some corner cases and possible scenarios.

Bug: webrtc:13429
Change-Id: Ib2a749207085c6324ffe3d5cc8f2f9c631fa6459
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/256267
Reviewed-by: Christoffer Jansson <jansson@webrtc.org>
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Jan Grulich <grulja@gmail.com>
Reviewed-by: Jeremy Leconte <jleconte@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38431}
This commit is contained in:
Jan Grulich 2022-10-18 14:27:17 +02:00 committed by WebRTC LUCI CQ
parent aafcc43440
commit 1264dc165b
15 changed files with 1195 additions and 21 deletions

View File

@ -106,6 +106,9 @@ if (!build_with_chromium) {
"tools_webrtc/perf:webrtc_dashboard_upload",
]
}
if ((is_linux || is_chromeos) && rtc_use_pipewire) {
deps += [ "modules/desktop_capture:shared_screencast_stream_test" ]
}
}
if (target_os == "android") {
deps += [ "tools_webrtc:binary_version_check" ]

15
DEPS
View File

@ -490,6 +490,21 @@ deps = {
],
'dep_type': 'cipd',
},
'src/third_party/pipewire/linux-amd64': {
'packages': [
{
'package': 'chromium/third_party/pipewire/linux-amd64',
'version': 'BaVKmAmwpjdS6O0pnjSaMNSKhO1nmk5mRnyPVAJ2-HEC',
},
{
'package': 'chromium/third_party/pipewire-media-session/linux-amd64',
'version': 'Y6wUeITvAA0QD1vt8_a7eQdzbp0gkI1B02qfZUMJdowC',
},
],
'condition': 'checkout_linux',
'dep_type': 'cipd',
},
# Everything coming after this is automatically updated by the auto-roller.
# === ANDROID_DEPS Generated Code Start ===

View File

@ -3324,6 +3324,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -4623,6 +4649,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -5056,6 +5108,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -6358,6 +6436,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -6792,6 +6896,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {

View File

@ -80,6 +80,14 @@
"label": "//pc:peerconnection_unittests",
"type": "console_test_launcher",
},
"pipewire_shared_screencast_stream_test": {
"label":
"//modules/desktop_capture:pipewire_shared_screencast_stream_test",
"type":
"script",
"script":
"//modules/desktop_capture/linux/wayland/test/shared_screencast_stream_test.py",
},
"rtc_media_unittests": {
"label": "//media:rtc_media_unittests",
"type": "console_test_launcher",

View File

@ -183,6 +183,12 @@
'voip_unittests': {},
'webrtc_nonparallel_tests': {},
},
'linux_desktop_specific_tests': {
'pipewire_shared_screencast_stream_test': {
'args': ['.'],
'mixins': ['resultdb-gtest-json-format'],
},
},
'more_configs_tests': {
'peerconnection_unittests': {
'swarming': {
@ -225,5 +231,20 @@
'desktop_tests',
'video_capture_tests',
],
'linux_desktop_tests_tryserver': [
'desktop_tests',
'linux_desktop_specific_tests',
'video_capture_tests_tryserver',
'webrtc_perf_tests_tryserver',
],
'linux_desktop_tests_with_video_capture': [
'desktop_tests',
'linux_desktop_specific_tests',
'video_capture_tests',
],
'linux_tests': [
'desktop_tests',
'linux_desktop_specific_tests',
],
},
}

View File

@ -5941,6 +5941,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -6380,6 +6406,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -6814,6 +6866,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -7706,6 +7784,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -8621,6 +8725,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {
@ -9054,6 +9184,32 @@
},
"test_id_prefix": "ninja://pc:peerconnection_unittests/"
},
{
"args": [
".",
"--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
],
"isolate_name": "pipewire_shared_screencast_stream_test",
"merge": {
"args": [],
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
"name": "pipewire_shared_screencast_stream_test",
"resultdb": {
"result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
"result_format": "gtest_json"
},
"swarming": {
"can_use_on_swarming_builders": true,
"dimension_sets": [
{
"cpu": "x86-64",
"os": "Ubuntu-18.04"
}
]
},
"test_id_prefix": "ninja://modules/desktop_capture:pipewire_shared_screencast_stream_test/"
},
{
"isolate_name": "rtc_media_unittests",
"merge": {

View File

@ -83,7 +83,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'Linux MSan': {
@ -91,6 +91,9 @@
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
# TODO(crbug.com/webrtc/14568): use 'linux_tests'
# Fails on "MemorySanitizer: use-of-uninitialized-value in
# libpipewire-0.3.so"
},
},
'Linux Tsan v2': {
@ -98,20 +101,23 @@
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
# TODO(crbug.com/webrtc/14568): use 'linux_tests'
# Fails on "ThreadSanitizer: data race on vptr (ctor/dtor vs
# virtual call) in shared_screencast_stream_test"
},
},
'Linux UBSan': {
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'Linux UBSan vptr': {
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'Linux32 Debug': {
@ -135,7 +141,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'Linux64 Debug (ARM)': {},
@ -143,7 +149,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests_with_video_capture',
'isolated_scripts': 'linux_desktop_tests_with_video_capture',
},
},
'Linux64 Release (ARM)': {},
@ -466,7 +472,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'linux_compile_arm64_dbg': {},
@ -479,7 +485,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'linux_libfuzzer_rel': {},
@ -487,7 +493,7 @@
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'linux_more_configs': {
@ -502,13 +508,16 @@
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
# TODO(crbug.com/webrtc/14568): use 'linux_tests'
# Fails on "MemorySanitizer: use-of-uninitialized-value in
# libpipewire-0.3.so"
},
},
'linux_rel': {
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests_tryserver',
'isolated_scripts': 'linux_desktop_tests_tryserver',
},
},
'linux_tsan2': {
@ -516,20 +525,23 @@
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
# TODO(crbug.com/webrtc/14568): use 'linux_tests'
# Fails on "ThreadSanitizer: data race on vptr (ctor/dtor vs
# virtual call) in shared_screencast_stream_test"
},
},
'linux_ubsan': {
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'linux_ubsan_vptr': {
'os_type': 'linux',
'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
'isolated_scripts': 'linux_tests',
},
},
'linux_x86_dbg': {

View File

@ -95,6 +95,66 @@ if (rtc_include_tests) {
}
}
if ((is_linux || is_chromeos) && rtc_use_pipewire) {
rtc_test("shared_screencast_stream_test") {
testonly = true
sources = [
"linux/wayland/shared_screencast_stream_unittest.cc",
"linux/wayland/test/fake_screencast_stream.cc",
"linux/wayland/test/fake_screencast_stream.h",
]
configs += [
":gio",
":pipewire",
":gbm",
":egl",
":epoxy",
":libdrm",
]
deps = [
":desktop_capture",
":desktop_capture_mock",
":primitives",
"../../rtc_base:checks",
"../../rtc_base:logging",
"../../rtc_base:random",
"../../rtc_base:timeutils",
# TODO(bugs.webrtc.org/9987): Remove this dep on rtc_base:rtc_base once
# rtc_base:threading is fully defined.
"../../rtc_base:rtc_base",
"../../rtc_base:task_queue_for_test",
"../../rtc_base:threading",
"../../system_wrappers",
"../../test:test_main",
"../../test:test_support",
"//api/units:time_delta",
"//rtc_base:rtc_event",
]
if (!rtc_link_pipewire) {
deps += [ ":pipewire_stubs" ]
}
public_configs = [ ":pipewire_config" ]
}
group("pipewire_shared_screencast_stream_test") {
testonly = true
deps = [ ":shared_screencast_stream_test" ]
data = [
"../../third_party/pipewire",
"linux/wayland/test/shared_screencast_stream_test.py",
"${root_out_dir}/shared_screencast_stream_test",
]
}
}
rtc_library("desktop_capture_unittests") {
testonly = true

View File

@ -31,6 +31,8 @@ pw_stream * pw_stream_new(pw_core *core, const char *name, pw_properties *props)
int pw_stream_queue_buffer(pw_stream *stream, pw_buffer *buffer);
int pw_stream_set_active(pw_stream *stream, bool active);
int pw_stream_update_params(pw_stream *stream, const spa_pod **params, uint32_t n_params);
uint32_t pw_stream_get_node_id(pw_stream *stream);
pw_stream_state pw_stream_get_state(pw_stream *stream, const char **error);
// thread-loop.h
void pw_thread_loop_destroy(pw_thread_loop *loop);

View File

@ -21,8 +21,6 @@
#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/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/sanitizer.h"
@ -90,8 +88,11 @@ class SharedScreenCastStreamPrivate {
uint32_t width = 0,
uint32_t height = 0);
void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
void SetObserver(SharedScreenCastStream::Observer* observer) {
observer_ = observer;
}
void StopScreenCastStream();
std::unique_ptr<DesktopFrame> CaptureFrame();
std::unique_ptr<SharedDesktopFrame> CaptureFrame();
std::unique_ptr<MouseCursor> CaptureCursor();
DesktopVector CaptureCursorPosition();
@ -99,6 +100,7 @@ class SharedScreenCastStreamPrivate {
// Stops the streams and cleans up any in-use elements.
void StopAndCleanupStream();
SharedScreenCastStream::Observer* observer_ = nullptr;
uint32_t pw_stream_node_id_ = 0;
DesktopSize stream_size_ = {};
@ -575,15 +577,15 @@ void SharedScreenCastStreamPrivate::StopAndCleanupStream() {
pw_main_loop_ = nullptr;
}
std::unique_ptr<DesktopFrame> SharedScreenCastStreamPrivate::CaptureFrame() {
std::unique_ptr<SharedDesktopFrame>
SharedScreenCastStreamPrivate::CaptureFrame() {
webrtc::MutexLock lock(&queue_lock_);
if (!pw_stream_ || !queue_.current_frame()) {
return std::unique_ptr<DesktopFrame>{};
return std::unique_ptr<SharedDesktopFrame>{};
}
std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share();
return std::move(frame);
return queue_.current_frame()->Share();
}
std::unique_ptr<MouseCursor> SharedScreenCastStreamPrivate::CaptureCursor() {
@ -628,8 +630,18 @@ void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height));
mouse_cursor_ = std::make_unique<MouseCursor>(
mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y));
// For testing purpose
if (observer_) {
observer_->OnCursorShapeChanged();
}
}
mouse_cursor_position_.set(cursor->position.x, cursor->position.y);
// For testing purpose
if (observer_) {
observer_->OnCursorPositionChanged();
}
}
}
@ -704,6 +716,10 @@ void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
}
if (!src) {
// For testing purpose
if (observer_) {
observer_->OnFailedToProcessBuffer();
}
return;
}
@ -730,6 +746,11 @@ void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
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;
}
@ -795,6 +816,10 @@ void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
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() ||
@ -821,6 +846,11 @@ void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
queue_.current_frame()->mutable_updated_region()->SetRect(
DesktopRect::MakeSize(queue_.current_frame()->size()));
// For testing purpose
if (observer_) {
observer_->OnDesktopFrameChanged();
}
}
void SharedScreenCastStreamPrivate::ConvertRGBxToBGRx(uint8_t* frame,
@ -861,11 +891,16 @@ void SharedScreenCastStream::UpdateScreenCastStreamResolution(uint32_t width,
private_->UpdateScreenCastStreamResolution(width, height);
}
void SharedScreenCastStream::SetObserver(
SharedScreenCastStream::Observer* observer) {
private_->SetObserver(observer);
}
void SharedScreenCastStream::StopScreenCastStream() {
private_->StopScreenCastStream();
}
std::unique_ptr<DesktopFrame> SharedScreenCastStream::CaptureFrame() {
std::unique_ptr<SharedDesktopFrame> SharedScreenCastStream::CaptureFrame() {
return private_->CaptureFrame();
}

View File

@ -16,8 +16,9 @@
#include "absl/types/optional.h"
#include "api/ref_counted_base.h"
#include "api/scoped_refptr.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
@ -27,6 +28,18 @@ class SharedScreenCastStreamPrivate;
class RTC_EXPORT SharedScreenCastStream
: public rtc::RefCountedNonVirtual<SharedScreenCastStream> {
public:
class Observer {
public:
virtual void OnCursorPositionChanged() = 0;
virtual void OnCursorShapeChanged() = 0;
virtual void OnDesktopFrameChanged() = 0;
virtual void OnFailedToProcessBuffer() = 0;
protected:
Observer() = default;
virtual ~Observer() = default;
};
static rtc::scoped_refptr<SharedScreenCastStream> CreateDefault();
bool StartScreenCastStream(uint32_t stream_node_id);
@ -35,6 +48,7 @@ class RTC_EXPORT SharedScreenCastStream
uint32_t width = 0,
uint32_t height = 0);
void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
void SetObserver(SharedScreenCastStream::Observer* observer);
void StopScreenCastStream();
// Below functions return the most recent information we get from a
@ -47,7 +61,7 @@ class RTC_EXPORT SharedScreenCastStream
// Returns the most recent screen/window frame we obtained from PipeWire
// buffer. Will return an empty frame in case we didn't manage to get a frame
// from PipeWire buffer.
std::unique_ptr<DesktopFrame> CaptureFrame();
std::unique_ptr<SharedDesktopFrame> CaptureFrame();
// Returns the most recent mouse cursor image. Will return an nullptr cursor
// in case we didn't manage to get a cursor from PipeWire buffer. NOTE: the
@ -65,6 +79,13 @@ class RTC_EXPORT SharedScreenCastStream
SharedScreenCastStream();
private:
friend class SharedScreenCastStreamPrivate;
// Allows test cases to use private functionality
friend class PipeWireStreamTest;
// FIXME: is this a useful thing to be public?
explicit SharedScreenCastStream(Observer* notifier);
SharedScreenCastStream(const SharedScreenCastStream&) = delete;
SharedScreenCastStream& operator=(const SharedScreenCastStream&) = delete;

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 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 <memory>
#include <utility>
#include "api/units/time_delta.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/linux/wayland/test/fake_screencast_stream.h"
#include "modules/desktop_capture/rgba_color.h"
#include "rtc_base/event.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::_;
using ::testing::Ge;
using ::testing::Invoke;
namespace webrtc {
constexpr TimeDelta kShortWait = TimeDelta::Seconds(2);
constexpr TimeDelta kLongWait = TimeDelta::Seconds(10);
constexpr int kBytesPerPixel = 4;
constexpr int32_t kWidth = 800;
constexpr int32_t kHeight = 640;
class PipeWireStreamTest : public ::testing::Test,
public FakeScreenCastStream::Observer,
public SharedScreenCastStream::Observer {
public:
PipeWireStreamTest()
: fake_screencast_stream_(
std::make_unique<FakeScreenCastStream>(this, kWidth, kHeight)),
shared_screencast_stream_(new SharedScreenCastStream()) {
shared_screencast_stream_->SetObserver(this);
}
~PipeWireStreamTest() override {}
// FakeScreenCastPortal::Observer
MOCK_METHOD(void, OnFrameRecorded, (), (override));
MOCK_METHOD(void, OnStreamReady, (uint32_t stream_node_id), (override));
MOCK_METHOD(void, OnStartStreaming, (), (override));
MOCK_METHOD(void, OnStopStreaming, (), (override));
// SharedScreenCastStream::Observer
MOCK_METHOD(void, OnCursorPositionChanged, (), (override));
MOCK_METHOD(void, OnCursorShapeChanged, (), (override));
MOCK_METHOD(void, OnDesktopFrameChanged, (), (override));
MOCK_METHOD(void, OnFailedToProcessBuffer, (), (override));
void StartScreenCastStream(uint32_t stream_node_id) {
shared_screencast_stream_->StartScreenCastStream(stream_node_id);
}
protected:
uint recorded_frames_ = 0;
bool streaming_ = false;
std::unique_ptr<FakeScreenCastStream> fake_screencast_stream_;
rtc::scoped_refptr<SharedScreenCastStream> shared_screencast_stream_;
};
TEST_F(PipeWireStreamTest, TestPipeWire) {
// Set expectations for PipeWire to successfully connect both streams
rtc::Event waitConnectEvent;
EXPECT_CALL(*this, OnStreamReady(_))
.WillOnce(Invoke(this, &PipeWireStreamTest::StartScreenCastStream));
EXPECT_CALL(*this, OnStartStreaming).WillOnce([&waitConnectEvent] {
waitConnectEvent.Set();
});
// Give it some time to connect, the order between these shouldn't matter, but
// we need to be sure we are connected before we proceed to work with frames.
waitConnectEvent.Wait(kLongWait);
rtc::Event frameRetrievedEvent;
EXPECT_CALL(*this, OnFrameRecorded).Times(3);
EXPECT_CALL(*this, OnDesktopFrameChanged)
.WillRepeatedly([&frameRetrievedEvent] { frameRetrievedEvent.Set(); });
// Record a frame in FakePipeWireStream
RgbaColor red_color(255, 0, 0);
fake_screencast_stream_->RecordFrame(red_color);
frameRetrievedEvent.Wait(kShortWait);
// Retrieve a frame from SharedScreenCastStream
frameRetrievedEvent.Wait(kShortWait);
std::unique_ptr<SharedDesktopFrame> frame =
shared_screencast_stream_->CaptureFrame();
// Check frame parameters
ASSERT_NE(frame, nullptr);
ASSERT_NE(frame->data(), nullptr);
EXPECT_EQ(frame->rect().width(), kWidth);
EXPECT_EQ(frame->rect().height(), kHeight);
EXPECT_EQ(frame->stride(), frame->rect().width() * kBytesPerPixel);
EXPECT_EQ(frame->data()[0], static_cast<uint8_t>(red_color.ToUInt32()));
// Test DesktopFrameQueue
RgbaColor green_color(0, 255, 0);
fake_screencast_stream_->RecordFrame(green_color);
frameRetrievedEvent.Wait(kShortWait);
std::unique_ptr<SharedDesktopFrame> frame2 =
shared_screencast_stream_->CaptureFrame();
ASSERT_NE(frame2, nullptr);
ASSERT_NE(frame2->data(), nullptr);
EXPECT_EQ(frame2->rect().width(), kWidth);
EXPECT_EQ(frame2->rect().height(), kHeight);
EXPECT_EQ(frame2->stride(), frame->rect().width() * kBytesPerPixel);
EXPECT_EQ(frame2->data()[0], static_cast<uint8_t>(green_color.ToUInt32()));
// Thanks to DesktopFrameQueue we should be able to have two frames shared
EXPECT_EQ(frame->IsShared(), true);
EXPECT_EQ(frame2->IsShared(), true);
EXPECT_NE(frame->data(), frame2->data());
// This should result into overwriting a frame in use
rtc::Event frameRecordedEvent;
RgbaColor blue_color(0, 0, 255);
EXPECT_CALL(*this, OnFailedToProcessBuffer).WillOnce([&frameRecordedEvent] {
frameRecordedEvent.Set();
});
fake_screencast_stream_->RecordFrame(blue_color);
frameRecordedEvent.Wait(kShortWait);
// Test disconnection from stream
EXPECT_CALL(*this, OnStopStreaming);
shared_screencast_stream_->StopScreenCastStream();
}
} // namespace webrtc

View File

@ -0,0 +1,370 @@
/*
* 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/test/fake_screencast_stream.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <utility>
#include <vector>
#include "rtc_base/logging.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::kModulePipewire;
using modules_desktop_capture_linux_wayland::StubPathMap;
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
namespace webrtc {
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
const char kPipeWireLib[] = "libpipewire-0.3.so.0";
#endif
constexpr int kBytesPerPixel = 4;
FakeScreenCastStream::FakeScreenCastStream(Observer* observer,
uint32_t width,
uint32_t height)
: observer_(observer), width_(width), height_(height) {
#if defined(WEBRTC_DLOPEN_PIPEWIRE)
StubPathMap paths;
// Check if the PipeWire library is available.
paths[kModulePipewire].push_back(kPipeWireLib);
if (!InitializeStubs(paths)) {
RTC_LOG(LS_ERROR)
<< "One of following libraries is missing on your system:\n"
<< " - PipeWire (" << kPipeWireLib << ")\n";
return;
}
#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
pw_main_loop_ = pw_thread_loop_new("pipewire-test-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) << "PipeWire test: Failed to create PipeWire context";
return;
}
if (pw_thread_loop_start(pw_main_loop_) < 0) {
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to start main PipeWire loop";
return;
}
// Initialize event handlers, remote end and stream-related.
pw_core_events_.version = PW_VERSION_CORE_EVENTS;
pw_core_events_.error = &OnCoreError;
pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
pw_stream_events_.add_buffer = &OnStreamAddBuffer;
pw_stream_events_.remove_buffer = &OnStreamRemoveBuffer;
pw_stream_events_.state_changed = &OnStreamStateChanged;
pw_stream_events_.param_changed = &OnStreamParamChanged;
{
PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
if (!pw_core_) {
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to connect PipeWire context";
return;
}
pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
pw_stream_ = pw_stream_new(pw_core_, "webrtc-test-stream", nullptr);
if (!pw_stream_) {
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire stream";
return;
}
pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
&pw_stream_events_, this);
uint8_t buffer[2048] = {};
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
std::vector<const spa_pod*> params;
spa_rectangle resolution =
SPA_RECTANGLE(uint32_t(width_), uint32_t(height_));
params.push_back(BuildFormat(&builder, SPA_VIDEO_FORMAT_BGRx,
/*modifiers=*/{}, &resolution));
auto flags =
pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
if (pw_stream_connect(pw_stream_, PW_DIRECTION_OUTPUT, SPA_ID_INVALID,
flags, params.data(), params.size()) != 0) {
RTC_LOG(LS_ERROR) << "PipeWire test: Could not connect receiving stream.";
pw_stream_destroy(pw_stream_);
pw_stream_ = nullptr;
return;
}
}
return;
}
FakeScreenCastStream::~FakeScreenCastStream() {
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_);
}
}
void FakeScreenCastStream::RecordFrame(RgbaColor rgba_color) {
const char* error;
if (pw_stream_get_state(pw_stream_, &error) != PW_STREAM_STATE_STREAMING) {
if (error) {
RTC_LOG(LS_ERROR)
<< "PipeWire test: Failed to record frame: stream is not active: "
<< error;
}
}
struct pw_buffer* buffer = pw_stream_dequeue_buffer(pw_stream_);
if (!buffer) {
RTC_LOG(LS_ERROR) << "PipeWire test: No available buffer";
return;
}
struct spa_buffer* spa_buffer = buffer->buffer;
struct spa_data* spa_data = spa_buffer->datas;
uint8_t* data = static_cast<uint8_t*>(spa_data->data);
if (!data) {
RTC_LOG(LS_ERROR)
<< "PipeWire test: Failed to record frame: invalid buffer data";
pw_stream_queue_buffer(pw_stream_, buffer);
return;
}
const int stride = SPA_ROUND_UP_N(width_ * kBytesPerPixel, 4);
spa_data->chunk->offset = 0;
spa_data->chunk->size = height_ * stride;
spa_data->chunk->stride = stride;
uint32_t color = rgba_color.ToUInt32();
for (uint32_t i = 0; i < height_; i++) {
uint32_t* column = reinterpret_cast<uint32_t*>(data);
for (uint32_t j = 0; j < width_; j++) {
column[j] = color;
}
data += stride;
}
pw_stream_queue_buffer(pw_stream_, buffer);
if (observer_) {
observer_->OnFrameRecorded();
}
}
void FakeScreenCastStream::StartStreaming() {
if (pw_stream_ && pw_node_id_ != 0) {
pw_stream_set_active(pw_stream_, true);
}
}
void FakeScreenCastStream::StopStreaming() {
if (pw_stream_ && pw_node_id_ != 0) {
pw_stream_set_active(pw_stream_, false);
}
}
// static
void FakeScreenCastStream::OnCoreError(void* data,
uint32_t id,
int seq,
int res,
const char* message) {
FakeScreenCastStream* that = static_cast<FakeScreenCastStream*>(data);
RTC_DCHECK(that);
RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire remote error: " << message;
}
// static
void FakeScreenCastStream::OnStreamStateChanged(void* data,
pw_stream_state old_state,
pw_stream_state state,
const char* error_message) {
FakeScreenCastStream* that = static_cast<FakeScreenCastStream*>(data);
RTC_DCHECK(that);
switch (state) {
case PW_STREAM_STATE_ERROR:
RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire stream state error: "
<< error_message;
break;
case PW_STREAM_STATE_PAUSED:
if (that->pw_node_id_ == 0 && that->pw_stream_) {
that->pw_node_id_ = pw_stream_get_node_id(that->pw_stream_);
that->observer_->OnStreamReady(that->pw_node_id_);
} else {
// Stop streaming
that->is_streaming_ = false;
that->observer_->OnStopStreaming();
}
break;
case PW_STREAM_STATE_STREAMING:
// Start streaming
that->is_streaming_ = true;
that->observer_->OnStartStreaming();
break;
case PW_STREAM_STATE_CONNECTING:
break;
case PW_STREAM_STATE_UNCONNECTED:
if (that->is_streaming_) {
// Stop streaming
that->is_streaming_ = false;
that->observer_->OnStopStreaming();
}
break;
}
}
// static
void FakeScreenCastStream::OnStreamParamChanged(void* data,
uint32_t id,
const struct spa_pod* format) {
FakeScreenCastStream* that = static_cast<FakeScreenCastStream*>(data);
RTC_DCHECK(that);
RTC_LOG(LS_INFO) << "PipeWire test: PipeWire stream format changed.";
if (!format || id != SPA_PARAM_Format) {
return;
}
spa_format_video_raw_parse(format, &that->spa_video_format_);
auto stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
uint8_t buffer[1024] = {};
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
// Setup buffers and meta header for new format.
std::vector<const spa_pod*> params;
const int buffer_types = (1 << SPA_DATA_MemFd);
spa_rectangle resolution = SPA_RECTANGLE(that->width_, that->height_);
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_stride,
SPA_POD_Int(stride), SPA_PARAM_BUFFERS_size,
SPA_POD_Int(stride * that->height_), SPA_PARAM_BUFFERS_align,
SPA_POD_Int(16), 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)))));
pw_stream_update_params(that->pw_stream_, params.data(), params.size());
}
// static
void FakeScreenCastStream::OnStreamAddBuffer(void* data, pw_buffer* buffer) {
FakeScreenCastStream* that = static_cast<FakeScreenCastStream*>(data);
RTC_DCHECK(that);
struct spa_data* spa_data = buffer->buffer->datas;
spa_data->mapoffset = 0;
spa_data->flags = SPA_DATA_FLAG_READWRITE;
if (!(spa_data[0].type & (1 << SPA_DATA_MemFd))) {
RTC_LOG(LS_ERROR)
<< "PipeWire test: Client doesn't support memfd buffer data type";
return;
}
const int stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
spa_data->maxsize = stride * that->height_;
spa_data->type = SPA_DATA_MemFd;
spa_data->fd =
memfd_create("pipewire-test-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (spa_data->fd == -1) {
RTC_LOG(LS_ERROR) << "PipeWire test: Can't create memfd";
return;
}
spa_data->mapoffset = 0;
if (ftruncate(spa_data->fd, spa_data->maxsize) < 0) {
RTC_LOG(LS_ERROR) << "PipeWire test: Can't truncate to"
<< spa_data->maxsize;
return;
}
unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1) {
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to add seals";
}
spa_data->data = mmap(nullptr, spa_data->maxsize, PROT_READ | PROT_WRITE,
MAP_SHARED, spa_data->fd, spa_data->mapoffset);
if (spa_data->data == MAP_FAILED) {
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to mmap memory";
} else {
RTC_LOG(LS_INFO) << "PipeWire test: Memfd created successfully: "
<< spa_data->data << spa_data->maxsize;
}
}
// static
void FakeScreenCastStream::OnStreamRemoveBuffer(void* data, pw_buffer* buffer) {
FakeScreenCastStream* that = static_cast<FakeScreenCastStream*>(data);
RTC_DCHECK(that);
struct spa_buffer* spa_buffer = buffer->buffer;
struct spa_data* spa_data = spa_buffer->datas;
if (spa_data && spa_data->type == SPA_DATA_MemFd) {
munmap(spa_data->data, spa_data->maxsize);
close(spa_data->fd);
}
}
uint32_t FakeScreenCastStream::PipeWireNodeId() {
return pw_node_id_;
}
} // namespace webrtc

View File

@ -0,0 +1,92 @@
/*
* 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.
*/
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_FAKE_SCREENCAST_STREAM_H_
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_FAKE_SCREENCAST_STREAM_H_
#include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h>
#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
#include "modules/desktop_capture/rgba_color.h"
#include "rtc_base/random.h"
namespace webrtc {
class FakeScreenCastStream {
public:
class Observer {
public:
virtual void OnFrameRecorded() = 0;
virtual void OnStreamReady(uint32_t stream_node_id) = 0;
virtual void OnStartStreaming() = 0;
virtual void OnStopStreaming() = 0;
protected:
Observer() = default;
virtual ~Observer() = default;
};
explicit FakeScreenCastStream(Observer* observer,
uint32_t width,
uint32_t height);
~FakeScreenCastStream();
uint32_t PipeWireNodeId();
void RecordFrame(RgbaColor rgba_color);
void StartStreaming();
void StopStreaming();
private:
Observer* observer_;
// Resolution parameters.
uint32_t width_ = 0;
uint32_t height_ = 0;
bool is_streaming_ = false;
uint32_t pw_node_id_ = 0;
// 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_;
// event handlers
pw_core_events pw_core_events_ = {};
pw_stream_events pw_stream_events_ = {};
struct spa_video_info_raw spa_video_format_;
// PipeWire callbacks
static void OnCoreError(void* data,
uint32_t id,
int seq,
int res,
const char* message);
static void OnStreamAddBuffer(void* data, pw_buffer* buffer);
static void OnStreamRemoveBuffer(void* data, pw_buffer* buffer);
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);
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_FAKE_SCREENCAST_STREAM_H_

View File

@ -0,0 +1,106 @@
#!/usr/bin/env vpython3
# Copyright (c) 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.
"""
This script is the wrapper that runs the "shared_screencast_screen" test.
"""
import argparse
import json
import os
import subprocess
import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# Get rid of "modules/desktop_capture/linux/wayland/test"
ROOT_DIR = os.path.normpath(
os.path.join(SCRIPT_DIR, os.pardir, os.pardir, os.pardir, os.pardir,
os.pardir))
def _ParseArgs():
parser = argparse.ArgumentParser(
description='Run shared_screencast_screen test.')
parser.add_argument('build_dir',
help='Path to the build directory (e.g. out/Release).')
# Unused args
# We just need to avoid passing these to the test
parser.add_argument(
'--isolated-script-test-perf-output',
default=None,
help='Path to store perf results in histogram proto format.')
parser.add_argument(
'--isolated-script-test-output',
default=None,
help='Path to output JSON file which Chromium infra requires.')
return parser.parse_known_args()
def _GetPipeWireDir():
pipewire_dir = os.path.join(ROOT_DIR, 'third_party', 'pipewire',
'linux-amd64')
if not os.path.isdir(pipewire_dir):
pipewire_dir = None
return pipewire_dir
def _ConfigurePipeWirePaths(path):
library_dir = os.path.join(path, 'lib64')
pipewire_binary_dir = os.path.join(path, 'bin')
pipewire_config_prefix = os.path.join(path, 'share', 'pipewire')
pipewire_module_dir = os.path.join(library_dir, 'pipewire-0.3')
spa_plugin_dir = os.path.join(library_dir, 'spa-0.2')
media_session_config_dir = os.path.join(pipewire_config_prefix,
'media-session.d')
env_vars = os.environ
env_vars['LD_LIBRARY_PATH'] = library_dir
env_vars['PIPEWIRE_CONFIG_PREFIX'] = pipewire_config_prefix
env_vars['PIPEWIRE_MODULE_DIR'] = pipewire_module_dir
env_vars['SPA_PLUGIN_DIR'] = spa_plugin_dir
env_vars['MEDIA_SESSION_CONFIG_DIR'] = media_session_config_dir
env_vars['PIPEWIRE_RUNTIME_DIR'] = '/tmp'
env_vars['PATH'] = env_vars['PATH'] + ':' + pipewire_binary_dir
def main():
args, extra_args = _ParseArgs()
pipewire_dir = _GetPipeWireDir()
if pipewire_dir is None:
return 1
_ConfigurePipeWirePaths(pipewire_dir)
pipewire_process = subprocess.Popen(["pipewire"], stdout=None)
pipewire_media_session_process = subprocess.Popen(["pipewire-media-session"],
stdout=None)
test_command = os.path.join(args.build_dir, 'shared_screencast_stream_test')
pipewire_test_process = subprocess.run([test_command] + extra_args,
stdout=True,
check=False)
return_value = pipewire_test_process.returncode
pipewire_media_session_process.terminate()
pipewire_process.terminate()
if args.isolated_script_test_output:
with open(args.isolated_script_test_output, 'w') as f:
json.dump({"version": 3}, f)
return return_value
if __name__ == '__main__':
sys.exit(main())