Skip to content

Commit f1674ac

Browse files
authored
Merge pull request qualcomm-linux#200 from smuppand/audio
AudioRecord: PipeWire-first recording with smart fallback and capture helpers added
2 parents 7e821eb + 64c575f commit f1674ac

File tree

2 files changed

+273
-44
lines changed

2 files changed

+273
-44
lines changed

Runner/suites/Multimedia/Audio/AudioRecord/run.sh

Lines changed: 207 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ log_info "Args: backend=${AUDIO_BACKEND:-auto} source=$SRC_CHOICE loops=$LOOPS d
140140
if [ -z "$AUDIO_BACKEND" ]; then
141141
AUDIO_BACKEND="$(detect_audio_backend)"
142142
fi
143+
BACKENDS_TO_TRY="$(build_backend_chain)"
144+
# Use it for visibility and to satisfy shellcheck usage
145+
log_info "Backend fallback chain: $BACKENDS_TO_TRY"
143146
if [ -z "$AUDIO_BACKEND" ]; then
144147
log_skip "$TESTNAME SKIP - no audio backend running"
145148
echo "$TESTNAME SKIP" > "$RES_FILE"
@@ -185,31 +188,126 @@ case "$AUDIO_BACKEND:$SRC_CHOICE" in
185188
;;
186189
esac
187190

188-
if [ -z "$SRC_ID" ]; then
189-
log_skip "$TESTNAME SKIP - requested source '$SRC_CHOICE' not found for $AUDIO_BACKEND"
191+
# ---- Dynamic fallback when mic is missing on the chosen backend ----
192+
# Stay on PipeWire even if SRC_ID is empty; pw-record and arecord -D pipewire can use the default source.
193+
if [ -z "$SRC_ID" ] && [ "$SRC_CHOICE" = "mic" ] && [ "$AUDIO_BACKEND" != "pipewire" ]; then
194+
for b in $BACKENDS_TO_TRY; do
195+
[ "$b" = "$AUDIO_BACKEND" ] && continue
196+
case "$b" in
197+
pipewire)
198+
cand="$(pw_default_mic)"
199+
if [ -n "$cand" ]; then
200+
AUDIO_BACKEND="pipewire"; SRC_ID="$cand"
201+
log_info "Falling back to backend: pipewire (source id=$SRC_ID)"
202+
break
203+
fi
204+
;;
205+
pulseaudio)
206+
cand="$(pa_default_mic)"
207+
if [ -n "$cand" ]; then
208+
AUDIO_BACKEND="pulseaudio"; SRC_ID="$cand"
209+
log_info "Falling back to backend: pulseaudio (source=$SRC_ID)"
210+
break
211+
fi
212+
;;
213+
alsa)
214+
cand="$(alsa_pick_capture)"
215+
if [ -n "$cand" ]; then
216+
AUDIO_BACKEND="alsa"; SRC_ID="$cand"
217+
log_info "Falling back to backend: alsa (device=$SRC_ID)"
218+
break
219+
fi
220+
;;
221+
esac
222+
done
223+
fi
224+
225+
# Only skip if no source AND not on PipeWire.
226+
if [ -z "$SRC_ID" ] && [ "$AUDIO_BACKEND" != "pipewire" ]; then
227+
log_skip "$TESTNAME SKIP - requested source '$SRC_CHOICE' not available on any backend ($BACKENDS_TO_TRY)"
190228
echo "$TESTNAME SKIP" > "$RES_FILE"
191229
exit 2
192230
fi
193231

