Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Some tests support these environment variables (work in progress):
- SOF_ALSA_OPTS contains optional parameters passed on both play and record.
- SOF_APLAY_OPTS and SOF_ARECORD_OPTS contain optional parameters passed additionally on play and record respectively.
These options are applied to the selected tool (alsa or tinyalsa) based on the value of SOF_ALSA_TOOL
- SOF_TEST_PIPEWIRE (default: false) allows to use pipewire instead of ALSA direct mode. Supported by a limited number of tests,
try 'git grep SOF_TEST_PIPEWIRE' to find them.

Warning, these environment variables do NOT support parameters
with whitespace or globbing characters, in other words this does NOT
Expand Down
2 changes: 2 additions & 0 deletions case-lib/hijack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ function func_exit_handler()

dlogi "Starting func_exit_handler($exit_status)"

func_lib_check_and_disable_pipewire

# call trace
if [ "$exit_status" -ne 0 ] ; then
dloge "Starting ${FUNCNAME[0]}(), exit status=$exit_status, FUNCNAME stack:"
Expand Down
85 changes: 79 additions & 6 deletions case-lib/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ minvalue() { printf '%d' $(( "$1" < "$2" ? "$1" : "$2" )); }
#
start_test()
{
if [ "$SOF_TEST_PIPEWIRE" == true ]; then
func_lib_enable_pipewire
fi

if is_subtest; then
return 0
fi
Expand Down Expand Up @@ -571,6 +575,45 @@ func_lib_check_sudo()
}
}

func_lib_enable_pipewire()
{
dlogi "Starting Pipewire..."

sudo systemctl --global unmask pipewire{,-pulse}.{socket,service}
systemctl --user unmask pipewire{,-pulse}.{socket,service}
sudo systemctl --global unmask wireplumber.service
systemctl --user unmask wireplumber.service

systemctl --user start pipewire{,-pulse}.{socket,service}
systemctl --user start wireplumber.service

systemctl --user daemon-reload

systemctl is-active --user --quiet pipewire{,-pulse}.{socket,service} && dlogi "Pipewire started"
systemctl is-active --user --quiet wireplumber.service && dlogi "Wireplumber started"
}

func_lib_check_and_disable_pipewire()
{
dlogi "Check if pipewire is running"

if systemctl is-active --user --quiet pipewire{,-pulse}.{socket,service}; then func_lib_disable_pipewire; else dlogi "Pipewire not running"; fi
}

func_lib_disable_pipewire()
{
dlogi "Stopping Pipewire..."

systemctl --user stop wireplumber.service
systemctl --user stop pipewire{,-pulse}.{socket,service}

sudo systemctl --global mask wireplumber.service
sudo systemctl --global mask pipewire{,-pulse}.{socket,service}

if systemctl is-active --user --quiet wireplumber.service; then dlogi "Wireplumber not stopped"; else dlogi "Wireplumber stopped"; fi
if systemctl is-active --user --quiet pipewire{,-pulse}.{socket,service}; then dlogi "Pipewire not stopped"; else dlogi "Pipewire stopped"; fi
}

systemctl_show_pulseaudio()
{
printf '\n'
Expand Down Expand Up @@ -908,9 +951,15 @@ aplay_opts()
# shellcheck disable=SC2086
tinyplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS -D "$card_nr" -d "$dev_nr" -i wav noise.wav
elif [[ "$SOF_ALSA_TOOL" = "alsa" ]]; then
dlogc "aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*"
# shellcheck disable=SC2086
aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
if [[ "$SOF_TEST_PIPEWIRE" == true ]]; then
dlogc "timeout -k $duration $duration aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*" # option -d doesn't work with pipewire so we need timeout
# shellcheck disable=SC2086
timeout -k "$duration" "$duration" aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
else
dlogc "aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS $*"
# shellcheck disable=SC2086
aplay $SOF_ALSA_OPTS $SOF_APLAY_OPTS "$@"
fi
else
die "Unknown ALSA tool: ${SOF_ALSA_TOOL}"
fi
Expand All @@ -927,14 +976,38 @@ arecord_opts()
# shellcheck disable=SC2086
tinycap $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$file" -D "$card_nr" -d "$dev_nr" -c "$channel" -t "$duration" -r "$rate" -b "$format"
elif [[ "$SOF_ALSA_TOOL" = "alsa" ]]; then
dlogc "arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*"
# shellcheck disable=SC2086
arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
if [[ "$SOF_TEST_PIPEWIRE" == true ]]; then
dlogc "timeout -k $duration $duration arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*" # option -d doesn't work with pipewire so we need timeout
# shellcheck disable=SC2086
timeout -k "$duration" "$duration" arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
else
dlogc "arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS $*"
# shellcheck disable=SC2086
arecord $SOF_ALSA_OPTS $SOF_ARECORD_OPTS "$@"
fi
else
die "Unknown ALSA tool: ${SOF_ALSA_TOOL}"
fi
}

