Skip to content

Commit 03e45f2

Browse files
committed
test: add option to test with pipewire
Add option to use pipewire instead of ALSA direct mode in tests. Currently works only with check-performance.sh - runs aplay/arecord on each type of pipewire sink/source. Added pipewire-wrapper.sh to run with SOF CI. Signed-off-by: Emilia Kurdybelska <emiliax.kurdybelska@intel.com>
1 parent 98c37cb commit 03e45f2

File tree

5 files changed

+220
-52
lines changed

5 files changed

+220
-52
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Some tests support these environment variables (work in progress):
6464
- SOF_ALSA_OPTS contains optional parameters passed on both play and record.
6565
- SOF_APLAY_OPTS and SOF_ARECORD_OPTS contain optional parameters passed additionally on play and record respectively.
6666
These options are applied to the selected tool (alsa or tinyalsa) based on the value of SOF_ALSA_TOOL
67+
- SOF_TEST_PIPEWIRE (default: false) allows to use pipewire instead of ALSA direct mode. Supported by a limited number of tests,
68+
try 'git grep SOF_TEST_PIPEWIRE' to find them.
6769

6870
Warning, these environment variables do NOT support parameters
6971
with whitespace or globbing characters, in other words this does NOT

case-lib/hijack.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ function func_exit_handler()
1212

1313
dlogi "Starting func_exit_handler($exit_status)"
1414

15+
if [ "$SOF_TEST_PIPEWIRE" == true ]; then
16+
func_lib_disable_pipewire
17+
fi
18+
1519
# call trace
1620
if [ "$exit_status" -ne 0 ] ; then
1721
dloge "Starting ${FUNCNAME[0]}(), exit status=$exit_status, FUNCNAME stack:"

case-lib/lib.sh

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ minvalue() { printf '%d' $(( "$1" < "$2" ? "$1" : "$2" )); }
8383
#
8484
start_test()
8585
{
86+
if [ "$SOF_TEST_PIPEWIRE" == true ]; then
87+
func_lib_enable_pipewire
88+
fi
89+
8690
if is_subtest; then
8791
return 0
8892
fi
@@ -571,6 +575,40 @@ func_lib_check_sudo()
571575
}
572576
}
573577

578+
func_lib_enable_pipewire()
579+
{
580+
dlogi "Starting Pipewire..."
581+
582+
sudo systemctl --global unmask pipewire{,-pulse}.{socket,service}
583+
systemctl --user unmask pipewire{,-pulse}.{socket,service}
584+
sudo systemctl --global unmask wireplumber.service
585+
systemctl --user unmask wireplumber.service
586+
587+
systemctl --user start pipewire{,-pulse}.{socket,service}
588+
systemctl --user start wireplumber.service
589+
590+
systemctl --user daemon-reload
591+
592+
systemctl is-active --user --quiet pipewire{,-pulse}.{socket,service} && dlogi "Pipewire started"
593+
systemctl is-active --user --quiet wireplumber.service && dlogi "Wireplumber started"
594+
}
595+
596+
func_lib_disable_pipewire()
597+
{
598+
dlogi "Stopping Pipewire..."
599+
600+
systemctl --user stop wireplumber.service
601+
systemctl --user stop pipewire{,-pulse}.{socket,service}
602+
603+
sudo systemctl --global mask wireplumber.service
604+
sudo systemctl --global mask pipewire{,-pulse}.{socket,service}
605+
606+
# shellcheck disable=SC2015
607+
systemctl is-active --user --quiet wireplumber.service && dlogi "Wireplumber not stopped" || dlogi "Wireplumber stopped"
608+
# shellcheck disable=SC2015
609+
systemctl is-active --user --quiet pipewire{,-pulse}.{socket,service} && dlogi "Pipewire not stopped" || dlogi "Pipewire stopped"
610+
}
611+
574612
systemctl_show_pulseaudio()
575613
{
576614
printf '\n'
@@ -908,9 +946,15 @@ aplay_opts()
908946
# shellcheck disable=SC2086
909947
tinyplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS -D "$card_nr" -d "$dev_nr" -i wav noise.wav
910948
elif [[ "$SOF_ALSA_TOOL" = "alsa" ]]; then
911-
dlogc "aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*"
912-
# shellcheck disable=SC2086
913-
aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
949+
if [[ "$SOF_TEST_PIPEWIRE" == true ]]; then
950+
dlogc "timeout -k $duration $duration aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*" # option -d doesn't work with pipewire so we need timeout
951+
# shellcheck disable=SC2086
952+
timeout -k "$duration" "$duration" aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
953+
else
954+
dlogc "aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*"
955+
# shellcheck disable=SC2086
956+
aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
957+
fi
914958
else
915959
die "Unknown ALSA tool: ${SOF_ALSA_TOOL}"
916960
fi
@@ -927,14 +971,38 @@ arecord_opts()
927971
# shellcheck disable=SC2086
928972
tinycap $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$file" -D "$card_nr" -d "$dev_nr" -c "$channel" -t "$duration" -r "$rate" -b "$format"
929973
elif [[ "$SOF_ALSA_TOOL" = "alsa" ]]; then
930-
dlogc "arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*"
931-
# shellcheck disable=SC2086
932-
arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
974+
if [[ "$SOF_TEST_PIPEWIRE" == true ]]; then
975+
dlogc "timeout -k $duration $duration arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*" # option -d doesn't work with pipewire so we need timeout
976+
# shellcheck disable=SC2086
977+
timeout -k "$duration" "$duration" arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
978+
else
979+
dlogc "arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*"
980+
# shellcheck disable=SC2086
981+
arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
982+
fi
933983
else
934984
die "Unknown ALSA tool: ${SOF_ALSA_TOOL}"
935985
fi
936986
}
937987