232+
# ---- Normalize ALSA device id (fix "hw:0 1," → "hw:0,1") ----
233+
if [ "$AUDIO_BACKEND" = "alsa" ]; then
234+
case "$SRC_ID" in
235+
hw:*" "*,)
236+
SRC_ID=$(printf '%s' "$SRC_ID" | sed -E 's/^hw:([0-9]+) ([0-9]+),$/hw:\1,\2/')
237+
;;
238+
hw:*" "*)
239+
SRC_ID=$(printf '%s' "$SRC_ID" | sed -E 's/^hw:([0-9]+) ([0-9]+)$/hw:\1,\2/')
240+
;;
241+
esac
242+
fi
243+
244+
# ---- Validate/auto-pick ALSA device if invalid (prevents "hw:,") ----
245+
if [ "$AUDIO_BACKEND" = "alsa" ]; then
246+
case "$SRC_ID" in
247+
hw:[0-9]*,[0-9]*|plughw:[0-9]*,[0-9]*)
248+
: ;;
249+
*)
250+
cand="$(arecord -l 2>/dev/null | sed -n 's/^card[[:space:]]*\([0-9][0-9]*\).*device[[:space:]]*\([0-9][0-9]*\).*/hw:\1,\2/p' | head -n 1)"
251+
if [ -z "$cand" ]; then
252+
cand="$(sed -n 's/^\([0-9][0-9]*\)-\([0-9][0-9]*\):.*capture.*/hw:\1,\2/p' /proc/asound/pcm 2>/dev/null | head -n 1)"
253+
fi
254+
if [ -z "$cand" ]; then
255+
cand="$(sed -n 's/.*\[\s*\([0-9][0-9]*\)-\s*\([0-9][0-9]*\)\]:.*capture.*/hw:\1,\2/p' /proc/asound/devices 2>/dev/null | head -n 1)"
256+
fi
257+
if printf '%s\n' "$cand" | grep -Eq '^hw:[0-9]+,[0-9]+$'; then
258+
SRC_ID="$cand"
259+
log_info "ALSA auto-pick: using $SRC_ID"
260+
else
261+
log_skip "$TESTNAME SKIP - no valid ALSA capture device found"
262+
echo "$TESTNAME SKIP" > "$RES_FILE"
263+
exit 2
264+
fi
265+
;;
266+
esac
267+
fi
268+
269+
# ---- Routing log / defaults per backend ----
194270
if [ "$AUDIO_BACKEND" = "pipewire" ]; then
195-
SRC_LABEL="$(pw_source_label_safe "$SRC_ID")"
196-
wpctl set-default "$SRC_ID" >/dev/null 2>&1 || true
197-
if [ -z "$SRC_LABEL" ]; then
198-
SRC_LABEL="unknown"
271+
if [ -n "$SRC_ID" ]; then
272+
SRC_LABEL="$(pw_source_label_safe "$SRC_ID")"
273+
wpctl set-default "$SRC_ID" >/dev/null 2>&1 || true
274+
[ -z "$SRC_LABEL" ] && SRC_LABEL="unknown"
275+
log_info "Routing to source: id/name=$SRC_ID label='$SRC_LABEL' choice=$SRC_CHOICE"
276+
else
277+
SRC_LABEL="default"
278+
log_info "Routing to source: id/name=default label='default' choice=$SRC_CHOICE"
199279
fi
200-
log_info "Routing to source: id/name=$SRC_ID label='$SRC_LABEL' choice=$SRC_CHOICE"
201-
else
280+
elif [ "$AUDIO_BACKEND" = "pulseaudio" ]; then
202281
SRC_LABEL="$(pa_source_name "$SRC_ID" 2>/dev/null || echo "$SRC_ID")"
203282
pa_set_default_source "$SRC_ID" >/dev/null 2>&1 || true
204283
log_info "Routing to source: name='$SRC_LABEL' choice=$SRC_CHOICE"
284+
else # ALSA
285+
SRC_LABEL="$SRC_ID"
286+
log_info "Routing to source: name='$SRC_LABEL' choice=$SRC_CHOICE"
205287
fi
206288

289+
# If fallback changed backend, ensure deps are present (non-fatal → SKIP)
290+
case "$AUDIO_BACKEND" in
291+
pipewire)
292+
if ! check_dependencies wpctl pw-record; then
293+
log_skip "$TESTNAME SKIP - missing PipeWire utils"
294+
echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2
295+
fi ;;
296+
pulseaudio)
297+
if ! check_dependencies pactl parecord; then
298+
log_skip "$TESTNAME SKIP - missing PulseAudio utils"
299+
echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2
300+
fi ;;
301+
alsa)
302+
if ! check_dependencies arecord; then
303+
log_skip "$TESTNAME SKIP - missing arecord"
304+
echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2
305+
fi ;;
306+
esac
307+
207308
# Watchdog info
208309
dur_s="$(duration_to_secs "$TIMEOUT" 2>/dev/null || echo 0)"
209-
if [ -z "$dur_s" ]; then
210-
dur_s=0
211-
fi
212-
310+
[ -z "$dur_s" ] && dur_s=0
213311
if [ "$dur_s" -gt 0 ] 2>/dev/null; then
214312
log_info "Watchdog/timeout: ${TIMEOUT}"
215313
else
@@ -240,12 +338,8 @@ append_junit() {
240338
{
241339
printf ' <testcase classname="%s" name="%s" time="%s">\n' "Audio.Record" "$name" "$elapsed"
242340
case "$status" in
243-
PASS)
244-
:
245-
;;
246-
SKIP)
247-
printf ' <skipped/>\n'
248-
;;
341+
PASS) : ;;
342+
SKIP) printf ' <skipped/>\n' ;;
249343
FAIL)
250344
printf ' <failure message="%s">\n' "failed"
251345
printf '%s\n' "$safe_msg"
@@ -266,6 +360,22 @@ auto_secs_for() {
266360
esac
267361
}
268362

