diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Handset.yaml b/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Handset.yaml new file mode 100644 index 00000000..e3309418 --- /dev/null +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Handset.yaml @@ -0,0 +1,35 @@ +metadata: + name: audio-playback-hamoa-handset + format: "Lava-Test Test Definition 1.0" + description: "Audio playback validation for Hamoa handset (4-way speaker system), direct ALSA with hardware mixer profile" + os: + - linux + scope: + - functional + +params: + CLIP_NAMES: "playback_config1" # Test specific clips (e.g., "playback_config1 playback_config2" or "play_48KHz_8b_2ch"), default: playback_config1 + CLIP_FILTER: "" # Filter clips by pattern (e.g., "48KHz" or "16b" or "2ch"), default: unset + FORMATS: "wav" # Audio formats: e.g. wav, default: wav + DURATIONS: "" # Playback durations: short, medium, long (legacy mode only, not used in config discovery mode), default: unset + LOOPS: 1 # Number of playback loops, default: 1 + TIMEOUT: 0 # Playback timeout per loop (0=automatic: clip duration plus 5s grace), default: 0 + STRICT: 0 # Enable strict mode (fail on any error), default: 0 + DMESG_SCAN: 1 # Scan dmesg for errors after playback, default: 1 + VERBOSE: 0 # Enable verbose logging, default: 0 + EXTRACT_AUDIO_ASSETS: true # Download/extract audio assets if missing, default: true + ENABLE_NETWORK_DOWNLOAD: false # Enable network download of missing audio files, default: false + AUDIO_CLIPS_BASE_DIR: "/home/AudioClips" # Custom path to pre-staged audio clips (for CI), default: /home/AudioClips + SSID: "" # Wi-Fi SSID for network connection, default: unset + PASSWORD: "" # Wi-Fi password for network connection, default: unset + RES_SUFFIX: "Hamoa_Handset" # Suffix for unique result file and log directory (generates AudioPlayback_Hamoa_Handset.res and results/AudioPlayback_Hamoa_Handset/) + LAVA_TESTCASE_ID: "AudioPlayback_Hamoa_Handset" # Unique testcase ID written into the .res file for LAVA + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/AudioPlayback/ + - DMESG_FLAG=""; [ "${DMESG_SCAN}" = "0" ] && DMESG_FLAG="--no-dmesg" + - VERBOSE_FLAG=""; [ "${VERBOSE}" = "1" ] && VERBOSE_FLAG="--verbose" + - ./run.sh --backend alsa --alsa-profile hamoa --device handset --sink speakers --clip-name "${CLIP_NAMES}" --clip-filter "${CLIP_FILTER}" --loops "${LOOPS}" --timeout "${TIMEOUT}" --strict "${STRICT}" --audio-clips-path "${AUDIO_CLIPS_BASE_DIR}" --res-suffix "${RES_SUFFIX}" --lava-testcase-id "${LAVA_TESTCASE_ID}" --ssid "${SSID}" --password "${PASSWORD}" ${DMESG_FLAG} ${VERBOSE_FLAG} || true + - $REPO_PATH/Runner/utils/send-to-lava.sh AudioPlayback${RES_SUFFIX:+_${RES_SUFFIX}}.res \ No newline at end of file diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Headset.yaml b/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Headset.yaml new file mode 100644 index 00000000..6195775b --- /dev/null +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/AudioPlayback_Hamoa_Headset.yaml @@ -0,0 +1,35 @@ +metadata: + name: audio-playback-hamoa-headset + format: "Lava-Test Test Definition 1.0" + description: "Audio playback validation for Hamoa headset (stereo headphones), direct ALSA with hardware mixer profile" + os: + - linux + scope: + - functional + +params: + CLIP_NAMES: "playback_config1" # Test specific clips (e.g., "playback_config1 playback_config2" or "play_48KHz_8b_2ch"), default: playback_config1 + CLIP_FILTER: "" # Filter clips by pattern (e.g., "48KHz" or "16b" or "2ch"), default: unset + FORMATS: "wav" # Audio formats: e.g. wav, default: wav + DURATIONS: "" # Playback durations: short, medium, long (legacy mode only, not used in config discovery mode), default: unset + LOOPS: 1 # Number of playback loops, default: 1 + TIMEOUT: 0 # Playback timeout per loop (0=automatic: clip duration plus 5s grace), default: 0 + STRICT: 0 # Enable strict mode (fail on any error), default: 0 + DMESG_SCAN: 1 # Scan dmesg for errors after playback, default: 1 + VERBOSE: 0 # Enable verbose logging, default: 0 + EXTRACT_AUDIO_ASSETS: true # Download/extract audio assets if missing, default: true + ENABLE_NETWORK_DOWNLOAD: false # Enable network download of missing audio files, default: false + AUDIO_CLIPS_BASE_DIR: "/home/AudioClips" # Custom path to pre-staged audio clips (for CI), default: /home/AudioClips + SSID: "" # Wi-Fi SSID for network connection, default: unset + PASSWORD: "" # Wi-Fi password for network connection, default: unset + RES_SUFFIX: "Hamoa_Headset" # Suffix for unique result file and log directory (generates AudioPlayback_Hamoa_Headset.res and results/AudioPlayback_Hamoa_Headset/) + LAVA_TESTCASE_ID: "AudioPlayback_Hamoa_Headset" # Unique testcase ID written into the .res file for LAVA + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/AudioPlayback/ + - DMESG_FLAG=""; [ "${DMESG_SCAN}" = "0" ] && DMESG_FLAG="--no-dmesg" + - VERBOSE_FLAG=""; [ "${VERBOSE}" = "1" ] && VERBOSE_FLAG="--verbose" + - ./run.sh --backend alsa --alsa-profile hamoa --device headset --sink speakers --clip-name "${CLIP_NAMES}" --clip-filter "${CLIP_FILTER}" --loops "${LOOPS}" --timeout "${TIMEOUT}" --strict "${STRICT}" --audio-clips-path "${AUDIO_CLIPS_BASE_DIR}" --res-suffix "${RES_SUFFIX}" --lava-testcase-id "${LAVA_TESTCASE_ID}" --ssid "${SSID}" --password "${PASSWORD}" ${DMESG_FLAG} ${VERBOSE_FLAG} || true + - $REPO_PATH/Runner/utils/send-to-lava.sh AudioPlayback${RES_SUFFIX:+_${RES_SUFFIX}}.res \ No newline at end of file diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md b/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md index 159c1d97..cc07373b 100644 --- a/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md @@ -2,12 +2,12 @@ ## Overview -This suite automates the validation of audio playback capabilities on Qualcomm Linux-based platforms. It supports PipeWire, PulseAudio, and direct ALSA backends with robust evidence-based PASS/FAIL logic, asset management, and diagnostic logging. +This suite automates the validation of audio playback capabilities on Qualcomm Linux-based platforms. It supports PipeWire, PulseAudio, and direct ALSA backends with robust evidence-based PASS/FAIL logic, asset management, and diagnostic logging. ## Features -- Supports **PipeWire**, **PulseAudio**, and direct **ALSA** backends +- Supports **PipeWire**, **PulseAudio**, and direct **ALSA** backends - **10-clip test coverage**: Comprehensive validation across diverse audio formats (sample rates: 8KHz-48KHz, bit depths: 8b-32b, channels: 1ch-8ch) - **Flexible clip selection**: - Use generic config names (playback_config1-playback_config10) for easy selection @@ -18,18 +18,18 @@ This suite automates the validation of audio playback capabilities on Qualcomm L - Unique result file suffixes prevent file collisions in parallel test runs - Unique testcase IDs prevent LAVA testcase ID collisions - Enables running multiple AudioPlayback configurations simultaneously in CI -- Plays audio clips with configurable format, duration, and loop count -- Validates every WAV before playback and emits machine-readable - `AUDIO_VALIDATION` records for both clip integrity and playback execution +- Plays audio clips with configurable format, duration, and loop count +- Validates every WAV before playback and emits machine-readable + `AUDIO_VALIDATION` records for both clip integrity and playback execution - **Network operations are optional**: By default, no network connection is attempted. Use `--enable-network-download` to enable downloading missing audio files -- Automatically downloads and extracts audio assets if missing - -Playback clip validation blocks only on basic integrity failures: corrupt or -empty WAV files, header-only payloads, all-zero audio, materially short clips, -or filename metadata that disagrees with the WAV header. RMS, peak, clipping, -digital-silence runs, DC offset, large sample transitions, and silent-channel -counts are reported as diagnostic `AUDIO_VALIDATION` metrics and do not fail the -default policy. +- Automatically downloads and extracts audio assets if missing + +Playback clip validation blocks only on basic integrity failures: corrupt or +empty WAV files, header-only payloads, all-zero audio, materially short clips, +or filename metadata that disagrees with the WAV header. RMS, peak, clipping, +digital-silence runs, DC offset, large sample transitions, and silent-channel +counts are reported as diagnostic `AUDIO_VALIDATION` metrics and do not fail the +default policy. - Validates playback using multiple evidence sources: - PipeWire/PulseAudio streaming state - ALSA and ASoC runtime status @@ -66,107 +66,143 @@ The test suite includes 10 diverse audio clip configurations covering various sa ## Prerequisites -Yocto images must provide the following components. The suite does not change -the Yocto package flow. - -- PipeWire: `pw-play` or `pw-cat --playback`, plus `wpctl`. CentOS provides - these playback clients in the `pipewire-utils` image package. -- PulseAudio: `paplay`, `pactl` -- ALSA: `aplay`, `amixer`, `alsaucm` when UCM is available -- Common tools: `pgrep`, `timeout`, `grep`, `tar`, and either `curl` or `wget` -- Daemon: `pipewire` or `pulseaudio` must be running - -On Debian, Ubuntu, and CentOS, the selected backend controls client package -recovery. PipeWire ensures `pw-play`, `pw-record`, and `wpctl`; ALSA ensures -`aplay` and `arecord`; PulseAudio ensures `paplay`, `parecord`, and `pactl`. -Yocto continues to use image-provided clients. - -### Ubuntu package preparation - -When run as root, the suite uses the shared package provider to install missing -Ubuntu audio packages. It does not run a blanket distribution upgrade. -When network download is requested and neither downloader is present, Ubuntu -uses the shared package provider to install the complete `audio-download` set, -`curl wget`, from the image's configured APT sources. Other distributions keep -their image-provided downloader policy. Each displayed download attempt has a -finite deadline. If recovery is unavailable or fails, the suite skips and -reports the missing prerequisite. - -If the target already has a valid global IPv4 address but the generic ICMP -probe is blocked, the suite preserves the active interface and lets the bounded -HTTPS download validate endpoint reachability. It does not cycle Wi-Fi, renew -DHCP, or probe unrelated network services in that state. CentOS targets using -the systemd-managed `NetworkManager.service` are recognized through `nmcli` -when network activation is actually required. The absence of -`systemd-networkd` or ConnMan is not reported when NetworkManager is active. - -- `auto` is the default profile. It selects `desktop` when `graphical.target` - is active and otherwise selects `server`. -- `server` ensures `alsa-utils` for ALSA playback. -- `desktop` ensures `alsa-utils`, `pipewire`, `pipewire-pulse`, `wireplumber`, - `pipewire-bin`, and `pulseaudio-utils`. -- AudioReach is not enabled for Ubuntu. An explicit `--overlay` request reports - SKIP before any AudioReach source refresh or package operation. Rerun without - `--overlay` to validate the Ubuntu base Audio stack. -- Set `AUDIO_PACKAGE_UPDATE=1` only when installed packages should be upgraded - as part of the test preparation. - -The optional `snd-soc-wcd938x` codec module is loaded only when the running -kernel provides it. Targets without that module are unchanged. - -### CentOS Stream 10 overlay preparation - -An explicit `./run.sh --overlay` request ensures EPEL and the Qualcomm CentOS -10 aarch64 and noarch repositories, refreshes DNF metadata, and installs: - -```text -audioreach-dkms audioreach-pal audioreach-pipewire-plugin -``` - -If the DKMS package changes, the suite requests a reboot before validation. -In base mode, CentOS recovers only the complete client set for the selected -backend from the configured distribution repositories. - -Backend client checks run after package recovery. PipeWire uses `pw-play` when -available and falls back to `pw-cat --playback`. Automatic backend selection -falls back to an available ALSA path. Package-recovery failure is reported as -FAIL before any audio-clip download begins. - -When launched by root on Debian or CentOS, the suite discovers and prepares the -regular desktop audio user, then runs PipeWire, PulseAudio, UCM, mixer, and PCM -operations in that user's context. Yocto retains its native execution model, -and Ubuntu continues to use its dynamically discovered desktop session. - -```sh -# Let the suite detect a server or desktop Ubuntu image -./run.sh --clip-name "playback_config1 playback_config7" - -# Force desktop package preparation -AUDIO_PACKAGE_PROFILE=desktop \ - ./run.sh --clip-name "playback_config1 playback_config7" - -# Upgrade the selected profile's installed packages -AUDIO_PACKAGE_PROFILE=desktop AUDIO_PACKAGE_UPDATE=1 \ - ./run.sh --clip-name "playback_config1 playback_config7" -``` - -## Backend and Route Selection - -When no backend is requested, the suite uses automatic selection. A physical PipeWire audio sink uses `pw-play` or `pw-cat --playback`, and a physical PulseAudio sink uses `paplay`. For the `speakers` route, a speaker endpoint takes precedence over headphones, then other physical outputs. Dummy, null, monitor, and loopback PipeWire sinks are not accepted as speaker routes. - -If automatic selection finds no physical managed speaker sink, the suite probes direct ALSA playback. It selects an ALSA card and PCM from the available device inventory, applies only mixer controls exposed by that card, and runs `aplay -D `. This supports the Shikra primary-MI2S, secondary-TDM, and codec-direct route capabilities without selecting a form factor or assuming card `0`. When the audio remoteproc preflight proves that audio is applicable, failure of both managed-sink discovery and direct ALSA probing is reported as FAIL so the image or runtime regression remains tracked. The same absence remains SKIP only when runtime preflight found no applicable audio subsystem. - -An explicit backend request is never replaced: - -- `--backend pipewire` or `AUDIO_BACKEND=pipewire` runs `pw-play` or `pw-cat --playback`. A missing optional client can skip, but a ready backend with no requested physical sink fails. -- `--backend pulseaudio` or `AUDIO_BACKEND=pulseaudio` runs `paplay` only. A ready backend with no requested sink fails. -- `--backend alsa` or `AUDIO_BACKEND=alsa` runs `aplay` with the discovered ALSA route. - -## Audio Remoteproc Preflight - -Before backend discovery, the suite enumerates runtime remoteprocs and checks the configured firmware and topology under `/lib/firmware` or `/usr/lib/firmware`. It recognizes audio DSP identities and the Shikra modem-hosted audio path. A remoteproc named `modem` is ignored on non-Shikra targets because it is not their audio preflight dependency. - -If the matching remoteproc is `running`, the suite records its name and firmware evidence. If it is `offline`, the suite starts it only after confirming the corresponding firmware is provisioned. Generic audio DSP paths also require adjacent topology evidence. Shikra's modem-hosted path does not require an adjacent topology file because its public image packages `qdsp6sw` modem firmware and `lpaicp` audio firmware separately. The suite never stops a remoteproc. Platforms with no applicable audio remoteproc continue normally. Provisioning or start issues produce a clean skip with the reason. +Yocto images must provide the following components. The suite does not change +the Yocto package flow. + +- PipeWire: `pw-play` or `pw-cat --playback`, plus `wpctl`. CentOS provides + these playback clients in the `pipewire-utils` image package. +- PulseAudio: `paplay`, `pactl` +- ALSA: `aplay`, `amixer`, `alsaucm` when UCM is available +- Common tools: `pgrep`, `timeout`, `grep`, `tar`, and either `curl` or `wget` +- Daemon: `pipewire` or `pulseaudio` must be running + +On Debian, Ubuntu, and CentOS, the selected backend controls client package +recovery. PipeWire ensures `pw-play`, `pw-record`, and `wpctl`; ALSA ensures +`aplay` and `arecord`; PulseAudio ensures `paplay`, `parecord`, and `pactl`. +Yocto continues to use image-provided clients. + +### Ubuntu package preparation + +When run as root, the suite uses the shared package provider to install missing +Ubuntu audio packages. It does not run a blanket distribution upgrade. +When network download is requested and neither downloader is present, Ubuntu +uses the shared package provider to install the complete `audio-download` set, +`curl wget`, from the image's configured APT sources. Other distributions keep +their image-provided downloader policy. Each displayed download attempt has a +finite deadline. If recovery is unavailable or fails, the suite skips and +reports the missing prerequisite. + +If the target already has a valid global IPv4 address but the generic ICMP +probe is blocked, the suite preserves the active interface and lets the bounded +HTTPS download validate endpoint reachability. It does not cycle Wi-Fi, renew +DHCP, or probe unrelated network services in that state. CentOS targets using +the systemd-managed `NetworkManager.service` are recognized through `nmcli` +when network activation is actually required. The absence of +`systemd-networkd` or ConnMan is not reported when NetworkManager is active. + +- `auto` is the default profile. It selects `desktop` when `graphical.target` + is active and otherwise selects `server`. +- `server` ensures `alsa-utils` for ALSA playback. +- `desktop` ensures `alsa-utils`, `pipewire`, `pipewire-pulse`, `wireplumber`, + `pipewire-bin`, and `pulseaudio-utils`. +- AudioReach is not enabled for Ubuntu. An explicit `--overlay` request reports + SKIP before any AudioReach source refresh or package operation. Rerun without + `--overlay` to validate the Ubuntu base Audio stack. +- Set `AUDIO_PACKAGE_UPDATE=1` only when installed packages should be upgraded + as part of the test preparation. + +The optional `snd-soc-wcd938x` codec module is loaded only when the running +kernel provides it. Targets without that module are unchanged. + +### CentOS Stream 10 overlay preparation + +An explicit `./run.sh --overlay` request ensures EPEL and the Qualcomm CentOS +10 aarch64 and noarch repositories, refreshes DNF metadata, and installs: + +```text +audioreach-dkms audioreach-pal audioreach-pipewire-plugin +``` + +If the DKMS package changes, the suite requests a reboot before validation. +In base mode, CentOS recovers only the complete client set for the selected +backend from the configured distribution repositories. + +Backend client checks run after package recovery. PipeWire uses `pw-play` when +available and falls back to `pw-cat --playback`. Automatic backend selection +falls back to an available ALSA path. Package-recovery failure is reported as +FAIL before any audio-clip download begins. + +When launched by root on Debian or CentOS, the suite discovers and prepares the +regular desktop audio user, then runs PipeWire, PulseAudio, UCM, mixer, and PCM +operations in that user's context. Yocto retains its native execution model, +and Ubuntu continues to use its dynamically discovered desktop session. + +```sh +# Let the suite detect a server or desktop Ubuntu image +./run.sh --clip-name "playback_config1 playback_config7" + +# Force desktop package preparation +AUDIO_PACKAGE_PROFILE=desktop \ + ./run.sh --clip-name "playback_config1 playback_config7" + +# Upgrade the selected profile's installed packages +AUDIO_PACKAGE_PROFILE=desktop AUDIO_PACKAGE_UPDATE=1 \ + ./run.sh --clip-name "playback_config1 playback_config7" +``` + +## Backend and Route Selection + +When no backend is requested, the suite uses automatic selection. A physical PipeWire audio sink uses `pw-play` or `pw-cat --playback`, and a physical PulseAudio sink uses `paplay`. For the `speakers` route, a speaker endpoint takes precedence over headphones, then other physical outputs. Dummy, null, monitor, and loopback PipeWire sinks are not accepted as speaker routes. + +If automatic selection finds no physical managed speaker sink, the suite probes direct ALSA playback. It selects an ALSA card and PCM from the available device inventory, applies only mixer controls exposed by that card, and runs `aplay -D `. This supports the Shikra primary-MI2S, secondary-TDM, and codec-direct route capabilities without selecting a form factor or assuming card `0`. When the audio remoteproc preflight proves that audio is applicable, failure of both managed-sink discovery and direct ALSA probing is reported as FAIL so the image or runtime regression remains tracked. The same absence remains SKIP only when runtime preflight found no applicable audio subsystem. + +An explicit backend request is never replaced: + +- `--backend pipewire` or `AUDIO_BACKEND=pipewire` runs `pw-play` or `pw-cat --playback`. A missing optional client can skip, but a ready backend with no requested physical sink fails. +- `--backend pulseaudio` or `AUDIO_BACKEND=pulseaudio` runs `paplay` only. A ready backend with no requested sink fails. +- `--backend alsa` or `AUDIO_BACKEND=alsa` runs `aplay` with the discovered ALSA route. + +## Hardware-Specific ALSA Profiles + +`--alsa-profile ` selects a declarative, hardware-specific mixer profile +instead of automatic route discovery. This requires `--backend alsa` (it is +set automatically when omitted) and `--device ` identifying which +physical route on that hardware to configure. + +Currently supported: + +```sh +./run.sh --backend alsa --alsa-profile hamoa --device handset \ + --clip-name playback_config1 +./run.sh --backend alsa --alsa-profile hamoa --device headset \ + --clip-name playback_config1 +``` + +`ALSA_PROFILE` and `DEVICE` environment variables are equivalent to +`--alsa-profile`/`--device`, for YAML-driven CI jobs that set environment +variables rather than CLI flags. + +The ALSA card for the requested profile is resolved dynamically at runtime by +a distinctive mixer control (never a hardcoded card index), so the correct +card is selected regardless of registration order. When an alsaucm UCM +configuration tree is installed for the resolved card +(`/usr/share/alsa/ucm2//`), the suite configures the route through +`alsaucm` first; it falls back to this repository's own +`Runner/utils/audio_profiles/*.profile` + `amixer` mechanism when UCM is not +usable on the running image. Both mechanisms are verified afterward by +confirming the resulting PCM device is enumerated for the requested +direction. An unsupported `--alsa-profile`/`--device` combination SKIPs +cleanly rather than failing. + +Mixer writes to the same hardware card are serialized across concurrent test +runs via a per-card lock, so parallel `--device handset`/`--device headset` +invocations on the same card do not interleave their mixer writes. + +## Audio Remoteproc Preflight + +Before backend discovery, the suite enumerates runtime remoteprocs and checks the configured firmware and topology under `/lib/firmware` or `/usr/lib/firmware`. It recognizes audio DSP identities and the Shikra modem-hosted audio path. A remoteproc named `modem` is ignored on non-Shikra targets because it is not their audio preflight dependency. + +If the matching remoteproc is `running`, the suite records its name and firmware evidence. If it is `offline`, the suite starts it only after confirming the corresponding firmware is provisioned. Generic audio DSP paths also require adjacent topology evidence. Shikra's modem-hosted path does not require an adjacent topology file because its public image packages `qdsp6sw` modem firmware and `lpaicp` audio firmware separately. The suite never stops a remoteproc. Platforms with no applicable audio remoteproc continue normally. Provisioning or start issues produce a clean skip with the reason. ## Overlay Build Support @@ -314,23 +350,23 @@ cd Runner/suites/Multimedia/Audio/AudioPlayback Environment Variables: Variable Description Default -AUDIO_BACKEND Selects backend: pipewire, pulseaudio, or alsa auto-detect +AUDIO_BACKEND Selects backend: pipewire, pulseaudio, or alsa auto-detect SINK_CHOICE Playback sink: speakers or null speakers CLIP_NAMES Test specific clips (e.g., "playback_config1 playback_config2") playback_config1 CLIP_FILTER Filter clips by pattern (e.g., "48KHz" or "16b" or "2ch") unset FORMATS Audio formats: e.g. wav wav DURATIONS Playback durations: short, medium, long (legacy mode only) "" LOOPS Number of playback loops 1 -TIMEOUT Playback timeout per loop (0=automatic for discovered clips) "10s" +TIMEOUT Playback timeout per loop (0=automatic for discovered clips) "10s" STRICT Enable strict mode (fail on any error) 0 DMESG_SCAN Scan dmesg for errors after playback 1 VERBOSE Enable verbose logging 0 EXTRACT_AUDIO_ASSETS Download/extract audio assets if missing true ENABLE_NETWORK_DOWNLOAD Enable network download of missing audio files false -AUDIO_CLIPS_BASE_DIR Custom path to pre-staged audio clips (CI use) unset -AUDIO_PACKAGE_PROFILE Ubuntu package profile: auto, server, or desktop auto -AUDIO_PACKAGE_UPDATE Upgrade selected Ubuntu audio packages 0 -JUNIT_OUT Path to write JUnit XML output unset +AUDIO_CLIPS_BASE_DIR Custom path to pre-staged audio clips (CI use) unset +AUDIO_PACKAGE_PROFILE Ubuntu package profile: auto, server, or desktop auto +AUDIO_PACKAGE_UPDATE Upgrade selected Ubuntu audio packages 0 +JUNIT_OUT Path to write JUnit XML output unset SSID Wi-Fi SSID for network connection unset PASSWORD Wi-Fi password for network connection unset NET_PROBE_ROUTE_IP IP used for route probing (default: 1.1.1.1) 1.1.1.1 @@ -341,14 +377,14 @@ LAVA_TESTCASE_ID Unique testcase ID written into the .res file for LAVA CLI Options Option Description ---backend Select backend: pipewire, pulseaudio, or alsa +--backend Select backend: pipewire, pulseaudio, or alsa --sink Playback sink: speakers or null --clip-name Test specific clips using playback_config1-playback_config10 or descriptive names (space-separated) --clip-filter Filter clips by sample rate, bit rate, or channels (space-separated patterns) --formats Audio formats (space/comma separated): e.g. wav --durations Playback durations: short, medium, long (legacy mode only) --loops Number of playback loops ---timeout Playback timeout per loop; 0 uses clip duration plus five seconds in discovery mode +--timeout Playback timeout per loop; 0 uses clip duration plus five seconds in discovery mode --strict Enable strict mode --no-dmesg Disable dmesg scan --no-extract-assets Disable asset extraction entirely (skips all asset operations) @@ -453,7 +489,7 @@ sh-5.3# ./run.sh --clip-name "playback_config1" --res-suffix "Config01" --audio- [INFO] 2026-01-22 17:46:33 - ---------------- Starting AudioPlayback ---------------- [INFO] 2026-01-22 17:46:33 - Using clip discovery mode [INFO] 2026-01-22 17:46:33 - Discovered 1 clips to test -[INFO] 2026-01-22 17:46:33 - [play_16KHz_16b_2ch] Clip duration: 30s (minimum successful runtime: 29s) +[INFO] 2026-01-22 17:46:33 - [play_16KHz_16b_2ch] Clip duration: 30s (minimum successful runtime: 29s) [PASS] 2026-01-22 17:47:04 - [play_16KHz_16b_2ch] loop 1 OK (rc=0, 30s) [PASS] 2026-01-22 17:47:04 - AudioPlayback PASS @@ -502,7 +538,7 @@ Results: - Results are stored in: results/AudioPlayback/ (or results/AudioPlayback_/ when using --res-suffix) - Summary result file: AudioPlayback.res (or AudioPlayback_.res when using --res-suffix) - JUnit XML (if enabled): .xml -- Diagnostic logs: dmesg snapshots, mixer dumps, playback logs per test case, and `alsa_playback_probe.log` containing the selected direct-ALSA candidate, probe command, exit status, and `aplay` error output +- Diagnostic logs: dmesg snapshots, mixer dumps, playback logs per test case, and `alsa_playback_probe.log` containing the selected direct-ALSA candidate, probe command, exit status, and `aplay` error output - **Note**: When using --res-suffix, both result files AND log directories are unique per invocation, preventing log collisions in CI/LAVA workflows diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh b/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh index d1c47434..f68f1d20 100755 --- a/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh @@ -101,6 +101,8 @@ export AUDIO_TAR_URL # ------------- Defaults / CLI ------------- AUDIO_BACKEND="${AUDIO_BACKEND:-}" +ALSA_PROFILE="${ALSA_PROFILE:-generic}" # ALSA profile (hamoa, generic) +DEVICE="${DEVICE:-}" # Device type for ALSA profiles (handset, headset) SINK_CHOICE="${SINK_CHOICE:-speakers}" # speakers|null FORMATS="" # Will be set to default only if using legacy mode DURATIONS="" # Will be set to default only if using legacy mode @@ -149,6 +151,8 @@ usage() { cat < "$RES_FILE" + exit 0 + fi +fi + # Resolve backend if [ -z "$AUDIO_BACKEND" ]; then AUDIO_BACKEND="$(audio_run_helper_as_test_user --require-session detect_audio_backend 2>/dev/null || echo "")" @@ -860,7 +888,29 @@ fi backend_ok=0 if [ "$AUDIO_BACKEND" = "alsa" ]; then - if audio_playback_probe_alsa_with_recovery; then + # Hardware-specific ALSA profiles provide explicit device paths and mixer + # configurations. Validate the profile/device combination and card + # presence here, so an unsupported combination or missing hardware is + # reported as a SKIP now rather than surfacing later during mixer setup. + if [ "$ALSA_PROFILE" != "generic" ] && [ -n "$ALSA_PROFILE" ]; then + case "$ALSA_PROFILE:$DEVICE" in + hamoa:handset|hamoa:headset) + if resolve_alsa_card_index "$ALSA_PROFILE" >/dev/null 2>&1; then + backend_ok=1 + log_info "Using hardware-specific ALSA profile '$ALSA_PROFILE' for device '$DEVICE' - card resolved" + else + log_skip "$TESTNAME SKIP - ALSA profile '$ALSA_PROFILE' hardware not present (card not found)" + echo "$RESULT_TESTNAME SKIP" > "$RES_FILE" + exit 0 + fi + ;; + *) + log_skip "$TESTNAME SKIP - unsupported ALSA profile/device combination: '$ALSA_PROFILE'/'$DEVICE'" + echo "$RESULT_TESTNAME SKIP" > "$RES_FILE" + exit 0 + ;; + esac + elif audio_playback_probe_alsa_with_recovery; then backend_ok=1 fi else @@ -1069,14 +1119,46 @@ case "$AUDIO_BACKEND:$SINK_CHOICE" in SINK_ID="null" ;; alsa:*) - if [ -n "${AUDIO_ALSA_PLAYBACK_DEVICE:-}" ]; then - SINK_ID="$AUDIO_ALSA_PLAYBACK_DEVICE" + # Hardware-specific ALSA profiles (e.g., Hamoa) configure mixer controls + # and provide an explicit device path for the requested device, instead + # of discovering a sink through the generic ALSA profile detection below. + if [ "$ALSA_PROFILE" != "generic" ] && [ -n "$DEVICE" ]; then + if ! CHECK_DEPS_NO_EXIT=1 check_dependencies amixer grep sed flock; then + log_skip "$TESTNAME SKIP - missing ALSA mixer utilities required by profile '$ALSA_PROFILE'" + echo "$RESULT_TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + + log_info "Using ALSA profile: $ALSA_PROFILE for device: $DEVICE" + case "$ALSA_PROFILE:$DEVICE" in + hamoa:handset|hamoa:headset) + SINK_ID="$(setup_alsa_profile_route "$ALSA_PROFILE" "$DEVICE" playback "$LOGDIR")" + profile_setup_rc=$? + ;; + *) + log_skip "$TESTNAME SKIP - unsupported ALSA profile/device: $ALSA_PROFILE/$DEVICE" + echo "$RESULT_TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac + + if [ "$profile_setup_rc" -eq 0 ]; then + log_info "ALSA profile configured successfully, device: $SINK_ID" + else + log_error "Failed to setup ALSA profile: $ALSA_PROFILE for device: $DEVICE" + echo "$RESULT_TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi else - SINK_ID="$(audio_playback_pick_alsa_sink)" - fi + if [ -n "${AUDIO_ALSA_PLAYBACK_DEVICE:-}" ]; then + SINK_ID="$AUDIO_ALSA_PLAYBACK_DEVICE" + else + SINK_ID="$(audio_playback_pick_alsa_sink)" + fi - audio_playback_alsa_prepare \ - "$(audio_alsa_device_card "$SINK_ID")" >/dev/null 2>&1 || true + audio_playback_alsa_prepare \ + "$(audio_alsa_device_card "$SINK_ID")" >/dev/null 2>&1 || true + fi ;; esac @@ -1305,8 +1387,23 @@ if [ "$USE_CLIP_DISCOVERY" = "true" ]; then audio_run_with_timeout_as_test_user --require-session "$effective_timeout" paplay --device="$SINK_NAME" "$clip_path" >>"$logf" 2>&1 rc=$? else - log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" \"$clip_path\"" - audio_run_with_timeout_as_test_user "$effective_timeout" aplay -D "$SINK_NAME" "$clip_path" >>"$logf" 2>&1 + # aplay's own -d duration limit lets it finish and close the PCM + # device on its own schedule, instead of being killed externally by + # the watchdog wrapper once effective_timeout elapses. An externally + # killed aplay process does not get a chance to report ALSA/ASoC + # runtime evidence the way a self-terminating one does, so passing + # -d here keeps the ALSA path's PASS/FAIL evidence consistent with + # the PipeWire and PulseAudio paths above, which are not subject to + # this because their playback client is not the sole owner of the + # underlying device. + aplay_duration_secs="$(audio_parse_secs "$effective_timeout" 2>/dev/null || echo 0)" + if [ "$aplay_duration_secs" -gt 0 ] 2>/dev/null; then + log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" -d $aplay_duration_secs \"$clip_path\"" + audio_run_with_timeout_as_test_user "$effective_timeout" aplay -D "$SINK_NAME" -d "$aplay_duration_secs" "$clip_path" >>"$logf" 2>&1 + else + log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" \"$clip_path\"" + audio_run_with_timeout_as_test_user "$effective_timeout" aplay -D "$SINK_NAME" "$clip_path" >>"$logf" 2>&1 + fi rc=$? fi @@ -1482,8 +1579,17 @@ else audio_run_with_timeout_as_test_user --require-session "$TIMEOUT" paplay --device="$SINK_NAME" "$clip" >>"$logf" 2>&1 rc=$? else - log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" \"$clip\"" - audio_run_with_timeout_as_test_user "$TIMEOUT" aplay -D "$SINK_NAME" "$clip" >>"$logf" 2>&1 + # Mirror the discovery-mode aplay invocation: pass -d so aplay + # can finish and close the PCM device on its own schedule rather + # than being killed externally once the watchdog elapses. + aplay_duration_secs="$(audio_parse_secs "$TIMEOUT" 2>/dev/null || echo 0)" + if [ "$aplay_duration_secs" -gt 0 ] 2>/dev/null; then + log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" -d $aplay_duration_secs \"$clip\"" + audio_run_with_timeout_as_test_user "$TIMEOUT" aplay -D "$SINK_NAME" -d "$aplay_duration_secs" "$clip" >>"$logf" 2>&1 + else + log_info "[$case_name] exec: aplay -D \"$SINK_NAME\" \"$clip\"" + audio_run_with_timeout_as_test_user "$TIMEOUT" aplay -D "$SINK_NAME" "$clip" >>"$logf" 2>&1 + fi rc=$? fi diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Handset.yaml b/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Handset.yaml new file mode 100644 index 00000000..e89f2bad --- /dev/null +++ b/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Handset.yaml @@ -0,0 +1,31 @@ +metadata: + name: audio-record-hamoa-handset + format: "Lava-Test Test Definition 1.0" + description: "Audio recording validation for Hamoa handset (built-in microphones, VA_DMIC), direct ALSA with hardware mixer profile" + os: + - linux + scope: + - functional + +params: + CONFIG_NAMES: "record_config1" # Test specific configs (e.g., "record_config1 record_config2"), default: record_config1 + CONFIG_FILTER: "" # Filter configs by pattern (e.g., "48KHz" or "2ch"), default: unset + DURATIONS: "" # Recording durations: short, medium, long (legacy mode only, not used in config discovery mode), default: unset + RECORD_SECONDS: "5s" # Recording duration with unit suffix (e.g., "5s", "10s", "30s"), default: 5s + LOOPS: 1 # Number of recording loops, default: 1 + TIMEOUT: 0 # Explicit timeout override per loop (e.g., 15s, 0=automatic), default: 0 + AUDIO_RECORD_START_GRACE: 5 # Startup headroom added to the automatic recorder watchdog, default: 5s + STRICT: 0 # Enable strict mode (fail on any error), default: 0 + DMESG_SCAN: 1 # Scan dmesg for errors after recording, default: 1 + VERBOSE: 0 # Enable verbose logging, default: 0 + RES_SUFFIX: "Hamoa_Handset" # Suffix for unique result file and log directory (generates AudioRecord_Hamoa_Handset.res and results/AudioRecord_Hamoa_Handset/) + LAVA_TESTCASE_ID: "AudioRecord_Hamoa_Handset" # Unique testcase ID written into the .res file for LAVA + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/AudioRecord/ + - DMESG_FLAG=""; [ "${DMESG_SCAN}" = "0" ] && DMESG_FLAG="--no-dmesg" + - VERBOSE_FLAG=""; [ "${VERBOSE}" = "1" ] && VERBOSE_FLAG="--verbose" + - ./run.sh --backend alsa --alsa-profile hamoa --device handset --source mic --config-name "${CONFIG_NAMES}" --config-filter "${CONFIG_FILTER}" --record-seconds "${RECORD_SECONDS}" --loops "${LOOPS}" --timeout "${TIMEOUT}" --start-grace "${AUDIO_RECORD_START_GRACE}" --strict "${STRICT}" --res-suffix "${RES_SUFFIX}" --lava-testcase-id "${LAVA_TESTCASE_ID}" ${DMESG_FLAG} ${VERBOSE_FLAG} || true + - $REPO_PATH/Runner/utils/send-to-lava.sh AudioRecord${RES_SUFFIX:+_${RES_SUFFIX}}.res \ No newline at end of file diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Headset.yaml b/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Headset.yaml new file mode 100644 index 00000000..9a8488ed --- /dev/null +++ b/Runner/suites/Multimedia/Audio/AudioRecord/AudioRecord_Hamoa_Headset.yaml @@ -0,0 +1,31 @@ +metadata: + name: audio-record-hamoa-headset + format: "Lava-Test Test Definition 1.0" + description: "Audio recording validation for Hamoa headset (headset microphone, SWR_MIC), direct ALSA with hardware mixer profile" + os: + - linux + scope: + - functional + +params: + CONFIG_NAMES: "record_config1" # Test specific configs (e.g., "record_config1 record_config2"), default: record_config1 + CONFIG_FILTER: "" # Filter configs by pattern (e.g., "48KHz" or "2ch"), default: unset + DURATIONS: "" # Recording durations: short, medium, long (legacy mode only, not used in config discovery mode), default: unset + RECORD_SECONDS: "5s" # Recording duration with unit suffix (e.g., "5s", "10s", "30s"), default: 5s + LOOPS: 1 # Number of recording loops, default: 1 + TIMEOUT: 0 # Explicit timeout override per loop (e.g., 15s, 0=automatic), default: 0 + AUDIO_RECORD_START_GRACE: 5 # Startup headroom added to the automatic recorder watchdog, default: 5s + STRICT: 0 # Enable strict mode (fail on any error), default: 0 + DMESG_SCAN: 1 # Scan dmesg for errors after recording, default: 1 + VERBOSE: 0 # Enable verbose logging, default: 0 + RES_SUFFIX: "Hamoa_Headset" # Suffix for unique result file and log directory (generates AudioRecord_Hamoa_Headset.res and results/AudioRecord_Hamoa_Headset/) + LAVA_TESTCASE_ID: "AudioRecord_Hamoa_Headset" # Unique testcase ID written into the .res file for LAVA + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/AudioRecord/ + - DMESG_FLAG=""; [ "${DMESG_SCAN}" = "0" ] && DMESG_FLAG="--no-dmesg" + - VERBOSE_FLAG=""; [ "${VERBOSE}" = "1" ] && VERBOSE_FLAG="--verbose" + - ./run.sh --backend alsa --alsa-profile hamoa --device headset --source mic --config-name "${CONFIG_NAMES}" --config-filter "${CONFIG_FILTER}" --record-seconds "${RECORD_SECONDS}" --loops "${LOOPS}" --timeout "${TIMEOUT}" --start-grace "${AUDIO_RECORD_START_GRACE}" --strict "${STRICT}" --res-suffix "${RES_SUFFIX}" --lava-testcase-id "${LAVA_TESTCASE_ID}" ${DMESG_FLAG} ${VERBOSE_FLAG} || true + - $REPO_PATH/Runner/utils/send-to-lava.sh AudioRecord${RES_SUFFIX:+_${RES_SUFFIX}}.res \ No newline at end of file diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md b/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md index 58f0493d..77463d96 100644 --- a/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md +++ b/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md @@ -2,18 +2,18 @@ ## Overview -This suite automates the validation of audio recording capabilities on Qualcomm Linux-based platforms. It supports PipeWire, PulseAudio, and direct ALSA backends with robust evidence-based PASS/FAIL logic, asset management, and diagnostic logging. - -The default CI policy blocks corrupt or empty WAV files, header-only payloads, -all-zero audio, materially short captures, and explicitly requested format -mismatches. RMS, peak, clipping, digital-silence runs, DC offset, large sample -transitions, and silent-channel counts are emitted as diagnostic -`AUDIO_VALIDATION` metrics. These metrics do not fail the default policy unless -strict signal validation is explicitly enabled. +This suite automates the validation of audio recording capabilities on Qualcomm Linux-based platforms. It supports PipeWire, PulseAudio, and direct ALSA backends with robust evidence-based PASS/FAIL logic, asset management, and diagnostic logging. + +The default CI policy blocks corrupt or empty WAV files, header-only payloads, +all-zero audio, materially short captures, and explicitly requested format +mismatches. RMS, peak, clipping, digital-silence runs, DC offset, large sample +transitions, and silent-channel counts are emitted as diagnostic +`AUDIO_VALIDATION` metrics. These metrics do not fail the default policy unless +strict signal validation is explicitly enabled. ## Features -- Supports **PipeWire**, **PulseAudio**, and direct **ALSA** backends +- Supports **PipeWire**, **PulseAudio**, and direct **ALSA** backends - **10-config test coverage**: Comprehensive validation across diverse audio formats (sample rates: 8KHz-96KHz, channels: 1ch-6ch) - **Flexible config selection**: - Use generic config names (record_config1-record_config10) for easy selection @@ -65,54 +65,90 @@ Ensure the following components are present in the target Yocto build: - PipeWire: `pw-record`, `wpctl` - PulseAudio: `parecord`, `pactl` - ALSA: `arecord` -- Common tools: `pgrep`, `timeout`, `grep`, `sed` -- Daemon: `pipewire` or `pulseaudio` must be running - -On Debian, Ubuntu, and CentOS, the selected backend controls client package -recovery. PipeWire ensures `pw-play`, `pw-record`, and `wpctl`; ALSA ensures -`aplay` and `arecord`; PulseAudio ensures `paplay`, `parecord`, and `pactl`. -Yocto continues to use image-provided clients. - -When launched by root on Debian or CentOS, the suite discovers and prepares the -regular desktop audio user, then runs PipeWire, PulseAudio, UCM, mixer, and PCM -operations in that user's context. Yocto retains its native execution model. - -## Backend and Route Selection - -When no backend is requested, the suite uses automatic selection. A real PipeWire audio source uses `pw-record`, and a real PulseAudio source uses `parecord`. Camera, dummy, null, monitor, and loopback PipeWire nodes are not accepted as microphone sources. - -If automatic selection finds no physical managed microphone source, the suite probes direct ALSA capture. It selects a card and PCM from the available device inventory, applies only mixer controls exposed by that card, and runs `arecord -D `. This discovers the VA-DMIC capture route from its controls without selecting a form factor or assuming card `0`. When audio remoteproc preflight proves that audio is applicable, failure of both managed-source discovery and direct ALSA probing is reported as FAIL so the image or runtime regression remains tracked. The same absence remains SKIP only when runtime preflight found no applicable audio subsystem. - -An explicit backend request is never replaced: - -- `--backend pipewire` or `AUDIO_BACKEND=pipewire` runs `pw-record` only and fails if the requested physical microphone source is unavailable. -- `--backend pulseaudio` or `AUDIO_BACKEND=pulseaudio` runs `parecord` only and fails if the requested source is unavailable. -- `--backend alsa` or `AUDIO_BACKEND=alsa` runs `arecord` with the discovered ALSA route. - -## Audio Remoteproc Preflight - -Before backend discovery, the suite enumerates runtime remoteprocs and checks the configured firmware and topology under `/lib/firmware` or `/usr/lib/firmware`. It recognizes audio DSP identities and the Shikra modem-hosted audio path. A remoteproc named `modem` is ignored on non-Shikra targets because it is not their audio preflight dependency. - -If the matching remoteproc is `running`, the suite records its name and firmware evidence. If it is `offline`, the suite starts it only after confirming the corresponding firmware is provisioned. Generic audio DSP paths also require adjacent topology evidence. Shikra's modem-hosted path does not require an adjacent topology file because its public image packages `qdsp6sw` modem firmware and `lpaicp` audio firmware separately. The suite never stops a remoteproc. Platforms with no applicable audio remoteproc continue normally. Provisioning or start issues produce a clean skip with the reason. - -## Overlay Build Support - -AudioReach is not enabled for Ubuntu. An explicit `./run.sh --overlay` request -reports SKIP before any AudioReach source refresh or package operation. Rerun -without `--overlay` to validate the Ubuntu base Audio stack. - -On CentOS Stream 10, an explicit `./run.sh --overlay` request ensures EPEL and -the Qualcomm aarch64 and noarch RPM repositories, refreshes DNF metadata, and -installs `audioreach-dkms`, `audioreach-pal`, and -`audioreach-pipewire-plugin`. If the DKMS package changes, reboot before -rerunning validation. Yocto remains image-provided and non-installing. - -Backend client checks run after recovery of the selected backend's complete -playback and recording client package set. Package recovery failure is reported -as FAIL. A client that remains unavailable after successful recovery produces -a clean SKIP for the unavailable recording path. - -For overlay builds using audioreach kernel modules, the test automatically: +- Common tools: `pgrep`, `timeout`, `grep`, `sed` +- Daemon: `pipewire` or `pulseaudio` must be running + +On Debian, Ubuntu, and CentOS, the selected backend controls client package +recovery. PipeWire ensures `pw-play`, `pw-record`, and `wpctl`; ALSA ensures +`aplay` and `arecord`; PulseAudio ensures `paplay`, `parecord`, and `pactl`. +Yocto continues to use image-provided clients. + +When launched by root on Debian or CentOS, the suite discovers and prepares the +regular desktop audio user, then runs PipeWire, PulseAudio, UCM, mixer, and PCM +operations in that user's context. Yocto retains its native execution model. + +## Backend and Route Selection + +When no backend is requested, the suite uses automatic selection. A real PipeWire audio source uses `pw-record`, and a real PulseAudio source uses `parecord`. Camera, dummy, null, monitor, and loopback PipeWire nodes are not accepted as microphone sources. + +If automatic selection finds no physical managed microphone source, the suite probes direct ALSA capture. It selects a card and PCM from the available device inventory, applies only mixer controls exposed by that card, and runs `arecord -D `. This discovers the VA-DMIC capture route from its controls without selecting a form factor or assuming card `0`. When audio remoteproc preflight proves that audio is applicable, failure of both managed-source discovery and direct ALSA probing is reported as FAIL so the image or runtime regression remains tracked. The same absence remains SKIP only when runtime preflight found no applicable audio subsystem. + +An explicit backend request is never replaced: + +- `--backend pipewire` or `AUDIO_BACKEND=pipewire` runs `pw-record` only and fails if the requested physical microphone source is unavailable. +- `--backend pulseaudio` or `AUDIO_BACKEND=pulseaudio` runs `parecord` only and fails if the requested source is unavailable. +- `--backend alsa` or `AUDIO_BACKEND=alsa` runs `arecord` with the discovered ALSA route. + +## Hardware-Specific ALSA Profiles + +`--alsa-profile ` selects a declarative, hardware-specific mixer profile +instead of automatic route discovery. This requires `--backend alsa` (set +automatically when omitted) and `--device ` identifying which physical +capture route on that hardware to configure. + +Currently supported: + +```sh +./run.sh --backend alsa --alsa-profile hamoa --device handset \ + --config-name record_config1 --record-seconds 5s +./run.sh --backend alsa --alsa-profile hamoa --device headset \ + --config-name record_config1 --record-seconds 5s +``` + +`ALSA_PROFILE` and `DEVICE` environment variables are equivalent to +`--alsa-profile`/`--device`, for YAML-driven CI jobs that set environment +variables rather than CLI flags. + +The ALSA card for the requested profile is resolved dynamically at runtime by +a distinctive mixer control (never a hardcoded card index), so the correct +card is selected regardless of registration order. When an alsaucm UCM +configuration tree is installed for the resolved card +(`/usr/share/alsa/ucm2//`), the suite configures the route through +`alsaucm` first; it falls back to this repository's own +`Runner/utils/audio_profiles/*.profile` + `amixer` mechanism when UCM is not +usable on the running image. Both mechanisms are verified afterward by +confirming the resulting PCM device is enumerated for the requested +direction. An unsupported `--alsa-profile`/`--device` combination SKIPs +cleanly rather than failing. + +Mixer writes to the same hardware card are serialized across concurrent test +runs via a per-card lock, so parallel `--device handset`/`--device headset` +invocations on the same card do not interleave their mixer writes. + +## Audio Remoteproc Preflight + +Before backend discovery, the suite enumerates runtime remoteprocs and checks the configured firmware and topology under `/lib/firmware` or `/usr/lib/firmware`. It recognizes audio DSP identities and the Shikra modem-hosted audio path. A remoteproc named `modem` is ignored on non-Shikra targets because it is not their audio preflight dependency. + +If the matching remoteproc is `running`, the suite records its name and firmware evidence. If it is `offline`, the suite starts it only after confirming the corresponding firmware is provisioned. Generic audio DSP paths also require adjacent topology evidence. Shikra's modem-hosted path does not require an adjacent topology file because its public image packages `qdsp6sw` modem firmware and `lpaicp` audio firmware separately. The suite never stops a remoteproc. Platforms with no applicable audio remoteproc continue normally. Provisioning or start issues produce a clean skip with the reason. + +## Overlay Build Support + +AudioReach is not enabled for Ubuntu. An explicit `./run.sh --overlay` request +reports SKIP before any AudioReach source refresh or package operation. Rerun +without `--overlay` to validate the Ubuntu base Audio stack. + +On CentOS Stream 10, an explicit `./run.sh --overlay` request ensures EPEL and +the Qualcomm aarch64 and noarch RPM repositories, refreshes DNF metadata, and +installs `audioreach-dkms`, `audioreach-pal`, and +`audioreach-pipewire-plugin`. If the DKMS package changes, reboot before +rerunning validation. Yocto remains image-provided and non-installing. + +Backend client checks run after recovery of the selected backend's complete +playback and recording client package set. Package recovery failure is reported +as FAIL. A client that remains unavailable after successful recovery produces +a clean SKIP for the unavailable recording path. + +For overlay builds using audioreach kernel modules, the test automatically: - Detects the overlay build configuration - Sets required DMA heap permissions - Restarts PipeWire service @@ -225,15 +261,15 @@ cd Runner/suites/Multimedia/Audio/AudioRecord Environment Variables: Variable Description Default -AUDIO_BACKEND Selects backend: pipewire, pulseaudio, or alsa auto-detect +AUDIO_BACKEND Selects backend: pipewire, pulseaudio, or alsa auto-detect SOURCE_CHOICE Recording source: mic or null mic CONFIG_NAMES Test specific configs (e.g., "record_config1 record_config2") record_config1 CONFIG_FILTER Filter configs by pattern (e.g., "48KHz" or "2ch") unset DURATIONS Recording durations: short, medium, long (legacy mode only) "" RECORD_SECONDS Number of seconds to record (e.g., 5s, 10s) 30s LOOPS Number of recording loops 1 -TIMEOUT Explicit timeout override (e.g., 15s, 0=automatic) 0 -AUDIO_RECORD_START_GRACE Startup headroom added to the automatic watchdog 5 +TIMEOUT Explicit timeout override (e.g., 15s, 0=automatic) 0 +AUDIO_RECORD_START_GRACE Startup headroom added to the automatic watchdog 5 STRICT Strict mode (0=disabled, 1=enabled, fail on any error) 0 DMESG_SCAN Scan dmesg for errors after recording 1 VERBOSE Enable verbose logging 0 @@ -244,47 +280,47 @@ LAVA_TESTCASE_ID Unique testcase ID written into the .res file for LAVA CLI Options: Option Description ---backend Select backend: pipewire, pulseaudio, or alsa +--backend Select backend: pipewire, pulseaudio, or alsa --source Recording source: mic or null --config-name Test specific configs using record_config1-record_config10 or descriptive names (space-separated) --config-filter Filter configs by sample rate or channels (space-separated patterns) --record-seconds Number of seconds to record (e.g., 5s, 10s) --durations Recording durations: short, medium, long (legacy mode only) --loops Number of recording loops ---timeout Override the automatic duration-based watchdog ---start-grace Recorder startup headroom, default: 5 +--timeout Override the automatic duration-based watchdog +--start-grace Recorder startup headroom, default: 5 --strict [0|1] Enable strict mode (0=disabled, 1=enabled) --no-dmesg Disable dmesg scan --res-suffix Suffix for unique result file and log directory (e.g., "Config01" generates AudioRecord_Config01.res and results/AudioRecord_Config01/) --lava-testcase-id Unique testcase ID written into the .res file for LAVA (e.g., "AudioRecord_Config01") --junit Write JUnit XML output --verbose Enable verbose logging ---help Show usage instructions -``` - -### Recording watchdog and clock handling - -When `TIMEOUT=0`, AudioRecord uses an automatic watchdog for recorder commands. -The watchdog duration is the requested recording duration plus -`AUDIO_RECORD_START_GRACE`, which defaults to five seconds. The additional time -allows PipeWire or PulseAudio to establish the stream without reducing the WAV -duration being validated. An explicit `--timeout` value overrides the automatic -watchdog and does not receive startup grace. - -Watchdog deadlines and elapsed-time reporting use monotonic uptime rather than -the system wall clock. RTC or NTP corrections during recording therefore do not -terminate the recorder early or produce an invalid elapsed time. When the wall -clock changes materially during a case, stdout includes a diagnostic such as: - -```text -[WARN] ... [record_8KHz_1ch] [AUDIO-CLOCK] wall-clock correction detected step=4581451s, watchdog and elapsed accounting used monotonic time -``` - -The requested recording duration remains the WAV validation target. Startup -grace affects only when the recorder is stopped, so a valid recording may be -longer than the requested minimum. - -Sample Output: +--help Show usage instructions +``` + +### Recording watchdog and clock handling + +When `TIMEOUT=0`, AudioRecord uses an automatic watchdog for recorder commands. +The watchdog duration is the requested recording duration plus +`AUDIO_RECORD_START_GRACE`, which defaults to five seconds. The additional time +allows PipeWire or PulseAudio to establish the stream without reducing the WAV +duration being validated. An explicit `--timeout` value overrides the automatic +watchdog and does not receive startup grace. + +Watchdog deadlines and elapsed-time reporting use monotonic uptime rather than +the system wall clock. RTC or NTP corrections during recording therefore do not +terminate the recorder early or produce an invalid elapsed time. When the wall +clock changes materially during a case, stdout includes a diagnostic such as: + +```text +[WARN] ... [record_8KHz_1ch] [AUDIO-CLOCK] wall-clock correction detected step=4581451s, watchdog and elapsed accounting used monotonic time +``` + +The requested recording duration remains the WAV validation target. Startup +grace affects only when the recorder is stopped, so a valid recording may be +longer than the requested minimum. + +Sample Output: **Example 1: Testing specific config using config naming** ``` @@ -292,20 +328,20 @@ sh-5.3# ./run.sh --config-name "record_config1" [INFO] 2026-01-02 12:00:46 - Base build detected (no audioreach modules), skipping overlay setup [INFO] 2026-01-02 12:00:46 - ---------------- Starting AudioRecord ---------------- [INFO] 2026-01-02 12:00:46 - Platform Details: machine='Qualcomm Technologies, Inc. Robotics RB3gen2' target='Kodiak' kernel='6.18.0-00393-g27507852413b' arch='aarch64' -[INFO] 2026-01-02 12:00:46 - Args: backend=auto source=mic overlay=0 loops=1 durations='' record_seconds=30s timeout=0 start_grace=5 strict=0 signal_strict=0 dmesg=1 bootstrap=auto runtime_dir=auto +[INFO] 2026-01-02 12:00:46 - Args: backend=auto source=mic overlay=0 loops=1 durations='' record_seconds=30s timeout=0 start_grace=5 strict=0 signal_strict=0 dmesg=1 bootstrap=auto runtime_dir=auto [INFO] 2026-01-02 12:00:46 - Backend fallback chain: pipewire pulseaudio alsa [INFO] 2026-01-02 12:00:46 - Using backend: pipewire [INFO] 2026-01-02 12:00:46 - Routing to source: id/name=45 label='Built-in Audio internal Mic' choice=mic -[INFO] 2026-01-02 12:00:46 - Watchdog/timeout: automatic per case, requested duration plus start_grace=5s +[INFO] 2026-01-02 12:00:46 - Watchdog/timeout: automatic per case, requested duration plus start_grace=5s [INFO] 2026-01-02 12:00:46 - Using config discovery mode [INFO] 2026-01-02 12:00:46 - Discovered 1 configs to test [INFO] 2026-01-02 12:00:46 - [record_8KHz_1ch] Using config: record_config1 (rate=8000Hz channels=1) [INFO] 2026-01-02 12:00:46 - [record_8KHz_1ch] loop 1/1 start=2026-01-02T12:00:46Z rate=8000Hz channels=1 backend=pipewire source=mic(45) [INFO] 2026-01-02 12:00:46 - [record_8KHz_1ch] exec: pw-record -v --rate=8000 --channels=1 "results/AudioRecord/record_8KHz_1ch.wav" -[INFO] 2026-01-02 12:01:16 - Recorder ended through expected watchdog timeout rc=124, validated WAV payload is accepted +[INFO] 2026-01-02 12:01:16 - Recorder ended through expected watchdog timeout rc=124, validated WAV payload is accepted [INFO] 2026-01-02 12:01:16 - [record_8KHz_1ch] evidence: pw_streaming=1 pa_streaming=0 alsa_running=1 asoc_path_on=1 bytes=482634 pw_log=1 [PASS] 2026-01-02 12:01:16 - [record_8KHz_1ch] loop 1 OK (rc=0, 30s, bytes=482634) -[INFO] 2026-01-02 12:01:16 - No relevant, non-benign errors for modules [snd|asoc|audio|lpass|q6|codec|xrun|underrun|overrun|pipewire|pulseaudio] in recent dmesg. +[INFO] 2026-01-02 12:01:16 - No relevant, non-benign errors for modules [snd|asoc|audio|lpass|q6|codec|xrun|underrun|overrun|pipewire|pulseaudio] in recent dmesg. [INFO] 2026-01-02 12:01:16 - Summary: total=1 pass=1 fail=0 skip=0 [PASS] 2026-01-02 12:01:16 - AudioRecord PASS ``` diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/run.sh b/Runner/suites/Multimedia/Audio/AudioRecord/run.sh index f1cdc889..ae6c1e8a 100755 --- a/Runner/suites/Multimedia/Audio/AudioRecord/run.sh +++ b/Runner/suites/Multimedia/Audio/AudioRecord/run.sh @@ -92,6 +92,8 @@ export AUDIO_OVERLAY_REQUESTED # ---------------- Defaults / CLI ---------------- AUDIO_BACKEND="${AUDIO_BACKEND:-}" +ALSA_PROFILE="${ALSA_PROFILE:-generic}" # ALSA profile (hamoa, generic) +DEVICE="${DEVICE:-}" # Device type for ALSA profiles (handset, headset) SRC_CHOICE="${SRC_CHOICE:-mic}" # mic|null DURATIONS="" # Will be set to default only if using legacy mode RECORD_SECONDS="${RECORD_SECONDS:-30s}" # DEFAULT: 30s; 'auto' maps short/med/long @@ -132,6 +134,8 @@ usage() { cat < "$RES_FILE" + exit 0 + fi +fi + # Resolve backend (allow minimal-build ALSA capture fallback) if [ -z "$AUDIO_BACKEND" ]; then AUDIO_BACKEND="$(audio_run_helper_as_test_user --require-session detect_audio_backend 2>/dev/null || echo "")" @@ -778,10 +805,43 @@ case "$AUDIO_BACKEND:$SRC_CHOICE" in SRC_ID="" ;; alsa:*) - if [ "$ALSA_CAPTURE_PROBED" -eq 1 ] && [ -n "$AUDIO_ALSA_CAPTURE_DEVICE" ]; then - SRC_ID="$AUDIO_ALSA_CAPTURE_DEVICE" + # Hardware-specific ALSA profiles (e.g., Hamoa) configure mixer controls + # and provide an explicit device path for the requested device, instead + # of discovering a source through the generic ALSA capture probe below. + if [ "$ALSA_PROFILE" != "generic" ] && [ -n "$DEVICE" ]; then + if ! CHECK_DEPS_NO_EXIT=1 check_dependencies amixer grep sed flock; then + log_skip "$TESTNAME SKIP - missing ALSA mixer utilities required by profile '$ALSA_PROFILE'" + echo "$RESULT_TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + + log_info "Using ALSA profile: $ALSA_PROFILE for device: $DEVICE" + case "$ALSA_PROFILE:$DEVICE" in + hamoa:handset|hamoa:headset) + SRC_ID="$(setup_alsa_profile_route "$ALSA_PROFILE" "$DEVICE" capture "$LOGDIR")" + profile_setup_rc=$? + ;; + *) + log_skip "$TESTNAME SKIP - unsupported ALSA profile/device: $ALSA_PROFILE/$DEVICE" + echo "$RESULT_TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac + + if [ "$profile_setup_rc" -eq 0 ]; then + ALSA_CAPTURE_PROBED=1 + log_info "ALSA profile configured successfully, device: $SRC_ID" + else + log_error "Failed to setup ALSA profile: $ALSA_PROFILE for device: $DEVICE" + echo "$RESULT_TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi else - SRC_ID="$(audio_run_helper_as_test_user alsa_pick_capture)" + if [ "$ALSA_CAPTURE_PROBED" -eq 1 ] && [ -n "$AUDIO_ALSA_CAPTURE_DEVICE" ]; then + SRC_ID="$AUDIO_ALSA_CAPTURE_DEVICE" + else + SRC_ID="$(audio_run_helper_as_test_user alsa_pick_capture)" + fi fi ;; esac @@ -891,13 +951,22 @@ if [ "$AUDIO_BACKEND" = "alsa" ]; then hw:[0-9]*,[0-9]*|plughw:[0-9]*,[0-9]*) : ;; *) + # Auto-picked candidates use the "plughw:" prefix, not "hw:" - a raw + # hw: device records at the hardware's native rate only and does not + # go through ALSA's "plug" resampling layer, so a requested rate that + # differs from the native rate (e.g. an 8000Hz capture config on + # hardware whose native rate is 48000Hz) silently records at the + # native rate instead, which the WAV validator then correctly reports + # as a sample-rate mismatch. plughw: supports the same native rate + # plus correct resampling for every other requested rate, so no + # capability is lost by preferring it for this auto-pick fallback. cand="$(audio_run_as_test_user arecord -l 2>/dev/null | - sed -n 's/^card[[:space:]]*\([0-9][0-9]*\).*device[[:space:]]*\([0-9][0-9]*\).*/hw:\1,\2/p' | + sed -n 's/^card[[:space:]]*\([0-9][0-9]*\).*device[[:space:]]*\([0-9][0-9]*\).*/plughw:\1,\2/p' | head -n 1)" if [ -z "$cand" ]; then - 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)" + cand="$(sed -n 's/^\([0-9][0-9]*\)-\([0-9][0-9]*\):.*capture.*/plughw:\1,\2/p' /proc/asound/pcm 2>/dev/null | head -n 1)" fi - if [ -n "$cand" ] && printf '%s\n' "$cand" | grep -Eq '^hw:[0-9][0-9]*,[0-9][0-9]*$'; then + if [ -n "$cand" ] && printf '%s\n' "$cand" | grep -Eq '^plughw:[0-9][0-9]*,[0-9][0-9]*$'; then SRC_ID="$cand" log_info "ALSA auto-pick: using $SRC_ID" else diff --git a/Runner/utils/alsa_common.sh b/Runner/utils/alsa_common.sh new file mode 100755 index 00000000..2de704e7 --- /dev/null +++ b/Runner/utils/alsa_common.sh @@ -0,0 +1,701 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# ALSA hardware-profile helpers for platform-specific mixer configuration. +# Provides mixer configuration and device management driven by declarative +# ".profile" files under Runner/utils/audio_profiles/, dispatched through a +# small set of registries keyed by (profile, device, operation) so that +# AudioPlayback/run.sh and AudioRecord/run.sh never name a specific hardware +# profile directly - they only pass through whatever --alsa-profile/--device +# the caller supplied, plus the operation (playback/capture) implied by which +# suite is running. Adding support for a new hardware profile means adding +# its profile files plus new entries in the registries below (identifying +# control, PCM-device-index, profile filename, UCM device name); no change +# to either run.sh, or to setup_alsa_profile_route() below, is required. +# +# Two mechanisms drive the same underlying hardware route for a profile: +# a raw-amixer profile mechanism (audio_profiles/*.profile + +# setup_audio_route()), and an alsaucm/UCM-configuration-tree mechanism +# (issue #454). setup_alsa_profile_route() prefers UCM when it is actually +# usable on the running image and falls back to the amixer-profile +# mechanism otherwise, so neither path is assumed to be the only one +# available. +# +# This library assumes functestlib.sh has already been sourced by the +# calling script, which provides log_info, log_warn, log_error, log_pass, +# and log_fail. It is sourced by audio_common.sh so every audio suite gets +# these helpers automatically; TOOLS must be exported by init_env. + +# --------------------------------------------------------------------------- +# Profile registries +# --------------------------------------------------------------------------- +# These four functions are the only place with hardware-profile-specific +# ALSA knowledge. Everything else in this file and in the calling suites +# operates purely in terms of (profile, device, operation). + +# Return the mixer control name used to identify a profile's card at +# runtime. Resolving by a distinctive control (rather than a hardcoded +# card-name string) keeps card selection correct regardless of how a card +# reports its id/name on different images. +# Args: $1 - profile name (e.g., hamoa) +# Returns: control name on stdout, 0 on success, 1 if profile unknown +_alsa_identifying_control() { + case "$1" in + hamoa) printf '%s\n' "WSA2 WSA RX0 MUX" ;; + *) return 1 ;; + esac +} + +# Return the topology-fixed ALSA PCM device number for a profile's +# device/operation combination. PCM device numbers identify the PCM stream +# within the card and remain constant regardless of card registration order. +# Args: $1 - profile name +# $2 - device name (e.g., handset, headset) +# $3 - operation (playback or capture) +# Returns: PCM device number on stdout, 0 on success, 1 if combination unknown +_alsa_pcm_device_registry_lookup() { + case "$1:$2:$3" in + hamoa:handset:playback) printf '%s\n' 1 ;; # 4-way speaker system (WSA2 + WSA) + hamoa:headset:playback) printf '%s\n' 0 ;; # Stereo headphones (RX codec) + hamoa:handset:capture) printf '%s\n' 3 ;; # Built-in microphone (VA_DMIC) + hamoa:headset:capture) printf '%s\n' 2 ;; # Headset microphone (SWR_MIC) + *) return 1 ;; + esac +} + +# Return the audio_profiles/*.profile filename for a profile's +# device/operation combination. Centralizing this mapping keeps the profile +# filename convention ("__.profile") in one +# place, used by both setup and validation call sites. +# Args: $1 - profile name +# $2 - device name +# $3 - operation +# Returns: profile filename (without directory) on stdout, 0 on success, +# 1 if combination unknown +_alsa_profile_file_registry_lookup() { + case "$1:$2:$3" in + hamoa:handset:playback) printf '%s\n' "hamoa_playback_handset.profile" ;; + hamoa:headset:playback) printf '%s\n' "hamoa_playback_headset.profile" ;; + hamoa:handset:capture) printf '%s\n' "hamoa_capture_handset.profile" ;; + hamoa:headset:capture) printf '%s\n' "hamoa_capture_headset.profile" ;; + *) return 1 ;; + esac +} + +# Return the UCM "_enadev" device name for a profile's device/operation +# combination (issue #454). This is the UCM equivalent of the raw-amixer +# PROFILE_MIXER_CONTROLS mechanism above: alsaucm applies the same hardware +# route declaratively via a pre-installed UCM configuration tree, selected +# by _alsa_ucm_available() at runtime rather than this repo's own +# audio_profiles/*.profile files. +# Args: $1 - profile name +# $2 - device name (e.g., handset, headset) +# $3 - operation (playback or capture) +# Returns: UCM device-enable name on stdout, 0 on success, 1 if the +# combination is unknown +_alsa_ucm_device_registry_lookup() { + case "$1:$2:$3" in + hamoa:handset:playback) printf '%s\n' "Speaker" ;; + hamoa:headset:playback) printf '%s\n' "Headphones" ;; + hamoa:handset:capture) printf '%s\n' "Mic" ;; + hamoa:headset:capture) printf '%s\n' "Headset" ;; + *) return 1 ;; + esac +} + +# --------------------------------------------------------------------------- +# Card resolution +# --------------------------------------------------------------------------- + +# Resolve the ALSA card index for a given profile's hardware at runtime. +# Identifies the card by the profile's distinctive mixer control (via +# _alsa_identifying_control()) rather than a platform/model name string, so +# the correct card is selected regardless of registration order (a USB/HDMI +# audio device enumerated earlier would otherwise shift a hardcoded index) +# and regardless of how a card reports its id/name string on different images. +# Args: $1 - profile name +# Returns: card index on stdout, 0 on success, 1 if profile unknown, card not +# found, or ambiguous +resolve_alsa_card_index() { + raci_profile="$1" + + if ! command -v amixer >/dev/null 2>&1; then + log_error "resolve_alsa_card_index: amixer is unavailable" >&2 + return 1 + fi + + raci_identifying_control="$(_alsa_identifying_control "$raci_profile")" || { + log_error "resolve_alsa_card_index: unknown ALSA profile: $raci_profile" >&2 + return 1 + } + + raci_match_count=0 + raci_matched_card="" + + # Enumerate registered card indexes via the existing shared helper + # (audio_card_get_registered_cards, in audio_common.sh) rather than a + # fixed/implicit card list, so a USB/HDMI audio device enumerated earlier + # does not shift which index this loop checks. + for raci_card in $(audio_card_get_registered_cards 2>/dev/null | cut -d'|' -f1); do + if amixer -c "$raci_card" controls 2>/dev/null | grep -q "name='$raci_identifying_control'"; then + raci_match_count=$((raci_match_count + 1)) + raci_matched_card="$raci_card" + fi + done + + if [ "$raci_match_count" -eq 0 ]; then + log_error "ALSA profile '$raci_profile' card not found (no card exposes '$raci_identifying_control')" >&2 + return 1 + fi + + # Reject ambiguous matches to avoid operating on the wrong card. + if [ "$raci_match_count" -gt 1 ]; then + log_error "ALSA profile '$raci_profile': multiple matching cards found - ambiguous card selection" >&2 + return 1 + fi + + printf '%s\n' "$raci_matched_card" +} + +# Retrieve the ALSA plughw device identifier for a profile's device/operation +# combination. Resolves the card index at call time and combines it with the +# registry's topology-specific PCM device number to construct the identifier. +# Args: $1 - profile name +# $2 - device name (e.g., handset, headset) +# $3 - operation (playback or capture) +# Returns: device identifier on stdout, 0 on success, 1 if the combination is +# unknown or the card cannot be resolved +get_alsa_route_device() { + gard_profile="$1" + gard_device="$2" + gard_operation="$3" + + gard_card_index="$(resolve_alsa_card_index "$gard_profile")" || return 1 + gard_pcm_device="$(_alsa_pcm_device_registry_lookup "$gard_profile" "$gard_device" "$gard_operation")" || { + log_error "get_alsa_route_device: unknown profile/device/operation combination: $gard_profile/$gard_device/$gard_operation" >&2 + return 1 + } + + printf 'plughw:%s,%s\n' "$gard_card_index" "$gard_pcm_device" +} + +# Verify that a specific ALSA PCM device exists and is accessible. +# Parses the exact card/device numbers out of the identifier and checks the +# correct enumeration for the requested direction - capture devices must be +# validated with "arecord -l", not "aplay -l", since a device number can +# exist for one direction but not the other. +# Args: $1 - ALSA device identifier (e.g., plughw:0,1 or hw:0,3) +# $2 - direction: playback (default) or capture +# Returns: 0 if the exact card/device combination is found, 1 otherwise +check_alsa_device() { + cad_device="$1" + cad_direction="${2:-playback}" + + if [ -z "$cad_device" ]; then + log_error "check_alsa_device: device parameter is empty" >&2 + return 1 + fi + + cad_card_num="$(printf '%s' "$cad_device" | sed -n 's/.*:\([0-9][0-9]*\),.*/\1/p')" + cad_dev_num="$(printf '%s' "$cad_device" | sed -n 's/.*:[0-9][0-9]*,\([0-9][0-9]*\).*/\1/p')" + + if [ -z "$cad_card_num" ] || [ -z "$cad_dev_num" ]; then + log_error "check_alsa_device: cannot parse card/device from: $cad_device" >&2 + return 1 + fi + + if [ "$cad_direction" = "capture" ]; then + if arecord -l 2>/dev/null | grep -q "card ${cad_card_num}:.*device ${cad_dev_num}:"; then + log_info "ALSA capture device accessible: $cad_device" >&2 + return 0 + fi + else + if aplay -l 2>/dev/null | grep -q "card ${cad_card_num}:.*device ${cad_dev_num}:"; then + log_info "ALSA playback device accessible: $cad_device" >&2 + return 0 + fi + fi + + log_error "ALSA device not found: $cad_device (direction=$cad_direction)" >&2 + return 1 +} + +# Resolve the UCM card name (the "-c" argument alsaucm expects) for a given +# profile's hardware at runtime, for issue #454's alsaucm-based route setup. +# Reuses resolve_alsa_card_index() for identification (so UCM shares the same +# dynamic, ambiguity-rejecting card lookup as the amixer-profile mechanism), +# then reads the matched card's long description from the existing +# audio_card_get_registered_cards() helper - this is the same string ALSA +# reports after the colon in /proc/asound/cards, which is also the name +# alsaucm's "-c" option and the /usr/share/alsa/ucm2// directory use. +# Args: $1 - profile name +# Returns: UCM card name on stdout, 0 on success, 1 if the card cannot be +# resolved or its description cannot be read +resolve_alsa_ucm_card_name() { + raucn_profile="$1" + + raucn_card_index="$(resolve_alsa_card_index "$raucn_profile")" || return 1 + + raucn_name="$( + audio_card_get_registered_cards 2>/dev/null | + awk -F'|' -v idx="$raucn_card_index" '$1 == idx { print $3; found=1 } END { exit !found }' + )" + + if [ -z "$raucn_name" ]; then + log_error "resolve_alsa_ucm_card_name: could not read UCM name for card index $raucn_card_index" >&2 + return 1 + fi + + printf '%s\n' "$raucn_name" +} + +# Check whether the alsaucm-based route-setup path (issue #454) is usable for +# a given profile right now: the alsaucm binary must be present, the card +# must resolve, and that card's UCM configuration directory must actually +# exist under /usr/share/alsa/ucm2/ - alsaucm itself does not reliably fail +# fast on a missing/empty UCM tree, so this is checked explicitly up front +# rather than only discovered after attempting "set _verb HiFi". +# Args: $1 - profile name +# Returns: UCM card name on stdout, 0 if the UCM path is usable, 1 otherwise +# (never logs an error - callers treat an unusable UCM path as +# "fall back", not a failure) +_alsa_ucm_available() { + aua_profile="$1" + + command -v alsaucm >/dev/null 2>&1 || return 1 + + aua_card_name="$(resolve_alsa_ucm_card_name "$aua_profile" 2>/dev/null)" || return 1 + [ -n "$aua_card_name" ] || return 1 + + [ -d "/usr/share/alsa/ucm2/$aua_card_name" ] || return 1 + + printf '%s\n' "$aua_card_name" +} + +# Validate that a recorded audio file meets a minimum size requirement. +# Delegates size handling to the shared file_size_bytes() helper so behavior +# stays consistent with the rest of the Audio test suite rather than +# duplicating direct stat logic here. +# Args: $1 - path to recording file +# $2 - minimum size in bytes (default: 1024) +# Returns: 0 if valid, 1 otherwise +validate_alsa_recording() { + var_file="$1" + var_min_size="${2:-1024}" + + if [ ! -f "$var_file" ]; then + log_error "Recording file not found: $var_file" >&2 + return 1 + fi + + var_size="$(file_size_bytes "$var_file" 2>/dev/null || echo 0)" + if [ "$var_size" -lt "$var_min_size" ] 2>/dev/null; then + log_error "Recording file too small: $var_size bytes (expected >= $var_min_size): $var_file" >&2 + return 1 + fi + + log_info "Recording validated: $var_file ($var_size bytes)" >&2 + return 0 +} + +# --------------------------------------------------------------------------- +# Mixer control read/validate helpers +# --------------------------------------------------------------------------- + +# Verify that a specific ALSA mixer control is set to the expected value. +# Args: $1 - card index to query +# $2 - mixer control name (e.g., 'WSA2 WSA RX0 MUX') +# $3 - expected value (e.g., 'AIF1_PB', 'on'/'off', or a number) +# Returns: 0 if control matches expected value, 1 otherwise +check_mixer_control() { + cmc_card_index="$1" + cmc_control="$2" + cmc_expected="$3" + + if [ -z "$cmc_control" ]; then + log_error "check_mixer_control: control name is empty" >&2 + return 1 + fi + + cmc_output="$(amixer -c "$cmc_card_index" cget iface=MIXER,name="$cmc_control" 2>/dev/null)" + + if [ -z "$cmc_output" ]; then + log_error "Mixer control not found: $cmc_control" >&2 + return 1 + fi + + if printf '%s\n' "$cmc_output" | grep -q "type=ENUMERATED"; then + # For ENUM controls, "values=" reports the selected item as a numeric + # index. Resolve that index to its item name and accept a match against + # either the numeric index or the item name, since profile files may + # declare either form (amixer cset accepts both). + cmc_active_index="$(printf '%s\n' "$cmc_output" | grep "^ : values=" | sed 's/.*values=\([0-9][0-9]*\).*/\1/')" + cmc_active_value="$(printf '%s\n' "$cmc_output" | grep "Item #${cmc_active_index} '" | sed "s/.*Item #${cmc_active_index} '\\(.*\\)'.*/\\1/")" + if [ "$cmc_expected" = "$cmc_active_index" ] || [ "$cmc_expected" = "$cmc_active_value" ]; then + return 0 + fi + cmc_actual="$cmc_active_value" + elif printf '%s\n' "$cmc_output" | grep -q "type=BOOLEAN"; then + # amixer always reports boolean controls as "on"/"off" in cget output, + # even though cset accepts (and profile files store) "1"/"0" as the + # argument. Normalize the expected value to the same on/off form cget + # reports, so profile-driven 1/0 values compare correctly. + cmc_actual="$(printf '%s\n' "$cmc_output" | grep ": values=" | cut -d'=' -f2)" + case "$cmc_expected" in + 1) cmc_expected_norm="on" ;; + 0) cmc_expected_norm="off" ;; + *) cmc_expected_norm="$cmc_expected" ;; + esac + if [ "$cmc_actual" = "$cmc_expected_norm" ]; then + return 0 + fi + else + # Integer controls (e.g. volume): compare the values= field exactly. + cmc_actual="$(printf '%s\n' "$cmc_output" | grep ": values=" | cut -d'=' -f2)" + if [ "$cmc_actual" = "$cmc_expected" ]; then + return 0 + fi + fi + + log_error "Mixer control validation failed: $cmc_control" >&2 + log_error " Expected: $cmc_expected" >&2 + log_error " Actual: ${cmc_actual:-}" >&2 + return 1 +} + +# Read the current value of a mixer control in a form suitable for +# reapplying via "amixer cset" later. Used by setup_audio_route() to +# snapshot a control's state before writing a new value, so a later failure +# in the same profile can restore exactly what was there before. Mirrors the +# same ENUM/BOOLEAN/INTEGER parsing used by check_mixer_control(), since the +# value returned here must round-trip back through "amixer cset". +# Args: $1 - mixer control name +# $2 - card index to query +# Returns: current value on stdout (empty if control not found) +get_mixer_control_value() { + gmcv_control="$1" + gmcv_card="$2" + + gmcv_output="$(amixer -c "$gmcv_card" cget iface=MIXER,name="$gmcv_control" 2>/dev/null)" + if [ -z "$gmcv_output" ]; then + return 1 + fi + + if printf '%s\n' "$gmcv_output" | grep -q "type=ENUMERATED"; then + # amixer cset accepts the item name for enumerated controls, so resolve + # the active index to its name - this keeps the restore value + # unambiguous regardless of whether the profile stores names or indices. + gmcv_active_index="$(printf '%s\n' "$gmcv_output" | grep "^ : values=" | sed 's/.*values=\([0-9][0-9]*\).*/\1/')" + printf '%s\n' "$gmcv_output" | grep "Item #${gmcv_active_index} '" | sed "s/.*Item #${gmcv_active_index} '\\(.*\\)'.*/\\1/" + else + # BOOLEAN and INTEGER controls report their current value directly in + # the "values=" field, and both accept that same value back via cset. + printf '%s\n' "$gmcv_output" | grep ": values=" | cut -d'=' -f2 + fi +} + +# Restore mixer controls to previously captured values. Used by +# setup_audio_route() when a control write fails partway through applying a +# profile, so the card is not left in a mix of old and new control values. +# Args: $1 - card index +# $2 - newline-separated "control_name|previous_value" pairs +# $3 - mixer log file to append amixer output to +rollback_audio_route() { + rar_card="$1" + rar_pairs="$2" + rar_log="$3" + + # Reads via a heredoc (not a pipe) so this loop runs in the current shell + # rather than a subshell, and scopes IFS to just this "read" invocation + # instead of mutating the shell's global IFS for the duration of the loop. + while IFS= read -r rar_pair; do + [ -n "$rar_pair" ] || continue + rar_name="${rar_pair%%|*}" + rar_value="${rar_pair#*|}" + amixer -c "$rar_card" cset iface=MIXER,name="$rar_name" "$rar_value" >>"$rar_log" 2>&1 + done <&2 + return 1 + fi + + # shellcheck disable=SC1090 + . "$sar_profile_file" + + if [ -z "$PROFILE_MIXER_CONTROLS" ]; then + log_error "setup_audio_route: profile defines no PROFILE_MIXER_CONTROLS: $sar_profile_file" >&2 + return 1 + fi + + # Waits (bounded) for the lock rather than failing immediately on + # contention, since the lock exists to serialize concurrent mixer writes + # on the same card - a brief wait for another in-flight writer to finish + # is the expected, correct outcome, not a failure. + sar_lockfile="/var/lock/alsa_audio_${sar_card_index}.lock" + exec 8>"$sar_lockfile" + if ! flock -w 10 8; then + log_error "setup_audio_route: could not acquire mixer lock after 10s: $sar_lockfile" >&2 + exec 8>&- + return 1 + fi + + sar_applied_pairs="" + sar_rc=0 + + # Reads via a heredoc (not a pipe) so this loop - and the sar_rc/ + # sar_applied_pairs it sets - run in the current shell rather than a + # subshell, and scopes IFS to just this "read" invocation instead of + # mutating the shell's global IFS for the duration of the loop. + while IFS= read -r sar_control_line; do + [ -n "$sar_control_line" ] || continue + sar_ctrl_name="${sar_control_line%%|*}" + sar_ctrl_value="${sar_control_line#*|}" + + sar_prev_value="$(get_mixer_control_value "$sar_ctrl_name" "$sar_card_index")" + + if ! amixer -c "$sar_card_index" cset iface=MIXER,name="$sar_ctrl_name" "$sar_ctrl_value" >>"$sar_mixer_log" 2>&1; then + log_error "setup_audio_route: failed to set '$sar_ctrl_name' to '$sar_ctrl_value' - restoring previously applied controls" >&2 + sar_rc=1 + break + fi + + sar_applied_pairs="${sar_applied_pairs}${sar_ctrl_name}|${sar_prev_value} +" + done <&- + + return "$sar_rc" +} + +# Validate complete mixer state for a specific profile/device/operation +# combination. Uses the same profile file that setup_audio_route() applied, +# so every control that was configured is also verified here - the profile +# is the single source of truth for both setup and validation, instead of a +# separate, easily-drifting shortlist of "key" controls. +# Args: $1 - card index to query +# $2 - path to profile file (same file setup_audio_route() applied) +# Returns: 0 if every control in the profile matches its configured value, +# 1 otherwise +validate_mixer_state() { + vms_card_index="$1" + vms_profile_file="$2" + + if [ ! -r "$vms_profile_file" ]; then + log_error "validate_mixer_state: profile file not readable: $vms_profile_file" >&2 + return 1 + fi + + # shellcheck disable=SC1090 + . "$vms_profile_file" + + if [ -z "$PROFILE_MIXER_CONTROLS" ]; then + log_error "validate_mixer_state: profile defines no PROFILE_MIXER_CONTROLS: $vms_profile_file" >&2 + return 1 + fi + + log_info "Validating mixer state against profile: $vms_profile_file" >&2 + + # Reads via a heredoc (not a pipe) so a failing check_mixer_control() can + # "return 1" directly from this loop instead of only exiting a subshell, + # and scopes IFS to just this "read" invocation instead of mutating the + # shell's global IFS for the duration of the loop. + while IFS= read -r vms_control_line; do + [ -n "$vms_control_line" ] || continue + vms_ctrl_name="${vms_control_line%%|*}" + vms_ctrl_value="${vms_control_line#*|}" + check_mixer_control "$vms_card_index" "$vms_ctrl_name" "$vms_ctrl_value" || return 1 + done <&2 + return 0 +} + +# --------------------------------------------------------------------------- +# UCM (Use Case Manager) route (issue #454) +# --------------------------------------------------------------------------- +# +# Ubuntu/UCM-configured images apply a route through "alsaucm" and a +# pre-installed UCM configuration tree (see +# https://github.com/alsa-project/alsa-ucm-conf, installed under +# /usr/share/alsa/ucm2//) instead of this file's own +# audio_profiles/*.profile + setup_audio_route() amixer mechanism. Both +# mechanisms drive the same underlying hardware route; setup_alsa_profile_route() +# below prefers UCM when it is actually usable on the running image and +# falls back to the amixer-profile mechanism otherwise, so neither path is +# ever assumed to be the only one available. + +# Configure the hardware route for a profile's device/operation combination +# via "alsaucm set _verb HiFi set _enadev ", then verify the exact +# PCM device is enumerated for the requested direction. UCM's own +# configuration tree is the single source of truth for the mixer state it +# applies, so (unlike the amixer-profile path) there is no separate +# per-control validation step here - the only locally-known fact to verify +# is that the resulting PCM device is actually present. +# Args: $1 - profile name (e.g., hamoa) +# $2 - device name (e.g., handset, headset) +# $3 - operation (playback or capture) +# $4 - optional log directory path for the alsaucm log +# Returns: 0 on success, 1 if card/UCM-name resolution, "alsaucm set", or +# device validation fails +setup_alsa_ucm_route() { + saur_profile="$1" + saur_device="$2" + saur_operation="$3" + saur_logdir="${4:-}" + + saur_ucm_card="$(_alsa_ucm_available "$saur_profile")" || return 1 + + saur_ucm_device="$(_alsa_ucm_device_registry_lookup "$saur_profile" "$saur_device" "$saur_operation")" || { + log_error "setup_alsa_ucm_route: unknown profile/device/operation combination: $saur_profile/$saur_device/$saur_operation" >&2 + return 1 + } + + if [ -n "$saur_logdir" ]; then + mkdir -p "$saur_logdir" 2>/dev/null || true + saur_log="$saur_logdir/ucm_${saur_device}_${saur_operation}.log" + else + saur_log="./ucm_${saur_device}_${saur_operation}.log" + fi + + log_info "Configuring UCM route for profile=$saur_profile device=$saur_device operation=$saur_operation (ucm_card=$saur_ucm_card, enadev=$saur_ucm_device)" >&2 + + if ! alsaucm -c "$saur_ucm_card" set _verb HiFi set _enadev "$saur_ucm_device" >>"$saur_log" 2>&1; then + log_error "setup_alsa_ucm_route: 'alsaucm -c $saur_ucm_card set _verb HiFi set _enadev $saur_ucm_device' failed, see $saur_log" >&2 + return 1 + fi + + saur_device_id="$(get_alsa_route_device "$saur_profile" "$saur_device" "$saur_operation")" || { + log_error "setup_alsa_ucm_route: failed to resolve device identifier for $saur_profile/$saur_device/$saur_operation" >&2 + return 1 + } + + if ! check_alsa_device "$saur_device_id" "$saur_operation"; then + log_error "setup_alsa_ucm_route: PCM device not enumerated: $saur_device_id (operation=$saur_operation)" >&2 + return 1 + fi + + log_info "UCM route ready: profile=$saur_profile device=$saur_device operation=$saur_operation -> $saur_device_id" >&2 + printf '%s\n' "$saur_device_id" +} + +# --------------------------------------------------------------------------- +# Generic profile dispatch +# --------------------------------------------------------------------------- +# +# This is the single entry point invoked from AudioPlayback/run.sh and +# AudioRecord/run.sh via: +# --backend alsa --alsa-profile --device +# +# run.sh validates (profile, device) against an explicit allowlist (never +# dynamic function-name construction - see the "no eval/dynamic dispatch" +# architectural requirement) before calling this function, then passes the +# literal profile/device/operation strings straight through. Adding a new +# hardware profile means adding entries to the four registries above +# (_alsa_identifying_control, _alsa_pcm_device_registry_lookup, +# _alsa_profile_file_registry_lookup, _alsa_ucm_device_registry_lookup) +# plus the profile files themselves - this function and its caller never +# change. setup_alsa_profile_route() tries the UCM route first when usable +# on the running image, falling back to the amixer-profile route otherwise. + +# Configure the ALSA mixer for a profile's device/operation route, then +# verify the exact PCM device is enumerated for the requested direction and +# that every mixer control in the profile reads back the expected value. +# Args: $1 - profile name (e.g., hamoa) +# $2 - device name (e.g., handset, headset) +# $3 - operation (playback or capture) +# $4 - optional log directory path for mixer logs +# Returns: 0 on success, 1 if card resolution, mixer setup, or device/state +# validation fails +setup_alsa_profile_route() { + sapr_profile="$1" + sapr_device="$2" + sapr_operation="$3" + sapr_logdir="${4:-}" + + sapr_ucm_card="$(_alsa_ucm_available "$sapr_profile" 2>/dev/null)" + if [ -n "$sapr_ucm_card" ]; then + log_info "UCM configuration tree found for profile=$sapr_profile (card=$sapr_ucm_card) - using alsaucm route setup" >&2 + setup_alsa_ucm_route "$sapr_profile" "$sapr_device" "$sapr_operation" "$sapr_logdir" && return 0 + log_warn "setup_alsa_profile_route: alsaucm route setup failed for $sapr_profile/$sapr_device/$sapr_operation - falling back to amixer profile mechanism" >&2 + fi + + sapr_card_index="$(resolve_alsa_card_index "$sapr_profile")" || return 1 + + sapr_profile_filename="$(_alsa_profile_file_registry_lookup "$sapr_profile" "$sapr_device" "$sapr_operation")" || { + log_error "setup_alsa_profile_route: unknown profile/device/operation combination: $sapr_profile/$sapr_device/$sapr_operation" >&2 + return 1 + } + sapr_profile_file="${TOOLS:-.}/audio_profiles/$sapr_profile_filename" + + if [ -n "$sapr_logdir" ]; then + mkdir -p "$sapr_logdir" 2>/dev/null || true + sapr_mixer_log="$sapr_logdir/mixer_${sapr_device}_${sapr_operation}.log" + else + sapr_mixer_log="./mixer_${sapr_device}_${sapr_operation}.log" + fi + + log_info "Configuring ALSA mixer for profile=$sapr_profile device=$sapr_device operation=$sapr_operation (card=$sapr_card_index)" >&2 + setup_audio_route "$sapr_card_index" "$sapr_profile_file" "$sapr_mixer_log" || return 1 + log_info "Mixer configured successfully for $sapr_device $sapr_operation" >&2 + + sapr_device_id="$(get_alsa_route_device "$sapr_profile" "$sapr_device" "$sapr_operation")" || { + log_error "setup_alsa_profile_route: failed to resolve device identifier for $sapr_profile/$sapr_device/$sapr_operation" >&2 + return 1 + } + + if ! check_alsa_device "$sapr_device_id" "$sapr_operation"; then + log_error "setup_alsa_profile_route: PCM device not enumerated: $sapr_device_id (operation=$sapr_operation)" >&2 + return 1 + fi + + if ! validate_mixer_state "$sapr_card_index" "$sapr_profile_file"; then + log_error "setup_alsa_profile_route: mixer state validation failed after setup for $sapr_profile/$sapr_device/$sapr_operation" >&2 + return 1 + fi + + log_info "ALSA profile route ready: profile=$sapr_profile device=$sapr_device operation=$sapr_operation -> $sapr_device_id" >&2 + printf '%s\n' "$sapr_device_id" +} diff --git a/Runner/utils/audio_common.sh b/Runner/utils/audio_common.sh index e3104459..b50b30c4 100755 --- a/Runner/utils/audio_common.sh +++ b/Runner/utils/audio_common.sh @@ -11,6 +11,14 @@ if [ -n "${TOOLS:-}" ] && . "$TOOLS/lib_pkg_provider.sh" fi +# Reuse the hardware ALSA profile helpers (e.g. Hamoa) directly. +if [ -n "${TOOLS:-}" ] && + [ -r "$TOOLS/alsa_common.sh" ] && + ! command -v resolve_alsa_card_index >/dev/null 2>&1; then + # shellcheck disable=SC1091 + . "$TOOLS/alsa_common.sh" +fi + # Check whether a command exists in PATH. # Used by bootstrap helpers before attempting backend startup. have_cmd() { @@ -1738,14 +1746,19 @@ build_backend_chain() { } # Pick a plausible ALSA capture device. -# Returns something like hw:0,0 if available, else "default". +# Returns something like plughw:0,0 if available, else "default". +# plughw: is used rather than hw: because a raw hw: device records at the +# hardware's native rate only and does not go through ALSA's "plug" +# resampling layer, so a caller that requests a rate different from the +# native rate silently gets the native rate instead. plughw: supports the +# native rate as well as correct resampling for any other requested rate. alsa_pick_capture() { command -v arecord >/dev/null 2>&1 || return 1 # Prefer the first real capture device from `arecord -l` arecord -l 2>/dev/null | awk ' /card [0-9]+: .*device [0-9]+:/ { if (match($0, /card ([0-9]+):/, c) && match($0, /device ([0-9]+):/, d)) { - printf("hw:%s,%s\n", c[1], d[1]); + printf("plughw:%s,%s\n", c[1], d[1]); exit 0; } } @@ -3773,7 +3786,12 @@ audio_probe_alsa_capture_profile() { esac fi - extra_devices="$(sed -n 's/^\([0-9][0-9]*\)-\([0-9][0-9]*\):.*capture.*/hw:\1,\2/p' /proc/asound/pcm 2>/dev/null)" + # /proc/asound/pcm entries are built as plughw: candidates, not hw:, for + # the same reason as alsa_pick_capture() above: a raw hw: device records + # at the hardware's native rate only, so a probe combo that happens to + # match the native rate (e.g. 48000Hz) can succeed on hw: and lock in a + # device that silently cannot honor any other requested rate later. + extra_devices="$(sed -n 's/^\([0-9][0-9]*\)-\([0-9][0-9]*\):.*capture.*/plughw:\1,\2/p' /proc/asound/pcm 2>/dev/null)" if [ -n "$extra_devices" ]; then for dev in $extra_devices; do seen=0 @@ -3786,8 +3804,8 @@ audio_probe_alsa_capture_profile() { if [ "$seen" -eq 0 ]; then probe_devices="$probe_devices $dev" case "$dev" in - hw:*) - probe_devices="$probe_devices plughw:${dev#hw:}" + plughw:*) + probe_devices="$probe_devices hw:${dev#plughw:}" ;; esac fi diff --git a/Runner/utils/audio_profiles/hamoa_capture_handset.profile b/Runner/utils/audio_profiles/hamoa_capture_handset.profile new file mode 100644 index 00000000..56f6a33d --- /dev/null +++ b/Runner/utils/audio_profiles/hamoa_capture_handset.profile @@ -0,0 +1,21 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Hamoa handset capture mixer profile. +# Sourced by setup_audio_route() in alsa_common.sh; defines the ordered list +# of ALSA mixer controls required to route the built-in microphones (VA_DMIC) +# through the VA decimators into VA_AIF1_CAP. +# Format: one "control_name|value" pair per line in PROFILE_MIXER_CONTROLS. + +PROFILE_NAME="hamoa_capture_handset" +PROFILE_MIXER_CONTROLS=" +VA DEC0 MUX|VA_DMIC +VA DMIC MUX0|DMIC0 +VA_AIF1_CAP Mixer DEC0|1 +VA_DEC0 Volume|100 +VA DEC1 MUX|VA_DMIC +VA DMIC MUX1|DMIC1 +VA_AIF1_CAP Mixer DEC1|1 +VA_DEC1 Volume|100 +" \ No newline at end of file diff --git a/Runner/utils/audio_profiles/hamoa_capture_headset.profile b/Runner/utils/audio_profiles/hamoa_capture_headset.profile new file mode 100644 index 00000000..9e5ff638 --- /dev/null +++ b/Runner/utils/audio_profiles/hamoa_capture_headset.profile @@ -0,0 +1,25 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Hamoa headset capture mixer profile. +# Sourced by setup_audio_route() in alsa_common.sh; defines the ordered list +# of ALSA mixer controls required to route the headset microphone (SWR_MIC) +# through the SoundWire interface into TX_AIF1_CAP. TX SMIC MUX0 must be set +# to SWR_MIC0 (not ADC1) for proper routing - ADC1 is not a valid value for +# this control and silently resolves to ZERO, breaking the capture path. +# Format: one "control_name|value" pair per line in PROFILE_MIXER_CONTROLS. + +PROFILE_NAME="hamoa_capture_headset" +PROFILE_MIXER_CONTROLS=" +TX DEC0 MUX|SWR_MIC +TX SMIC MUX0|SWR_MIC0 +TX_AIF1_CAP Mixer DEC0|1 +TX1 MODE|ADC_NORMAL +ADC2 Volume|20 +TX_DEC0 Volume|84 +ADC2_MIXER Switch|1 +HDR12 MUX|NO_HDR12 +ADC2 MUX|INP2 +ADC2 Switch|1 +" \ No newline at end of file diff --git a/Runner/utils/audio_profiles/hamoa_playback_handset.profile b/Runner/utils/audio_profiles/hamoa_playback_handset.profile new file mode 100644 index 00000000..c1600bbb --- /dev/null +++ b/Runner/utils/audio_profiles/hamoa_playback_handset.profile @@ -0,0 +1,60 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Hamoa handset playback mixer profile. +# Sourced by setup_audio_route() in alsa_common.sh; defines the ordered list +# of ALSA mixer controls required to route AIF1_PB through the 4-way speaker +# system (WSA2 + WSA amplifiers driving WooferLeft/Right + TweeterLeft/Right). +# Format: one "control_name|value" pair per line in PROFILE_MIXER_CONTROLS. +# +# Volume values (81 / 6) reflect hardware-enforced maximums observed during +# verification: requesting 84/12 is silently capped by the codec to 81/6. + +PROFILE_NAME="hamoa_playback_handset" +PROFILE_MIXER_CONTROLS=" +WSA2 WSA RX0 MUX|AIF1_PB +WSA2 WSA RX1 MUX|AIF1_PB +WSA2 WSA_RX0 INP0|RX0 +WSA2 WSA_RX1 INP0|RX1 +WSA2 WSA_COMP1 Switch|1 +WSA2 WSA_COMP2 Switch|1 +WSA2 WSA_RX0 Digital Volume|81 +WSA2 WSA_RX1 Digital Volume|81 +WSA WSA RX0 MUX|AIF1_PB +WSA WSA RX1 MUX|AIF1_PB +WSA WSA_RX0 INP0|RX0 +WSA WSA_RX1 INP0|RX1 +WSA WSA_COMP1 Switch|1 +WSA WSA_COMP2 Switch|1 +WSA WSA_RX0 Digital Volume|81 +WSA WSA_RX1 Digital Volume|81 +WooferLeft COMP Switch|1 +WooferLeft BOOST Switch|1 +WooferLeft DAC Switch|1 +WooferLeft PBR Switch|1 +WooferLeft VISENSE Switch|0 +WooferLeft WSA MODE|0 +WooferLeft PA Volume|6 +TweeterLeft COMP Switch|1 +TweeterLeft BOOST Switch|1 +TweeterLeft DAC Switch|1 +TweeterLeft PBR Switch|1 +TweeterLeft VISENSE Switch|0 +TweeterLeft WSA MODE|0 +TweeterLeft PA Volume|6 +WooferRight COMP Switch|1 +WooferRight BOOST Switch|1 +WooferRight DAC Switch|1 +WooferRight PBR Switch|1 +WooferRight VISENSE Switch|0 +WooferRight WSA MODE|0 +WooferRight PA Volume|6 +TweeterRight COMP Switch|1 +TweeterRight BOOST Switch|1 +TweeterRight DAC Switch|1 +TweeterRight PBR Switch|1 +TweeterRight VISENSE Switch|0 +TweeterRight WSA MODE|0 +TweeterRight PA Volume|6 +" \ No newline at end of file diff --git a/Runner/utils/audio_profiles/hamoa_playback_headset.profile b/Runner/utils/audio_profiles/hamoa_playback_headset.profile new file mode 100644 index 00000000..e43a4c78 --- /dev/null +++ b/Runner/utils/audio_profiles/hamoa_playback_headset.profile @@ -0,0 +1,35 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Hamoa headset playback mixer profile. +# Sourced by setup_audio_route() in alsa_common.sh; defines the ordered list +# of ALSA mixer controls required to route AIF1_PB through the stereo +# headphone output using the RX codec in Class-H High Fidelity mode. +# Format: one "control_name|value" pair per line in PROFILE_MIXER_CONTROLS. + +PROFILE_NAME="hamoa_playback_headset" +PROFILE_MIXER_CONTROLS=" +HPHL_RDAC Switch|1 +HPHR_RDAC Switch|1 +HPHL Switch|1 +HPHR Switch|1 +HPHR_COMP Switch|1 +HPHL_COMP Switch|1 +RX HPH Mode|CLS_H_HIFI +RX_HPH PWR Mode|LOHIFI +RX_MACRO RX0 MUX|AIF1_PB +RX_MACRO RX1 MUX|AIF1_PB +RX INT0_1 MIX1 INP0|RX0 +RX INT1_1 MIX1 INP0|RX1 +RX INT0 DEM MUX|CLSH_DSM_OUT +RX INT1 DEM MUX|CLSH_DSM_OUT +RX_COMP1 Switch|1 +RX_COMP2 Switch|1 +RX_RX0 Digital Volume|60 +RX_RX1 Digital Volume|60 +HPHL Volume|20 +HPHR Volume|20 +CLSH Switch|1 +LO Switch|1 +" \ No newline at end of file