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:
parent
aafcc43440
commit
1264dc165b
3
BUILD.gn
3
BUILD.gn
@ -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
15
DEPS
@ -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 ===
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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': {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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())
|
||||
Loading…
x
Reference in New Issue
Block a user