363+
# Prefer virtual capture PCMs (PipeWire/Pulse) over raw hw: when a sound server is present
364+
alsa_pick_virtual_pcm() {
365+
command -v arecord >/dev/null 2>&1 || return 1
366+
367+
pcs="$(arecord -L 2>/dev/null | sed -n 's/^[[:space:]]*\([[:alnum:]_]\+\)[[:space:]]*$/\1/p')"
368+
369+
for pcm in pipewire pulse default; do
370+
if printf '%s\n' "$pcs" | grep -m1 -x "$pcm" >/dev/null 2>&1; then
371+
printf '%s\n' "$pcm"
372+
return 0
373+
fi
374+
done
375+
376+
return 1
377+
}
378+
269379
# ---------------- Matrix execution ----------------
270380
total=0
271381
pass=0
@@ -305,7 +415,11 @@ for dur in $DURATIONS; do
305415

306416
loop_hdr="source=$SRC_CHOICE"
307417
if [ "$AUDIO_BACKEND" = "pipewire" ]; then
308-
loop_hdr="$loop_hdr($SRC_ID)"
418+
if [ -n "$SRC_ID" ]; then
419+
loop_hdr="$loop_hdr($SRC_ID)"
420+
else
421+
loop_hdr="$loop_hdr(default)"
422+
fi
309423
else
310424
loop_hdr="$loop_hdr($SRC_LABEL)"
311425
fi
@@ -321,48 +435,101 @@ for dur in $DURATIONS; do
321435
log_info "[$case_name] exec: pw-record -v \"$out\""
322436
audio_exec_with_timeout "$effective_timeout" pw-record -v "$out" >> "$logf" 2>&1
323437
rc=$?
324-
325438
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
326439

