diff --git a/case-lib/lib.sh b/case-lib/lib.sh index 074ea313..f63b5a42 100644 --- a/case-lib/lib.sh +++ b/case-lib/lib.sh @@ -253,6 +253,14 @@ func_lib_get_tplg_path() return 0 } +func_lib_check_pa() +{ + pactl stat || { + dloge "pactl stat failed" + return 1 + } +} + # We must not quote SOF_ALSA_OPTS and disable SC2086 below for two reasons: # # 1. We want to support multiple parameters in a single variable, in diff --git a/test-case/check-userspace-cardinfo.sh b/test-case/check-userspace-cardinfo.sh new file mode 100755 index 00000000..428faa18 --- /dev/null +++ b/test-case/check-userspace-cardinfo.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +## +## Case Name: check-userspace-cardinfo +## Preconditions: +## N/A +## Description: +## Get the card name from pactl info +## check if there is an available card to do the following tests +## Case step: +## 1. run pactl to get the cards info +## 2. check the card name +## Expect result: +## There is at least one available card for test +## + +set -e + +# shellcheck source=case-lib/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh + +# check pulseaudio runs properly or not +func_lib_check_pa || die "Please check whether pulseaudio runs correctly or not" + +func_opt_parse_option "$@" + +: $((available_card=0)) +while read -r card; do + # pactl list cards short format should be like: + # 0 alsa_card.pci-0000_00_1f.3-platform-sof_sdw module-alsa-card.c + dlogi "found card: $(echo "$card" | awk '{print $2}')" + echo "$card" | grep -q -i "usb" || : $((available_card++)) +done < <(pactl list cards short) + +if [ "$available_card" == "0" ]; then + # TODO: do more check to give hint why there is no available card + die "no available card for test" +fi diff --git a/test-case/check-userspace-paplay.sh b/test-case/check-userspace-paplay.sh new file mode 100755 index 00000000..0774c1d5 --- /dev/null +++ b/test-case/check-userspace-paplay.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +## +## Case Name: check-userspace-paplay +## Preconditions: +## N/A +## Description: +## Go through all the sinks and paplay on the sinks whose +## active port is available (jack connected) or unknown (speaker) +## Case step: +## 1. go through all the sinks +## 2. check the sink's active port is available or not +## 3. paplay on the active port which is available or unknown +## Expect result: +## paplay on the sinks successfully +## + +set -e + +# shellcheck source=case-lib/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh + +# check pulseaudio runs properly or not +func_lib_check_pa || die "Please check whether pulseaudio runs correctly or not" + +OPT_OPT_lst['r']='round' OPT_DESC_lst['r']='round count' +OPT_PARM_lst['r']=1 OPT_VALUE_lst['r']=3 + +OPT_OPT_lst['d']='duration' OPT_DESC_lst['d']='paplay duration in second' +OPT_PARM_lst['d']=1 OPT_VALUE_lst['d']=8 + +OPT_OPT_lst['f']='file' OPT_DESC_lst['f']='source file path' +OPT_PARM_lst['f']=1 OPT_VALUE_lst['f']='/dev/zero' + +OPT_OPT_lst['F']='format' OPT_DESC_lst['F']='sample format' +OPT_PARM_lst['F']=1 OPT_VALUE_lst['F']=s16le + +OPT_OPT_lst['R']='rate' OPT_DESC_lst['R']='sample rate' +OPT_PARM_lst['R']=1 OPT_VALUE_lst['R']=44100 + +OPT_OPT_lst['C']='channels' OPT_DESC_lst['C']='channels' +OPT_PARM_lst['C']=1 OPT_VALUE_lst['C']=2 + +func_opt_parse_option "$@" + +round_cnt=${OPT_VALUE_lst['r']} +duration=${OPT_VALUE_lst['d']} +file=${OPT_VALUE_lst['f']} +format=${OPT_VALUE_lst['F']} +rate=${OPT_VALUE_lst['R']} +channel=${OPT_VALUE_lst['C']} + +[[ -e $file ]] || { dlogw "$file does not exist, use /dev/zero as dummy playback source" && file=/dev/zero; } + +# TODO: check the parameter is valid or not + +# go through all the sinks +# get all the sinks name +sinkkeys=$(pactlinfo.py --showsinks) +for round in $(seq 1 $round_cnt); do + for i in $sinkkeys; do + sinkcard=$(pactlinfo.py --getsinkcardname "$i") || { + dlogw "failed to get sink $i card_name" + continue + } + + # Let's skip testing on USB card + # TODO: add a list for other skipped cards such as HDA + if echo "$sinkcard" |grep -q -i "usb"; then + continue + fi + + # get the sink's active port + actport=$(pactlinfo.py --getsinkactport "$i") || { + dlogw "failed to get sink $i active port" + continue + } + + # get the active port's information + actportvalue=$(pactlinfo.py --getsinkportinfo "$actport") || { + dlogw "failed to get sink $i active port $actport info" + continue + } + + # check the active port is available or not from the port's information + portavailable=$(echo "$actportvalue" |grep "not available") || true + if [ -z "$portavailable" ]; then + # now prepare to paplay on this sink as the active port is not "not available" + # get the sink's name + sinkname=$(pactlinfo.py --getsinkname "$i") + sinkname=$(eval echo "$sinkname") + dlogi "===== Testing: (Round: $round/$round_cnt) (sink: $sinkname.$actport) =====" + dlogc "paplay -v --device=$sinkname --raw --rate=$rate --format=$format --channels=$channel $file" + paplay -v --device="$sinkname" --raw --rate=$rate --format=$format --channels=$channel $file & + pid=$! + sleep $duration + # check whether process is still running + count=$(ps -A| grep -c $pid) || true + if [[ $count -eq 0 ]]; then + if wait $pid; then #checks if process executed successfully or not + dlogi "paplay has stopped successfully" + else + die "paplay on $sinkname failed (returned $?)" + fi + else + dlogi "paplay runs successfully" + # kill all paplay process + kill -9 $pid + sleep 0.5 + fi + fi + done +done diff --git a/test-case/check-userspace-parecord.sh b/test-case/check-userspace-parecord.sh new file mode 100755 index 00000000..640de1db --- /dev/null +++ b/test-case/check-userspace-parecord.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +## +## Case Name: check-userspace-parecord +## Preconditions: +## N/A +## Description: +## Go through all the sources and parecord on the sources whose +## active port is available (jack connected) or unknown (DMIC) +## Case step: +## 1. go through all the sources +## 2. check the source's active port is available or not +## 3. parecord on the active port which is available or unknown +## Expect result: +## parecord on the source successfully +## + +set -e + +# shellcheck source=case-lib/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh + +# check pulseaudio runs properly or not +func_lib_check_pa || die "Please check whether pulseaudio runs correctly or not" + +OPT_OPT_lst['r']='round' OPT_DESC_lst['r']='round count' +OPT_PARM_lst['r']=1 OPT_VALUE_lst['r']=3 + +OPT_OPT_lst['d']='duration' OPT_DESC_lst['d']='parecord duration in second' +OPT_PARM_lst['d']=1 OPT_VALUE_lst['d']=8 + +OPT_OPT_lst['f']='file' OPT_DESC_lst['f']='source file path' +OPT_PARM_lst['f']=1 OPT_VALUE_lst['f']='/dev/zero' + +OPT_OPT_lst['F']='format' OPT_DESC_lst['F']='sample format' +OPT_PARM_lst['F']=1 OPT_VALUE_lst['F']=s16le + +OPT_OPT_lst['R']='rate' OPT_DESC_lst['R']='sample rate' +OPT_PARM_lst['R']=1 OPT_VALUE_lst['R']=44100 + +OPT_OPT_lst['C']='channels' OPT_DESC_lst['C']='channels' +OPT_PARM_lst['C']=1 OPT_VALUE_lst['C']=2 + +func_opt_parse_option "$@" + +round_cnt=${OPT_VALUE_lst['r']} +duration=${OPT_VALUE_lst['d']} +file=${OPT_VALUE_lst['f']} +format=${OPT_VALUE_lst['F']} +rate=${OPT_VALUE_lst['R']} +channel=${OPT_VALUE_lst['C']} + +[[ -e $file ]] || { dlogw "$file does not exist, use /dev/zero as dummy playback source" && file=/dev/null; } + +# TODO: check the parameter is valid or not + +# go through all the sources +# get all the sources name +sourcekeys=$(pactlinfo.py --showsources) +for round in $(seq 1 $round_cnt); do + for i in $sourcekeys; do + sourcecard=$(pactlinfo.py --getsourcecardname "$i") || { + dlogw "failed to get source $i card_name" + continue + } + + # Let's skip testing on USB card + # TODO: add a list for other skipped cards such as HDA + if echo "$sourcecard" |grep -q -i "usb"; then + continue + fi + + sourceclass=$(pactlinfo.py --getsourcedeviceclass "$i") || { + dlogw "failed to get source $i device class" + continue + } + # Let's skip the monitor sources + if echo "$sourceclass" |grep "monitor" &>/dev/null; then + continue + fi + + # get the source's active port + actport=$(pactlinfo.py --getsourceactport "$i") || { + dlogw "failed to get source $i active port" + continue + } + # get the active port's information + actportvalue=$(pactlinfo.py --getsourceportinfo "$actport") || { + dlogw "failed to get source $i active port $actport info" + continue + } + # check the active port is available or not from the port's information + portavailable=$(echo "$actportvalue" |grep "not available") || true + if [ -z "$portavailable" ]; then + # now prepare to parecord on this source as the active port is not "not available" + # get the source's name + sourcename=$(pactlinfo.py --getsourcename "$i") + sourcename=$(eval echo "$sourcename") + dlogi "===== Testing: (Round: $round/$round_cnt) (source: $sourcename.$actport) =====" + dlogc "parecord -v --device=$sourcename --raw --rate=$rate --format=$format --channels=$channel $file" + parecord -v --device="$sourcename" --raw --rate=$rate --format=$format --channels=$channel $file & + pid=$! + sleep $duration + # check whether process is still running + count=$(ps -A| grep -c $pid) || true + if [[ $count -eq 0 ]]; then + if wait $pid; then #checks if process executed successfully or not + die "parecord has stopped successfully, which is abnormal" + else + die "parecord on $sourcename failed (returned $?)" + fi + else + dlogi "parecord runs successfully" + # kill all parecord processes + kill -9 $pid + sleep 0.5 + fi + fi + + done +done diff --git a/tools/pactlinfo.py b/tools/pactlinfo.py new file mode 100755 index 00000000..b6573230 --- /dev/null +++ b/tools/pactlinfo.py @@ -0,0 +1,170 @@ +#!/usr/bin/python3 + +''' This is a helper tool to analysis pactl cmd ''' + +import re +import os +import sys +import argparse + +# get the specified sink information from "pactl list sink" +def get_sink(sinkname): + ''' extract the appointed sink information ''' + _sinks = os.popen("pactl list sinks").read() + _sink = "" + start = 0 + for sinkline in _sinks.splitlines(): + # sink information starts with "Sink #n" + mod = re.match(r'^Sink #(\d+)', sinkline) + if mod: + modst = re.match('^Sink #'+sinkname, sinkline) + if modst: + start = 1 + else: + start = 0 + else: + if start == 1: + _sink += sinkline+'\n' + return _sink + +# get the specified source information from "pactl list source" +def get_source(sourcename): + ''' extract the appointed source information ''' + _sources = os.popen("pactl list sources").read() + _source = "" + start = 0 + for srcline in _sources.splitlines(): + # source information starts with "Source #n" + mod = re.match(r'^Source #(\d+)', srcline) + if mod: + modst = re.match('^Source #'+sourcename, srcline) + if modst: + start = 1 + else: + start = 0 + else: + if start == 1: + _source += srcline+'\n' + return _source + +# get the value of the key in the bulk +# For example, the bulk is the information of a sink, which +# includes "State", "Name", "Driver" and etc information. +# get_value(bulk, "Name: ") will get the name information. +# The "Name: " is the key word format, please refer 'pactl list' for +# more keys format. +# TODO: some informations are splitted into multiple lines. Need to handle +# this situation if necessary. +def get_value(bulk, key): + ''' get the value of the "key" from the bulk info ''' + # refine the key to handle the special characters + schar = r'\[@_!#$%^&*()<>?/|}{~]' + for char in schar: + key = key.replace(char, "\\"+char) + + res = re.findall(key+".*", bulk) + if res: + print(re.match("("+key+")(.*)", res[0]).groups(1)[1]) + return 0 + + return 1 + +if __name__ == '__main__': + PARSER = argparse.ArgumentParser(description='parse pactl list information', + add_help=True, formatter_class=argparse.RawTextHelpFormatter) + PARSER.add_argument('--showsinks', + action='store_true', default=False, help='show all sinks') + PARSER.add_argument('--showsources', + action='store_true', default=False, help='show all sources') + PARSER.add_argument('--getsinkname', type=int, + help='get the appointed sink Name') + PARSER.add_argument('--getsourcename', type=int, + help='get the appointed source Name') + PARSER.add_argument('--getsinkcardname', type=int, + help='get the card_name of appointed sink') + PARSER.add_argument('--getsourcecardname', type=int, + help='get the card_name of appointed source') + PARSER.add_argument('--getsinkdeviceclass', type=int, + help='get the appointed sink device.class') + PARSER.add_argument('--getsourcedeviceclass', type=int, + help='get the appointed source device.class') + PARSER.add_argument('--getsinkactport', type=int, + help='get the appointed sink active port name') + PARSER.add_argument('--getsourceactport', type=int, + help='get the appointed source active port name') + PARSER.add_argument('--getsinkportinfo', + help='get the appointed sink port information') + PARSER.add_argument('--getsourceportinfo', + help='get the appointed source port information') + PARSER.add_argument('--version', action='version', version='%(prog)s 1.0') + + ret_args = PARSER.parse_args() + + if ret_args.showsinks is True: + SINKS = os.popen("pactl list short sinks").read() + for line in SINKS.splitlines(): + print(re.match(r"(\d+)(.*)", line).groups(1)[0]) + sys.exit(0) + + if ret_args.showsources is True: + SOURCES = os.popen("pactl list short sources").read() + for line in SOURCES.splitlines(): + print(re.match(r"(\d+)(.*)", line).groups(1)[0]) + sys.exit(0) + + if ret_args.getsinkname is not None: + SINK = ret_args.getsinkname + SINKINFO = get_sink(str(SINK)) + sys.exit(get_value(SINKINFO, "Name: ")) + + if ret_args.getsourcename is not None: + SOURCE = ret_args.getsourcename + SOURCEINFO = get_source(str(SOURCE)) + sys.exit(get_value(SOURCEINFO, "Name: ")) + + if ret_args.getsinkcardname is not None: + SINK = ret_args.getsinkcardname + SINKINFO = get_sink(str(SINK)) + sys.exit(get_value(SINKINFO, "alsa.card_name = ")) + + if ret_args.getsourcecardname is not None: + SOURCE = ret_args.getsourcecardname + SOURCEINFO = get_source(str(SOURCE)) + sys.exit(get_value(SOURCEINFO, "alsa.card_name = ")) + + if ret_args.getsinkdeviceclass is not None: + SINK = ret_args.getsinkdeviceclass + SINKINFO = get_sink(str(SINK)) + sys.exit(get_value(SINKINFO, "device.class = ")) + + if ret_args.getsourcedeviceclass is not None: + SOURCE = ret_args.getsourcedeviceclass + SOURCEINFO = get_source(str(SOURCE)) + sys.exit(get_value(SOURCEINFO, "device.class = ")) + + if ret_args.getsinkactport is not None: + SINK = ret_args.getsinkactport + SINKINFO = get_sink(str(SINK)) + sys.exit(get_value(SINKINFO, "Active Port: ")) + + if ret_args.getsourceactport is not None: + SOURCE = ret_args.getsourceactport + SOURCEINFO = get_source(str(SOURCE)) + sys.exit(get_value(SOURCEINFO, "Active Port: ")) + + if ret_args.getsinkportinfo is not None: + PORT = ret_args.getsinkportinfo + # get all the sinks + # TODO: skip the sink ports which belong to unsupported card + SINKINFO = get_sink(".*") + sys.exit(get_value(SINKINFO, PORT+": ")) + + + if ret_args.getsourceportinfo is not None: + PORT = ret_args.getsourceportinfo + # get all the sources + # TODO: skip the source ports which belong to unsupported card + SOURCEINFO = get_source(".*") + sys.exit(get_value(SOURCEINFO, PORT+": ")) + + sys.exit(0)