# 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.
get_id_of_pipewire_endpoint()
{
# $ wpctl status returns list of all endpoints managed by wireplumber. We filter by given sink/source type, which returns something like this:
# │ * 48. sof-soundwire Headphones [vol: 0.40] (or without the * when it's not the current default)
# 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)

local object_name="$1"
object_id=$(wpctl status | awk -v name="$object_name" 'tolower($0) ~ tolower(name) { sub(/\*/,""); sub(/\./,"",$2); print $2; exit }')

# Check if object_id is a number
re='^[0-9]+$'
if [[ "$object_id" =~ $re ]] ; then
printf '%s' "$object_id"
fi

}

die()
{
dloge "$@"
Expand Down
155 changes: 109 additions & 46 deletions test-case/check-performance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

set -e

PIPEWIRE_OUTPUTS=("Speaker" "Headphones" "HDMI" "Stereo")
PIPEWIRE_INPUTS=("Digital Microphone" "DMIC" "Headset Microphone" "SoundWire microphone" "Stereo")

# It is pointless to perf component in HDMI pipeline, so filter out HDMI pipelines
# shellcheck disable=SC2034
NO_HDMI_MODE=true
Expand All @@ -41,52 +44,112 @@ func_opt_parse_option "$@"
tplg=${OPT_VAL['t']}
duration=${OPT_VAL['d']}

start_test
logger_disabled || func_lib_start_log_collect

setup_kernel_check_point
func_lib_check_sudo
func_pipeline_export "$tplg" "type:any"

aplay_num=0
arecord_num=0

for idx in $(seq 0 $((PIPELINE_COUNT - 1)))
do
channel=$(func_pipeline_parse_value "$idx" channel)
rate=$(func_pipeline_parse_value "$idx" rate)
dev=$(func_pipeline_parse_value "$idx" dev)
pcm=$(func_pipeline_parse_value "$idx" pcm)
type=$(func_pipeline_parse_value "$idx" type)

# Currently, copier will convert bit depth to S32_LE despite what bit depth
# is used in aplay, so make S32_LE as base bit depth for performance analysis.
fmt=S32_LE

dlogi "Running (PCM: $pcm [$dev]<$type>) in background"
if [ "$type" == "playback" ]; then
aplay_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/zero -q &
aplay_num=$((aplay_num+1))
else
arecord_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/null -q &
arecord_num=$((arecord_num+1))
fi
done

sleep 1 # waiting stable streaming of aplay/arecord
dlogi "Number of aplay/arecord process started: $aplay_num, $arecord_num"

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

dlogi "Waiting for aplay/arecord process to exit"
sleep $((duration + 2))
for sink_type in "${sinks[@]}"
do
sink_id=$(get_id_of_pipewire_endpoint "$sink_type")
if [ -z "$sink_id" ]; then
dlogi "No $sink_type found, skipping to the next one"
continue
fi
dlogi "Setting default sink to $sink_id: $sink_type"
wpctl set-default "$sink_id"
aplay_opts -D pipewire /dev/zero -q &
aplay_num=$((aplay_num+1))
done
}