440+
# If we already got real audio, accept and skip fallbacks
441+
if [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
442+
if [ "$rc" -ne 0 ]; then
443+
log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS"
444+
rc=0
445+
fi
446+
else
447+
# Only if output is tiny/empty do we try a virtual PCM (pipewire/pulse/default)
448+
if command -v arecord >/dev/null 2>&1; then
449+
pcm="$(alsa_pick_virtual_pcm || true)"
450+
if [ -n "$pcm" ]; then
451+
secs_int="$(audio_parse_secs "$secs" 2>/dev/null || echo 0)"; [ -z "$secs_int" ] && secs_int=0
452+
: > "$out"
453+
log_info "[$case_name] fallback: arecord -D $pcm -f S16_LE -r 48000 -c 2 -d $secs_int \"$out\""
454+
audio_exec_with_timeout "$effective_timeout" \
455+
arecord -D "$pcm" -f S16_LE -r 48000 -c 2 -d "$secs_int" "$out" >> "$logf" 2>&1
456+
rc=$?
457+
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
458+
fi
459+
fi
460+
461+
# As a last resort, retry pw-record with --target (only if we have a source id)
462+
if { [ "$rc" -ne 0 ] || [ "${bytes:-0}" -le 1024 ] 2>/dev/null; } && [ -n "$SRC_ID" ]; then
463+
: > "$out"
464+
log_info "[$case_name] exec: pw-record -v --target \"$SRC_ID\" \"$out\""
465+
audio_exec_with_timeout "$effective_timeout" pw-record -v --target "$SRC_ID" "$out" >> "$logf" 2>&1
466+
rc=$?
467+
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
468+
fi
469+
fi
470+
471+
# (Optional safety) If nonzero rc but output is clearly valid, accept.
327472
if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
328-
log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS"
473+
log_warn "[$case_name] nonzero rc==$rc but recording looks valid (bytes=$bytes) - PASS"
329474
rc=0
330475
fi
476+
else
477+
if [ "$AUDIO_BACKEND" = "alsa" ]; then
478+
secs_int="$(audio_parse_secs "$secs" 2>/dev/null || echo 0)"
479+
[ -z "$secs_int" ] && secs_int=0
480+
log_info "[$case_name] exec: arecord -D \"$SRC_ID\" -f S16_LE -r 48000 -c 2 -d $secs_int \"$out\""
481+
audio_exec_with_timeout "$effective_timeout" \
482+
arecord -D "$SRC_ID" -f S16_LE -r 48000 -c 2 -d "$secs_int" "$out" >> "$logf" 2>&1
483+
rc=$?
484+
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
485+
486+
if [ "$rc" -ne 0 ] || [ "${bytes:-0}" -le 1024 ] 2>/dev/null; then
487+
if printf '%s\n' "$SRC_ID" | grep -q '^hw:'; then
488+
alt_dev="plughw:${SRC_ID#hw:}"
489+
else
490+
alt_dev="$SRC_ID"
491+
fi
492+
for combo in "S16_LE 48000 2" "S16_LE 44100 2" "S16_LE 16000 1"; do
493+
fmt=$(printf '%s\n' "$combo" | awk '{print $1}')
494+
rate=$(printf '%s\n' "$combo" | awk '{print $2}')
495+
ch=$(printf '%s\n' "$combo" | awk '{print $3}')
496+
[ -z "$fmt" ] || [ -z "$rate" ] || [ -z "$ch" ] && continue
497+
: > "$out"
498+
log_info "[$case_name] retry: arecord -D \"$alt_dev\" -f $fmt -r $rate -c $ch -d $secs_int \"$out\""
499+
audio_exec_with_timeout "$effective_timeout" \
500+
arecord -D "$alt_dev" -f "$fmt" -r "$rate" -c "$ch" -d "$secs_int" "$out" >> "$logf" 2>&1
501+
rc=$?
502+
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
503+
if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
504+
break
505+
fi
506+
done
507+
fi
331508

332-
if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -le 1024 ] 2>/dev/null; then
333-
log_warn "[$case_name] first attempt rc=$rc bytes=$bytes; retry with --target $SRC_ID"
334-
: > "$out"
335-
log_info "[$case_name] exec: pw-record -v --target \"$SRC_ID\" \"$out\""
336-
audio_exec_with_timeout "$effective_timeout" pw-record -v --target "$SRC_ID" "$out" >> "$logf" 2>&1
509+
if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
510+
log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS"
511+
rc=0
512+
fi
513+
else
514+
log_info "[$case_name] exec: parecord --file-format=wav \"$out\""
515+
audio_exec_with_timeout "$effective_timeout" parecord --file-format=wav "$out" >> "$logf" 2>&1
337516
rc=$?
338517
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
339518
if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
340-
log_warn "[$case_name] nonzero rc=$rc after retry but recording looks valid (bytes=$bytes) - PASS"
519+
log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS"
341520
rc=0
342521
fi
343522
fi
344-
else
345-
log_info "[$case_name] exec: parecord --file-format=wav \"$out\""
346-
audio_exec_with_timeout "$effective_timeout" parecord --file-format=wav "$out" >> "$logf" 2>&1
347-
rc=$?
348-
bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")"
349-
if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
350-
log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS"
351-
rc=0
352-
fi
353523
fi
354524

355525
end_s="$(date +%s 2>/dev/null || echo 0)"
356526
last_elapsed=$((end_s - start_s))
357-
if [ "$last_elapsed" -lt 0 ]; then
358-
last_elapsed=0
359-
fi
527+
[ "$last_elapsed" -lt 0 ] && last_elapsed=0
360528

361529
# Evidence
362530
pw_ev=$(audio_evidence_pw_streaming || echo 0)
363531
pa_ev=$(audio_evidence_pa_streaming || echo 0)
364532

365-
# ---- minimal PulseAudio fallback so pa_streaming doesn't read as 0 after teardown ----
366533
if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 0 ]; then
367534
if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
368535
pa_ev=1
@@ -376,7 +543,6 @@ for dur in $DURATIONS; do
376543
pwlog_ev=0
377544
fi
378545

379-
# Fast teardown fallback: if user-space stream was active, trust ALSA/ASoC too.
380546
if [ "$alsa_ev" -eq 0 ]; then
381547
if [ "$AUDIO_BACKEND" = "pipewire" ] && [ "$pw_ev" -eq 1 ]; then
382548
alsa_ev=1
@@ -392,7 +558,6 @@ for dur in $DURATIONS; do
392558

393559
log_info "[$case_name] evidence: pw_streaming=$pw_ev pa_streaming=$pa_ev alsa_running=$alsa_ev asoc_path_on=$asoc_ev bytes=${bytes:-0} pw_log=$pwlog_ev"
394560

395-
# Final PASS/FAIL
396561
if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then
397562
log_pass "[$case_name] loop $i OK (rc=0, ${last_elapsed}s, bytes=$bytes)"
398563
ok_runs=$((ok_runs + 1))

0 commit comments

Comments
 (0)