988+
# Get the ID of the first sink/source of a given type, e.g. "Speaker" or "Headphones". Print an empty line if ID not found.
989+
get_id_of_pipewire_endpoint()
990+
{
991+
# $ wpctl status returns list of all endpoints managed by wireplumber. We filter by given sink/source type, which returns something like this:
992+
# │ * 48. sof-soundwire Headphones [vol: 0.40] (or without the * when it's not the current default)
993+
# We filter out everything but ID, and only take the first line of the output (if there's more that one object of that type we ignore the rest)
994+
995+
local object_name="$1"
996+
object_id=$(wpctl status | awk -v name="$object_name" 'tolower($0) ~ tolower(name) { sub(/\*/,""); sub(/\./,"",$2); print $2; exit }')
997+
998+
# Check if object_id is a number
999+
re='^[0-9]+$'
1000+
if [[ "$object_id" =~ $re ]] ; then
1001+
printf '%s' "$object_id"
1002+
fi
1003+
1004+
}
1005+
9381006
die()
9391007
{
9401008
dloge "$@"

test-case/check-performance.sh

Lines changed: 109 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020

2121
set -e
2222

23+
PIPEWIRE_OUTPUTS=("Speaker" "Headphones" "HDMI" "Stereo")
24+
PIPEWIRE_INPUTS=("Digital Microphone" "DMIC" "Headset Microphone" "SoundWire microphone" "Stereo")
25+
2326
# It is pointless to perf component in HDMI pipeline, so filter out HDMI pipelines
2427
# shellcheck disable=SC2034
2528
NO_HDMI_MODE=true
@@ -41,52 +44,112 @@ func_opt_parse_option "$@"
4144
tplg=${OPT_VAL['t']}
4245
duration=${OPT_VAL['d']}
4346

44-
start_test
45-
logger_disabled || func_lib_start_log_collect
46-
47-
setup_kernel_check_point
48-
func_lib_check_sudo
49-
func_pipeline_export "$tplg" "type:any"
50-
51-
aplay_num=0
52-
arecord_num=0
53-
54-
for idx in $(seq 0 $((PIPELINE_COUNT - 1)))
55-
do
56-
channel=$(func_pipeline_parse_value "$idx" channel)
57-
rate=$(func_pipeline_parse_value "$idx" rate)
58-
dev=$(func_pipeline_parse_value "$idx" dev)
59-
pcm=$(func_pipeline_parse_value "$idx" pcm)
60-
type=$(func_pipeline_parse_value "$idx" type)
61-
62-
# Currently, copier will convert bit depth to S32_LE despite what bit depth
63-
# is used in aplay, so make S32_LE as base bit depth for performance analysis.
64-
fmt=S32_LE
65-
66-
dlogi "Running (PCM: $pcm [$dev]<$type>) in background"
67-
if [ "$type" == "playback" ]; then
68-
aplay_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/zero -q &
69-
aplay_num=$((aplay_num+1))
70-
else
71-
arecord_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/null -q &
72-
arecord_num=$((arecord_num+1))
73-
fi
74-
done
75-
76-
sleep 1 # waiting stable streaming of aplay/arecord
77-
dlogi "Number of aplay/arecord process started: $aplay_num, $arecord_num"
7847

79-
real_aplay_num=$(ps --no-headers -C aplay | wc -l)
80-
real_arecord_num=$(ps --no-headers -C arecord | wc -l)
81-
if [ "$real_aplay_num" != "$aplay_num" ] || [ "$real_arecord_num" != "$arecord_num" ];
82-
then
83-
dlogi "Number of aplay/arecord process running: $real_aplay_num, $real_arecord_num"
84-
die "aplay/arecord process exit unexpectedly"
85-
fi
48+
# run aplay for all sink types given as a parameter and save the number of started aplay processes
49+
run_aplays()
50+
{
51+
local sinks=("$@")
8652

87-
dlogi "Waiting for aplay/arecord process to exit"
88-
sleep $((duration + 2))
53+
for sink_type in "${sinks[@]}"
54+
do
55+
sink_id=$(get_id_of_pipewire_endpoint "$sink_type")
56+
if [ -z "$sink_id" ]; then
57+
dlogi "No $sink_type found, skipping to the next one"
58+
continue
59+
fi
60+
dlogi "Setting default sink to $sink_id: $sink_type"
61+
wpctl set-default "$sink_id"
62+
aplay_opts -D pipewire /dev/zero -q &
63+
aplay_num=$((aplay_num+1))
64+
done
65+
}
66+
67+
# run arecord for all source types given as a parameter and save the number of started arecord processes
68+
run_arecords()
69+
{
70+
local sources=("$@")
71+
72+
for source_type in "${sources[@]}"
73+
do
74+
source_id=$(get_id_of_pipewire_endpoint "$source_type")
75+
if [ -z "$source_id" ]; then
76+
dlogi "No $source_type found, skipping to the next one"
77+
continue
78+
fi
79+
dlogi "Setting default source to $source_id: $source_type"
80+
wpctl set-default "$source_id"
81+
arecord_opts -D pipewire /dev/null -q &
82+
arecord_num=$((arecord_num+1))
83+
done
84+
}
85+
86+
main()
87+
{
88+
start_test
89+
logger_disabled || func_lib_start_log_collect
90+
91+
setup_kernel_check_point
92+
func_lib_check_sudo
93+
func_pipeline_export "$tplg" "type:any"
94+
95+
aplay_num=0
96+
arecord_num=0
97+
98+
if [ "$SOF_TEST_PIPEWIRE" == true ]; then
99+
100+
dlogi "Running aplays"
101+
run_aplays "${PIPEWIRE_OUTPUTS[@]}"
102+
dlogi "Running arecords"
103+
run_arecords "${PIPEWIRE_INPUTS[@]}"
104+
105+
if [ "$aplay_num" == 0 ] && [ "$arecord_num" == 0 ]; then
106+
skip_test "No sinks/sources to be tested found, skipping test"
107+
fi
89108

90-
# Enable performance analysis
91-
# shellcheck disable=SC2034
92-
DO_PERF_ANALYSIS=1
109+
else
110+
111+
for idx in $(seq 0 $((PIPELINE_COUNT - 1)))
112+
do
113+
channel=$(func_pipeline_parse_value "$idx" channel)
114+
rate=$(func_pipeline_parse_value "$idx" rate)
115+
dev=$(func_pipeline_parse_value "$idx" dev)
116+
pcm=$(func_pipeline_parse_value "$idx" pcm)
117+
type=$(func_pipeline_parse_value "$idx" type)
118+
119+
# Currently, copier will convert bit depth to S32_LE despite what bit depth
120+
# is used in aplay, so make S32_LE as base bit depth for performance analysis.
121+
fmt=S32_LE
122+
123+
dlogi "Running (PCM: $pcm [$dev]<$type>) in background"
124+
if [ "$type" == "playback" ]; then
125+
aplay_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/zero -q &
126+
aplay_num=$((aplay_num+1))
127+
else
128+
arecord_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/null -q &
129+
arecord_num=$((arecord_num+1))
130+
fi
131+
done
132+
fi
133+
134+
sleep 1 # waiting stable streaming of aplay/arecord
135+
dlogi "Number of aplay/arecord process started: $aplay_num, $arecord_num"
136+
137+
real_aplay_num=$(ps --no-headers -C aplay | wc -l)
138+
real_arecord_num=$(ps --no-headers -C arecord | wc -l)
139+
if [ "$real_aplay_num" != "$aplay_num" ] || [ "$real_arecord_num" != "$arecord_num" ];
140+
then
141+
dlogi "Number of aplay/arecord process running: $real_aplay_num, $real_arecord_num"
142+
die "aplay/arecord process exit unexpectedly"
143+
fi
144+
145+
dlogi "Waiting for aplay/arecord process to exit"
146+
sleep $((duration + 2))
147+
148+
# Enable performance analysis
149+
# shellcheck disable=SC2034
150+
DO_PERF_ANALYSIS=1
151+
}
152+
153+
{
154+
main "$@"; exit "$?"
155+
}

test-case/pipewire-wrapper.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
##
4+
## Case Name: Wrapper to run a test case given with Pipewire in setup that cannot set the environment variable.
5+
## Keep this script as simple as possible and avoid additional layers of indirections when possible.
6+
## Preconditions:
7+
## Pipewire and Wireplumber are installed.
8+
## Description:
9+
## This script serves as a wrapper to execute a test case script using Pipewire.
10+
## It expects the test case script file name (without path) as the first parameter,
11+
## followed by other parameters required for that test case.
12+
## Case step:
13+
## 1. SOF_TEST_PIPEWIRE environment variable is set to true.
14+
## 2. The test case script is executed.
15+
## Expected result:
16+
## The test case script is executed using Pipewire.
17+
18+
set -e
19+
20+
# Ensure the test case script file name is provided
21+
if [ -z "$1" ]; then
22+
echo "Error: No test case script file name provided. Exiting..."
23+
exit 1
24+
fi
25+
26+
export SOF_TEST_PIPEWIRE=true
27+
28+
TESTDIR=$(realpath -e "$(dirname "${BASH_SOURCE[0]}")/..")
29+
30+
# shellcheck disable=SC2145
31+
[ -x "$TESTDIR/test-case/$(basename "$1")" ] && exec "$TESTDIR"/test-case/"$@"

0 commit comments

Comments
 (0)