# run arecord for all source types given as a parameter and save the number of started arecord processes
run_arecords()
{
local sources=("$@")

for source_type in "${sources[@]}"
do
source_id=$(get_id_of_pipewire_endpoint "$source_type")
if [ -z "$source_id" ]; then
dlogi "No $source_type found, skipping to the next one"
continue
fi
dlogi "Setting default source to $source_id: $source_type"
wpctl set-default "$source_id"
arecord_opts -D pipewire /dev/null -q &
arecord_num=$((arecord_num+1))
done
}

main()
{
start_test
logger_disabled || func_lib_start_log_collect

setup_kernel_check_point
func_lib_check_sudo
func_pipeline_export "$tplg" "type:any"

aplay_num=0
arecord_num=0

if [ "$SOF_TEST_PIPEWIRE" == true ]; then

dlogi "Running aplays"
run_aplays "${PIPEWIRE_OUTPUTS[@]}"
dlogi "Running arecords"
run_arecords "${PIPEWIRE_INPUTS[@]}"

if [ "$aplay_num" == 0 ] && [ "$arecord_num" == 0 ]; then
skip_test "No sinks/sources to be tested found, skipping test"
fi

# Enable performance analysis
# shellcheck disable=SC2034
DO_PERF_ANALYSIS=1
else

for idx in $(seq 0 $((PIPELINE_COUNT - 1)))
do
channel=$(func_pipeline_parse_value "$idx" channel)
rate=$(func_pipeline_parse_value "$idx" rate)
dev=$(func_pipeline_parse_value "$idx" dev)
pcm=$(func_pipeline_parse_value "$idx" pcm)
type=$(func_pipeline_parse_value "$idx" type)

# Currently, copier will convert bit depth to S32_LE despite what bit depth
# is used in aplay, so make S32_LE as base bit depth for performance analysis.
fmt=S32_LE

dlogi "Running (PCM: $pcm [$dev]<$type>) in background"
if [ "$type" == "playback" ]; then
aplay_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/zero -q &
aplay_num=$((aplay_num+1))
else
arecord_opts -D "$dev" -c "$channel" -r "$rate" -f "$fmt" -d "$duration" /dev/null -q &
arecord_num=$((arecord_num+1))
fi
done
fi

sleep 1 # waiting stable streaming of aplay/arecord
dlogi "Number of aplay/arecord process started: $aplay_num, $arecord_num"

real_aplay_num=$(ps --no-headers -C aplay | wc -l)
real_arecord_num=$(ps --no-headers -C arecord | wc -l)
if [ "$real_aplay_num" != "$aplay_num" ] || [ "$real_arecord_num" != "$arecord_num" ];
then
dlogi "Number of aplay/arecord process running: $real_aplay_num, $real_arecord_num"
die "aplay/arecord process exit unexpectedly"
fi

dlogi "Waiting for aplay/arecord process to exit"
sleep $((duration + 2))

# Enable performance analysis
# shellcheck disable=SC2034
DO_PERF_ANALYSIS=1
}

{
main "$@"; exit "$?"
}
31 changes: 31 additions & 0 deletions test-case/pipewire-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

##
## Case Name: Wrapper to run a test case given with Pipewire in setup that cannot set the environment variable.
## Keep this script as simple as possible and avoid additional layers of indirections when possible.
## Preconditions:
## Pipewire and Wireplumber are installed.
## Description:
## This script serves as a wrapper to execute a test case script using Pipewire.
## It expects the test case script file name (without path) as the first parameter,
## followed by other parameters required for that test case.
## Case step:
## 1. SOF_TEST_PIPEWIRE environment variable is set to true.
## 2. The test case script is executed.
## Expected result:
## The test case script is executed using Pipewire.

set -e

# Ensure the test case script file name is provided
if [ -z "$1" ]; then
echo "Error: No test case script file name provided. Exiting..."
exit 1
fi

export SOF_TEST_PIPEWIRE=true

TESTDIR=$(realpath -e "$(dirname "${BASH_SOURCE[0]}")/..")

# shellcheck disable=SC2145
[ -x "$TESTDIR/test-case/$(basename "$1")" ] && exec "$TESTDIR"/test-case/"$@"