mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
The platform, audio and camera libraries, the pre-js and the closure externs the web build links in live under os/emscripten.
1330 lines
67 KiB
YAML
1330 lines
67 KiB
YAML
name: CI Linux samples/audio
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
|
|
|
jobs:
|
|
samples-audio:
|
|
name: Build and run samples/audio
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
APT_PKGS: build-essential libasound2-dev libsdl2-dev libopenal-dev libpipewire-0.3-dev libpulse-dev pulseaudio ffmpeg gcc-mingw-w64-x86-64 wine64 ccache
|
|
timeout-minutes: 25
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Apt cache key
|
|
id: apt
|
|
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
|
|
|
|
- name: Restore apt cache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: ~/.apt-cache
|
|
key: ${{ steps.apt.outputs.key }}
|
|
restore-keys: ${{ steps.apt.outputs.prefix }}
|
|
|
|
- name: Install dependencies
|
|
timeout-minutes: 20
|
|
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
|
|
|
|
- name: Save apt cache
|
|
if: env.APT_CACHE_SAVE == '1'
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: ~/.apt-cache
|
|
key: ${{ steps.apt.outputs.key }}
|
|
|
|
- name: Link with lld
|
|
run: sh .github/workflows/scripts/use-lld.sh
|
|
|
|
- name: Restore ccache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: ~/.ccache
|
|
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-${{ github.workflow }}-${{ github.job }}-
|
|
|
|
- name: Enable ccache
|
|
run: |
|
|
set -eu
|
|
export CCACHE_DIR="$HOME/.ccache"
|
|
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
|
|
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
|
|
ccache -M 250M
|
|
ccache -z
|
|
|
|
- name: Build and run mixer_lock_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/mixer_lock
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c and drives the mixer's public
|
|
# entry points with a real state lock in place. The lock is a
|
|
# plain mutex, so any path that reaches a locking entry point
|
|
# while already holding it parks the thread against itself;
|
|
# loading the first menu sound did exactly that
|
|
# (libretro/RetroArch#19485). Every case runs on a watchdog:
|
|
# a reintroduced recursive acquisition aborts with a stack
|
|
# instead of hanging the job. Also covers the lock being
|
|
# released on out-of-range slots and a NULL stream name
|
|
# surviving removal.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x mixer_lock_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./mixer_lock_test
|
|
echo "[pass] mixer_lock_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./mixer_lock_test
|
|
echo "[pass] mixer_lock_test (TSan)"
|
|
|
|
- name: Build and run thread_handshake_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/thread_handshake
|
|
run: |
|
|
set -eu
|
|
# Links audio/audio_thread_wrapper.c against a scripted
|
|
# driver. The wrapped thread parks in one of two waits and
|
|
# the main thread's block() needs stopped_ack from either; a
|
|
# stop() landing between start()'s signal and the thread's
|
|
# re-check used to leave it in the one wait that never
|
|
# acknowledged. Two thousand start/stop round-trips hit that
|
|
# window on the previous wrapper on every run. The scripted
|
|
# device can also stop returning from a call: a stop must
|
|
# then still complete once the call does, and must be
|
|
# reported exactly once meanwhile - prompt handshakes are
|
|
# asserted not to be reported.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x thread_handshake_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 \
|
|
./thread_handshake_test
|
|
echo "[pass] thread_handshake_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 600 ./thread_handshake_test
|
|
echo "[pass] thread_handshake_test (TSan)"
|
|
|
|
- name: Build and run mmdevice_active_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/mmdevice_active
|
|
run: |
|
|
set -eu
|
|
# The open-endpoint table the WASAPI notifications consult,
|
|
# set from two threads and matched from a third, none of
|
|
# which has touched it before.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
timeout 300 ./mmdevice_active_test
|
|
echo "[pass] mmdevice_active_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./mmdevice_active_test
|
|
echo "[pass] mmdevice_active_test (TSan)"
|
|
|
|
- name: Build and run graph_clock_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/graph_clock
|
|
run: |
|
|
set -eu
|
|
# The two conversions the JACK and PipeWire clock fits add.
|
|
# Neither a JACK server nor a PipeWire daemon can be started
|
|
# here, so unlike alsa_clock this mirrors the arithmetic
|
|
# rather than driving the drivers - a weaker check, said
|
|
# plainly, and aimed at the parts where being wrong is
|
|
# silent.
|
|
#
|
|
# JACK's current_frames is 32 bits and wraps in about a day
|
|
# at 48 kHz, sooner if the server was up before RetroArch
|
|
# started, since the counter is the server's. The difference
|
|
# is taken in 32 bits where the wrap cancels; widening first
|
|
# turns one wrap into a four-billion-frame jump. A run is
|
|
# started two seconds short of the wrap so it goes round
|
|
# mid-measurement.
|
|
#
|
|
# PipeWire's ticks are in units of pw_time.rate, a fraction
|
|
# that is 1/samplerate. The same physical drift has to read
|
|
# the same ppm at 44.1k and 48k, which is what the fraction
|
|
# is there for.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x graph_clock_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./graph_clock_test
|
|
echo "[pass] graph_clock_test (ASan)"
|
|
|
|
- name: Build and run alsa_clock_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/alsa_clock
|
|
run: |
|
|
set -eu
|
|
# The ALSA device-clock estimate, driving the shipping
|
|
# audio/drivers/alsa.c on a real PCM opened on the null
|
|
# device. The four libasound calls the estimate is built from
|
|
# are scripted via --wrap, so a device can be made to run at a
|
|
# chosen offset and the driver's own answer read back out of
|
|
# what it logs - what is asserted is the line a user sees.
|
|
#
|
|
# Two clocks are covered. The audio timestamp, where a driver
|
|
# counts one, is the DMA or link clock compared straight
|
|
# against the system clock and needs neither a rate nor a
|
|
# delay. Where there is none - the null PCM reports none, and
|
|
# nor do plenty of real devices - the position is fitted
|
|
# against the timestamp it was taken with. That position is
|
|
# inferred from the write count less the delay, so one case
|
|
# puts an absurd but constant delay in: it shifts the line
|
|
# and leaves its slope alone, which is why this is a fit and
|
|
# not a difference of two samples.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x alsa_clock_test
|
|
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./alsa_clock_test
|
|
echo "[pass] alsa_clock_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
timeout 60 ./alsa_clock_test
|
|
echo "[pass] alsa_clock_test (TSan)"
|
|
|
|
- name: Build and run alsa_wait_writable_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/alsa_wait_writable
|
|
run: |
|
|
set -eu
|
|
# Links the shipping audio/drivers/alsa.c and interposes the
|
|
# five libasound calls alsa_wait_writable() makes via
|
|
# -Wl,--wrap, so the devices no stock PCM can produce on
|
|
# demand are scripted: a running stream whose poll never
|
|
# signals, a prepared stream reporting no space, a start that
|
|
# lands in an underrun storm, and wakes that never deliver
|
|
# space. Each must return 0 within a bounded lap and wall-time
|
|
# budget rather than parking the audio thread; the previous
|
|
# wait was an unbounded snd_pcm_wait(-1). The conforming path
|
|
# runs end-to-end on the null PCM, and the harness skips
|
|
# cleanly if no ALSA configuration is available at all.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x alsa_wait_writable_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./alsa_wait_writable_test
|
|
echo "[pass] alsa_wait_writable_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./alsa_wait_writable_test
|
|
echo "[pass] alsa_wait_writable_test (TSan)"
|
|
|
|
- name: Build and run alsa_lifecycle_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/alsa_lifecycle
|
|
run: |
|
|
set -eu
|
|
# Links the shipping audio/drivers/alsa.c against the null PCM
|
|
# and scripts the libasound calls that decide stop/start and
|
|
# the blocking write - can_pause, pause, drop, prepare, writei,
|
|
# wait, recover - via -Wl,--wrap. alive() must follow stop()
|
|
# and start() on a device that can pause, one that cannot, and
|
|
# one that says it can and refuses; the blocking write must
|
|
# write first, wait only on EAGAIN, and never without a bound.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x alsa_lifecycle_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./alsa_lifecycle_test
|
|
echo "[pass] alsa_lifecycle_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./alsa_lifecycle_test
|
|
echo "[pass] alsa_lifecycle_test (TSan)"
|
|
|
|
- name: Build and run alsa_chmap_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/alsa_chmap
|
|
run: |
|
|
set -eu
|
|
# The shipping ALSA driver against the null PCM, with the
|
|
# channel map, the channel counts the card takes and writei
|
|
# scripted via -Wl,--wrap: a card is handed the frontend's
|
|
# channels in its own order - its map's, or ALSA's FL FR RL
|
|
# RR FC LFE SL SR without one - by write() and through a lent
|
|
# mmap span, reports the layout it has, and steps 7.1 down to
|
|
# 5.1 or quad before stereo. A short writei must not lose the
|
|
# frame stride.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
|
timeout 120 ./alsa_chmap_test
|
|
echo "[pass] alsa_chmap_test (ASan)"
|
|
|
|
- name: Build and run pipeline_stall_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/pipeline_stall
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c and runs the threaded pipeline's
|
|
# shipping producer and consumer against a scripted driver whose
|
|
# write_raw() path reaches the device without the resampler or
|
|
# the arenas. The device is then made to stop taking audio. The
|
|
# consumer must take nothing from the ring it cannot deliver and
|
|
# report no progress it did not make; the producer must record
|
|
# the stall after one bounded wait and drop at once thereafter;
|
|
# and both must recover when the device returns. The previous
|
|
# consumer took the chunk first, threw it away on finding no
|
|
# room, and cleared the stall - the frontend paced at one frame
|
|
# per failed pass for as long as the device was stuck.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x pipeline_stall_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 180 \
|
|
./pipeline_stall_test
|
|
echo "[pass] pipeline_stall_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 400 ./pipeline_stall_test
|
|
echo "[pass] pipeline_stall_test (TSan)"
|
|
|
|
- name: Build and run upmix_test (plain, ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/upmix
|
|
run: |
|
|
set -eu
|
|
# The stereo-to-wider stage at the last step before a device
|
|
# opened with a wider speaker layout. A layout is a mask of
|
|
# positions in ascending bit order, never a count: 5.1 with
|
|
# the rear pair at the back and 5.1 with it at the sides are
|
|
# two layouts whose slots differ. The fronts carry the stereo
|
|
# unchanged, the rear pair -3 dB in its own slots, the centre
|
|
# the -6 dB sum, the LFE the bass through a 120 Hz low-pass and
|
|
# none of the treble; stereo is a copy; a mask the stage
|
|
# cannot fill is refused.
|
|
make clean && make -j$(nproc) all
|
|
./upmix_test
|
|
echo "[pass] upmix_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./upmix_test
|
|
echo "[pass] upmix_test (ASan)"
|
|
|
|
- name: Build and run discrete_multichannel_test (plain, ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/discrete_multichannel
|
|
run: |
|
|
set -eu
|
|
# A core's 5.1 through the multi-channel batch entry to a 5.1
|
|
# device without the fold and the upmix in between: each
|
|
# device channel carries its core channel's tone and none of
|
|
# the others', in all four format pairings, through the
|
|
# resampler off unity; over the threaded ring; recorded; and
|
|
# to a stereo device, the fold at the BS.775 gains with the
|
|
# LFE dropped. The last case runs the shipping WASAPI driver
|
|
# over the fake device from wasapi_exclusive_pacing, so a
|
|
# 5.1 core reaches a Dolby Digital decoder as AC-3 bursts
|
|
# that decode back to its own channels. Includes the
|
|
# shipping audio_driver.c.
|
|
make clean && make -j$(nproc) all
|
|
./discrete_multichannel_test
|
|
echo "[pass] discrete_multichannel_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./discrete_multichannel_test
|
|
echo "[pass] discrete_multichannel_test (ASan)"
|
|
|
|
- name: Build and run embedded_alsa_test (the embedded ALSA driver, mock kernel device)
|
|
shell: bash
|
|
working-directory: samples/audio/embedded_alsa
|
|
run: |
|
|
set -eu
|
|
# The "tinyalsa" driver in audio/drivers/alsa.c talks the
|
|
# kernel PCM ioctls directly. abi_check compares its copy of
|
|
# that ABI - extracted from the driver itself, so it cannot
|
|
# go stale - against <sound/asound.h>: every struct size and
|
|
# offset, constant and ioctl number. The test then runs the
|
|
# driver against a mock device that refines parameters the
|
|
# way the kernel does.
|
|
make clean && make -j$(nproc) check
|
|
echo "[pass] embedded_alsa_test"
|
|
make clean && make -j$(nproc) check SANITIZER=address,undefined
|
|
echo "[pass] embedded_alsa_test (ASan)"
|
|
|
|
- name: Build and run ealsa_test (the embedded ALSA driver against a mock PCM device)
|
|
shell: bash
|
|
working-directory: samples/audio/ealsa
|
|
run: |
|
|
set -eu
|
|
# The embedded driver in audio/drivers/alsa.c talks to the
|
|
# kernel's PCM ABI through one seam - open, ioctl, close,
|
|
# poll - so a scripted device that answers the way a card
|
|
# does runs it with no card present, and can refuse a rate, a
|
|
# format, a channel count or a period grid, which is where a
|
|
# negotiation goes wrong. Built with HAVE_TINYALSA alone, so
|
|
# the build never sees an alsa-lib header either. It also
|
|
# carries what tinyalsa_write_test used to check of the
|
|
# driver this one replaced: a full device refusing in both
|
|
# modes, a device that never signals, a poll cut short by a
|
|
# signal, and the first write reporting what it took.
|
|
make clean && make -j$(nproc) all
|
|
./ealsa_test
|
|
echo "[pass] ealsa_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./ealsa_test
|
|
echo "[pass] ealsa_test (ASan)"
|
|
|
|
- name: Build and run bitstream_test (plain, ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/bitstream
|
|
run: |
|
|
set -eu
|
|
# audio/audio_bitstream.c: a coded stream on its way to a
|
|
# receiver, which the PCM pipeline has no place for. Real
|
|
# DTS and AC-3 from ffmpeg, handed over frame by frame:
|
|
# bursts leave whole and never partial, the queue paces the
|
|
# source rather than the source running ahead, a gap
|
|
# produces the pause burst the standard has for it rather
|
|
# than silence (which makes a receiver drop its lock), and
|
|
# the PCM frames reported are the device time the bytes
|
|
# occupy.
|
|
make clean && make -j$(nproc) all
|
|
./bitstream_test
|
|
echo "[pass] bitstream_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./bitstream_test
|
|
echo "[pass] bitstream_test (ASan)"
|
|
|
|
- name: Build and run opensl_test (the Android driver against a mock OpenSL player)
|
|
shell: bash
|
|
working-directory: samples/audio/opensl
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/opensl.c is Android-only and has no build
|
|
# here, so it is built against stand-in <SLES/*.h> over a
|
|
# scripted player whose queue a thread drains: the block
|
|
# arithmetic at the latencies of issue #6405 and below, the
|
|
# queue accounting the hang there came from, float and
|
|
# 16-bit, a full queue, and a device that stops consuming.
|
|
make clean && make -j$(nproc) all
|
|
./opensl_test
|
|
echo "[pass] opensl_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./opensl_test
|
|
echo "[pass] opensl_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 ./opensl_test
|
|
echo "[pass] opensl_test (TSan)"
|
|
|
|
- name: Build and run qsa_test (the QNX driver against a mock QSA device)
|
|
shell: bash
|
|
working-directory: samples/audio/qsa
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/alsa_qsa.c is QNX-only and has no build here,
|
|
# so it is built against a stand-in <sys/asoundlib.h> over a
|
|
# scripted device: init and its failure path, the rate it
|
|
# reports back, blocking and non-blocking writes, stop and
|
|
# start, underrun recovery, and a device that stops draining.
|
|
# Not a substitute for a QNX build; it is the coverage the
|
|
# driver had none of.
|
|
make clean && make -j$(nproc) all
|
|
./qsa_test
|
|
echo "[pass] qsa_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./qsa_test
|
|
echo "[pass] qsa_test (ASan)"
|
|
|
|
- name: Build and run ctr_audio_test (the 3DS CSND and NDSP drivers against a scripted device)
|
|
shell: bash
|
|
working-directory: samples/audio/ctr_audio
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/ctr_csnd_audio.c and ctr_dsp_audio.c against a
|
|
# stand-in <3ds.h> whose system tick and channel position are
|
|
# the device: buffer_size() and write_avail() in bytes of what
|
|
# write() takes, and a write longer than the ring taking half
|
|
# of it - ASan is what sees a copy run past the ring.
|
|
make clean && make -j$(nproc) all
|
|
./ctr_audio_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./ctr_audio_test
|
|
echo "[pass] ctr_audio_test (ASan)"
|
|
|
|
- name: Build and run switch_audio_test (the Switch audout driver against a scripted service)
|
|
shell: bash
|
|
working-directory: samples/audio/switch_audio
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/switch_audio.c's libnx build against a
|
|
# stand-in <switch.h> whose audout service queues buffers and
|
|
# plays them out on the test's word: write_avail() must be
|
|
# what a write takes, across the released buffers, and
|
|
# buffer_size() every buffer carrying its share of the
|
|
# latency setting.
|
|
make clean && make -j$(nproc) all
|
|
./switch_audio_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./switch_audio_test
|
|
echo "[pass] switch_audio_test (ASan)"
|
|
|
|
- name: Build and run switch_audren_test (the Switch audren driver's renderer ownership, ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/switch_audren
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/switch_libnx_audren_audio.c's libnx build
|
|
# against a stand-in <switch.h> whose renderer thread plays a
|
|
# wave buffer a frame. libnx's audrv is not thread-safe and
|
|
# the driver takes no lock: every renderer call must come from
|
|
# the writer's thread while another reads write_avail(), a
|
|
# write_avail() on the writer's own thread must see what the
|
|
# renderer played, non-blocking writes must keep taking audio,
|
|
# and stop and start must pause and resume the voice. TSan
|
|
# reports two threads in the renderer as a race on the
|
|
# stand-in's wave buffer list.
|
|
make clean && make -j$(nproc) all
|
|
./switch_audren_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./switch_audren_test
|
|
echo "[pass] switch_audren_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 ./switch_audren_test
|
|
echo "[pass] switch_audren_test (TSan)"
|
|
|
|
- name: Build and run wiiu_ax_test (the Wii U AX driver's callback against its writer)
|
|
shell: bash
|
|
working-directory: samples/audio/wiiu_ax
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/wiiu_audio.c against the tree's own Wii U
|
|
# headers, with stand-in AX and OS functions behind them: the
|
|
# frame callback on one thread and the writer on another keep
|
|
# the count of buffered frames in one atomic word, and every
|
|
# frame accepted must be buffered or counted played. TSan is
|
|
# what sees a field the two share without ordering.
|
|
make clean && make -j$(nproc) all
|
|
./wiiu_ax_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./wiiu_ax_test
|
|
echo "[pass] wiiu_ax_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./wiiu_ax_test
|
|
echo "[pass] wiiu_ax_test (TSan)"
|
|
make clean
|
|
|
|
- name: Build and run xenon360_test (the libxenon driver against a scripted sound queue)
|
|
shell: bash
|
|
working-directory: samples/audio/xenon360
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/xenon360_audio.c against stand-in libxenon
|
|
# headers: buffer_size() is the latency setting, write_avail()
|
|
# what a write takes, the queue never past it, and a write
|
|
# longer than the staging buffer goes in passes - ASan is what
|
|
# sees a pass overrun it.
|
|
make clean && make -j$(nproc) all
|
|
./xenon360_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./xenon360_test
|
|
echo "[pass] xenon360_test (ASan)"
|
|
|
|
- name: Build and run audioworklet_test (the web driver's write against its own ring)
|
|
shell: bash
|
|
working-directory: samples/audio/audioworklet
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/audioworklet.c, #included by the test and
|
|
# built against the tree's emscripten stand-ins: write()
|
|
# returns the bytes it took, the ring holds exactly that, and
|
|
# frames a write gives up on are counted, not logged.
|
|
make clean && make -j$(nproc) all
|
|
./audioworklet_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./audioworklet_test
|
|
echo "[pass] audioworklet_test (ASan)"
|
|
|
|
- name: Build and run rwebaudio_test (the web driver's channels, C and JavaScript halves)
|
|
shell: bash
|
|
working-directory: samples/audio/rwebaudio
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/rwebaudio.c against a stand-in for its
|
|
# JavaScript half: frames of the layout the page took go out
|
|
# one plane per channel, and every byte count is in frames of
|
|
# that layout. Then os/emscripten/library_rwebaudio.js itself
|
|
# under Node with a mock AudioContext: the destination is
|
|
# widened only past stereo and only as far as its
|
|
# maxChannelCount allows, and a stereo one is left as it was.
|
|
make clean && make -j$(nproc) check
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./rwebaudio_test
|
|
echo "[pass] rwebaudio_test (ASan)"
|
|
|
|
- name: Build and run pipewire_write_test (the PipeWire driver's write against its own ring)
|
|
shell: bash
|
|
working-directory: samples/audio/pipewire_write
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/pipewire.c, #included by the test and built
|
|
# against the system's PipeWire headers, with the graph as a
|
|
# loop wait that plays a quantum: a write longer than the
|
|
# ring's mark takes what fits, or blocking goes in parts, in
|
|
# whole frames, and the write path logs nothing.
|
|
make clean && make -j$(nproc) all
|
|
./pipewire_write_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./pipewire_write_test
|
|
echo "[pass] pipewire_write_test (ASan)"
|
|
|
|
- name: Build and run wasapi_mic_test (the WASAPI microphone's fetch against a scripted capture client)
|
|
shell: bash
|
|
working-directory: samples/audio/wasapi_mic
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/wasapi.c's text against the shared fake WASAPI
|
|
# with a capture client added: a shared-mode FIFO set shorter
|
|
# than one engine packet still takes every packet, and a
|
|
# packet with no room waits for the FIFO to be read.
|
|
make clean && make -j$(nproc) all
|
|
./wasapi_mic_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./wasapi_mic_test
|
|
echo "[pass] wasapi_mic_test (ASan)"
|
|
|
|
- name: Build and run oss_report_test (the OSS driver's reports on a device that cannot answer)
|
|
shell: bash
|
|
working-directory: samples/audio/oss_report
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/oss.c, #included by the test, over Linux's
|
|
# soundcard.h: a device that has gone away reports no room and
|
|
# no size, and a second of frames asking logs nothing.
|
|
make clean && make -j$(nproc) all
|
|
./oss_report_test
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./oss_report_test
|
|
echo "[pass] oss_report_test (ASan)"
|
|
|
|
- name: Build and run live_drivers_test (a live PulseAudio server, a null ALSA PCM)
|
|
shell: bash
|
|
working-directory: samples/audio/live_drivers
|
|
run: |
|
|
set -eu
|
|
# A server with a null sink, and an ALSA config naming a null
|
|
# PCM: the real drivers against the real libraries, no
|
|
# hardware. Pulse's telemetry is cached from the server
|
|
# thread's callbacks and its writer takes no mainloop lock;
|
|
# ALSA is run once as the device reports
|
|
# itself and once told it cannot pause, the fallback from
|
|
# snd_pcm_pause to snd_pcm_drop. With the threaded pipeline a
|
|
# plug PCM opens for mmap and lends its buffer; a plug over a
|
|
# file plugin reads the lent bytes back.
|
|
export XDG_RUNTIME_DIR=/tmp/xdg
|
|
mkdir -p "$XDG_RUNTIME_DIR/pulse"
|
|
printf '%s\n' 'pcm.nullpcm { type null }' \
|
|
'pcm.plugnull { type plug slave.pcm "nullpcm" }' \
|
|
'pcm.filenull { type file slave.pcm "nullpcm" file "/tmp/ra_live_drivers_lend.raw" format raw }' \
|
|
'pcm.plugfile { type plug slave.pcm "filenull" }' > "$HOME/.asoundrc"
|
|
pulseaudio --daemonize=no --exit-idle-time=-1 --disallow-exit -n \
|
|
-L "module-null-sink sink_name=nullsink rate=48000 channels=2" \
|
|
-L "module-native-protocol-unix auth-anonymous=1 socket=$XDG_RUNTIME_DIR/pulse/native" \
|
|
--log-target=file:/tmp/pa.log &
|
|
PA_PID=$!
|
|
sleep 3
|
|
make clean && make -j$(nproc) all
|
|
./live_drivers_test
|
|
echo "[pass] live_drivers_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./live_drivers_test
|
|
echo "[pass] live_drivers_test (ASan)"
|
|
# Pulse's writer hands audio to the server's thread through a
|
|
# ring and an eventcount, which is what TSan is for.
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1:suppressions=$PWD/tsan.supp ./live_drivers_test
|
|
echo "[pass] live_drivers_test (TSan)"
|
|
kill $PA_PID || true
|
|
|
|
- name: Build and run binaural_test (plain, ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/binaural
|
|
run: |
|
|
set -eu
|
|
# A layout rendered to two ears for headphones: a left speaker
|
|
# arrives at the left ear first, louder and brighter; the rear
|
|
# pair duller than the front; centre and LFE alike to both
|
|
# ears; a centred source keeps its level and a full-scale one
|
|
# leaves at full scale; the two 5.1s render their rear pair
|
|
# from different directions; stereo is a mild crossfeed.
|
|
make clean && make -j$(nproc) all
|
|
./binaural_test
|
|
echo "[pass] binaural_test"
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./binaural_test
|
|
echo "[pass] binaural_test (ASan)"
|
|
|
|
- name: Build and run pipeline_rate_control_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/pipeline_rate_control
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c and runs the threaded pipeline's
|
|
# shipping producer, consumer and rate controller against a
|
|
# device whose 8 ms buffer drains at exactly 48000 frames a
|
|
# second of wall-clock time, fed one 800-frame video frame every
|
|
# 1/60 s on an absolute schedule, audio sync off. The clocks
|
|
# match, so the right ratio is 1.0: after a four-second settle
|
|
# the mean rate adjustment over three seconds must be within
|
|
# 500 ppm of it, and the device must have taken 99.5% of what
|
|
# was produced. Rate control on the pipeline samples pipe and
|
|
# device together, at the producer; reading the device alone,
|
|
# which the consumer holds between half and full, pins the
|
|
# ratio at a bound and the pipe overflows behind it. Real time,
|
|
# so a loaded runner can shift the mean; the tolerance is ten
|
|
# times the settled residual.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x pipeline_rate_control_test
|
|
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./pipeline_rate_control_test
|
|
echo "[pass] pipeline_rate_control_test (ASan)"
|
|
# A core late by 60 ms every 2 s, audio sync off: the silence is
|
|
# the stalls' own and no more, the late audio is not kept, and
|
|
# rate control is not pinned against a backlog. And with sync on:
|
|
# the writer waits, nothing is dropped.
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 12 4 jitter
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 12 4 sync-jitter
|
|
echo "[pass] pipeline_rate_control_test (late core, ASan)"
|
|
# The 64 ms default buffer, sync off, a steady core and a late one:
|
|
# the consumer's chunks are whole publishes and the pipe's fill
|
|
# swings by one with the phase; nothing is dropped from a steady
|
|
# core at any phase.
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big-jitter
|
|
echo "[pass] pipeline_rate_control_test (64 ms device, ASan)"
|
|
# The producer held 4 ms every five seconds, as a loaded runner
|
|
# holds it: a publish pushed across a sink window boundary. The
|
|
# estimate must settle and apply about zero, not the shift.
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big-stall
|
|
echo "[pass] pipeline_rate_control_test (stalled producer, ASan)"
|
|
# A float core on a float ring into a float device: a signal a
|
|
# tenth of an int16 LSB high arrives as published, not as zero.
|
|
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 float
|
|
echo "[pass] pipeline_rate_control_test (float core, ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 240 ./pipeline_rate_control_test
|
|
echo "[pass] pipeline_rate_control_test (TSan)"
|
|
|
|
- name: Build and run resampler_reset_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/resampler_reset
|
|
run: |
|
|
set -eu
|
|
# Every resampler backend's reset(): after it the stream
|
|
# continues as a freshly initialised backend would, to the
|
|
# sample, at the ratios the frontend meets - and the reset
|
|
# makes no allocation, counted through wrapped malloc, calloc
|
|
# and memalign_alloc.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x resampler_reset_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 ./resampler_reset_test
|
|
echo "[pass] resampler_reset_test (ASan)"
|
|
|
|
- name: Build and run ff_speedup_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/ff_speedup
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c with the clock replaced by a
|
|
# counter. The fast-forward speedup multiplier must follow the
|
|
# core's cadence on both pipelines: measured at the flush inline,
|
|
# and by the producer at its publish cadence on the threaded
|
|
# pipeline, where the consumer's own flush interval is the device
|
|
# draining the previous chunk and would only confirm the previous
|
|
# multiplier. Re-entry after an idle spell must start from unity
|
|
# rather than read the idle time as one flush interval.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x ff_speedup_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 ./ff_speedup_test
|
|
echo "[pass] ff_speedup_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./ff_speedup_test
|
|
echo "[pass] ff_speedup_test (TSan)"
|
|
|
|
- name: Build and run device_list_driver_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/device_list_driver
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c with the alsa and null drivers in
|
|
# the table. Picking a driver in the menu writes the setting but
|
|
# does not reinitialise audio, and the Device list - rebuilt on
|
|
# each open - was asked of the running driver, so it showed the
|
|
# old driver's devices under the new driver's name until a
|
|
# restart. The list now comes from the configured driver, and
|
|
# is freed by the driver that built it. Cases: configured and
|
|
# running agree; configured differs from running; back again;
|
|
# nothing running; and release by the builder, once.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x device_list_driver_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./device_list_driver_test
|
|
echo "[pass] device_list_driver_test (ASan)"
|
|
|
|
- name: Build and run device_label_path_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/device_label_path
|
|
run: |
|
|
set -eu
|
|
# A device list entry with the hardware's name after the path,
|
|
# "/dev/dsp0 (HDA Intel)", is stored by the menu as shown and
|
|
# comes back to init as the device string; oss opened it as a
|
|
# path and failed, so a device picked from the list could not
|
|
# be used. Compiles oss and audioio whole with open() redirected
|
|
# to a recorder, and checks each reaches it with the path.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x device_label_path_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./device_label_path_test
|
|
echo "[pass] device_label_path_test (ASan)"
|
|
|
|
- name: Build and run rate_control_bounds_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/rate_control_bounds
|
|
run: |
|
|
set -eu
|
|
# Includes audio/audio_driver.c and runs the rate control's
|
|
# shipping computation against a scripted driver. At the
|
|
# setpoint, empty and full it must give 1.0 and the band's two
|
|
# edges; with write_avail() reporting five times buffer_size()
|
|
# it must give the upper edge and no more. Before the clamp an
|
|
# over-reporting driver drove it nine times past the band.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x rate_control_bounds_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./rate_control_bounds_test
|
|
echo "[pass] rate_control_bounds_test (ASan)"
|
|
|
|
- name: Build and run latency_contract_test (ASan + UBSan, TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/latency_contract
|
|
run: |
|
|
set -eu
|
|
# Real drivers on backends that need no hardware - the ALSA null
|
|
# PCM, SDL's dummy audio, OpenAL Soft's null device - opened at
|
|
# 64 and 32 ms and checked against audio_driver.h's contract:
|
|
# buffer_size() is the setting in bytes of the output format,
|
|
# write_avail() never exceeds it, a buffer's worth offered takes
|
|
# room, and the room comes back as the device plays. The sdl
|
|
# driver before fdda5f7 reports 42.7 ms against 64 here.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x latency_contract_test
|
|
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
|
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./latency_contract_test
|
|
echo "[pass] latency_contract_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
TSAN_OPTIONS=halt_on_error=1:suppressions=$PWD/tsan.supp \
|
|
timeout 300 ./latency_contract_test
|
|
echo "[pass] latency_contract_test (TSan)"
|
|
|
|
- name: Build and run asio_convert_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/asio_convert
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/asio_convert.h is the ASIO driver's sample
|
|
# conversion, pure C, so it runs here without Windows. Every
|
|
# ASIOSampleType the specification defines is converted for full
|
|
# scale, zero, half and one 16-bit step in both signs and checked
|
|
# byte for byte against the layout the specification gives - the
|
|
# Int32LSB16..24 family as low-aligned, sign-extended words, the
|
|
# MSB types big-endian. Saturation and the silence fill past the
|
|
# supplied frames are checked too. The converter this replaced
|
|
# wrote five types and memset the rest to silence.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x asio_convert_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./asio_convert_test
|
|
echo "[pass] asio_convert_test (ASan)"
|
|
|
|
- name: Build and run coreaudio_clock_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/coreaudio_clock
|
|
run: |
|
|
set -eu
|
|
# The device clock fitted from the timestamp the render
|
|
# callback is handed, which the driver used to discard.
|
|
#
|
|
# The fit is the same shape as the ASIO one and is checked
|
|
# there. What is different is the time axis: mHostTime is in
|
|
# mach absolute units, not nanoseconds, and the ratio is one
|
|
# to one on Intel and 125 to 3 on Apple silicon. Ignoring it
|
|
# reads a 48 kHz device as 1152 Hz - invisible on Intel and
|
|
# wrong on every Mac since 2020 - so the same clock is driven
|
|
# through both timebases here.
|
|
#
|
|
# Also mRateScalar, which is the HAL's own figure for the
|
|
# same thing, read only where it is flagged valid.
|
|
#
|
|
# Arithmetic only, so it runs here without a Mac.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x coreaudio_clock_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./coreaudio_clock_test
|
|
echo "[pass] coreaudio_clock_test (ASan)"
|
|
|
|
- name: Build and run coreaudio_ring_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/coreaudio_ring
|
|
run: |
|
|
set -eu
|
|
# The CoreAudio ring keeps a frame's channels together. Its
|
|
# capacity is the sample count rounded up to a power of two,
|
|
# which for six channels is not a whole number of frames, so
|
|
# the cursors wrap mid-frame - and that does not matter,
|
|
# because a FIFO returns samples in the order they went in
|
|
# wherever they sit. This runs the traffic for every layout,
|
|
# then forces every transfer across the wrap, to keep that
|
|
# answer honest rather than assumed.
|
|
#
|
|
# What does rotate the channels is a transfer whose count is
|
|
# not a whole number of frames, which is why the callback
|
|
# trims short reads. The last case does that on purpose and
|
|
# fails if it goes unnoticed, so the trim has a guard.
|
|
#
|
|
# Arithmetic only, so it runs here without a Mac.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x coreaudio_ring_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./coreaudio_ring_test
|
|
echo "[pass] coreaudio_ring_test (ASan)"
|
|
|
|
- name: Build and run sdl_lost_wakeup_test (the SDL2 driver against SDL's dummy device)
|
|
shell: bash
|
|
working-directory: samples/audio/sdl_lost_wakeup
|
|
run: |
|
|
set -eu
|
|
# The producer's waits and the capture parks are measured; the
|
|
# lend pair is asserted: whole-frame spans, published to a
|
|
# device that pulls them, refused while stopped.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
|
SDL_AUDIODRIVER=dummy timeout 300 ./sdl_lost_wakeup_test 1
|
|
echo "[pass] sdl_lost_wakeup_test (ASan)"
|
|
|
|
- name: Build and run coreaudio_write_test (the shipping writer, lend included, on stubs)
|
|
shell: bash
|
|
working-directory: samples/audio/coreaudio_write
|
|
run: |
|
|
set -eu
|
|
# run.py lifts the writer, the render callback and the lend
|
|
# pair out of audio/drivers/coreaudio.c and drives them on a
|
|
# simulated device clock; no Apple framework is involved.
|
|
python3 run.py
|
|
echo "[pass] coreaudio_write_test"
|
|
|
|
- name: Build and run asio_clock_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/asio_clock
|
|
run: |
|
|
set -eu
|
|
# The device clock read from the driver's own time
|
|
# information, rather than from when its callbacks arrived.
|
|
#
|
|
# Two things here do not need an interface. The ASIOTime
|
|
# layout is an ABI: the sample position and system time are
|
|
# 64-bit values the SDK splits into 32-bit halves on Windows,
|
|
# and getting that wrong does not fail to build, it reads a
|
|
# driver's numbers as garbage in a real-time callback. The
|
|
# offsets are asserted against the SDK's, and asio.c carries
|
|
# the same assertions for the compiler that sees the real
|
|
# Windows ABI.
|
|
#
|
|
# And the estimate itself: a clock at a known offset is fed
|
|
# in with scheduling jitter on the timestamps, and the answer
|
|
# has to be the clock. A two-point estimate fails that - the
|
|
# anchor's own noise divided by the window reads as drift,
|
|
# 50 ppm for half a millisecond at ten seconds - which is why
|
|
# it is a least-squares fit over every callback instead.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x asio_clock_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./asio_clock_test
|
|
echo "[pass] asio_clock_test (ASan)"
|
|
|
|
- name: Build and run asio_ring_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/asio_ring
|
|
run: |
|
|
set -eu
|
|
# Also the write's frame alignment: the callback reads the
|
|
# ring a frame at a time, so a write must end on one, and
|
|
# the rule is checked for every layout the driver can open.
|
|
#
|
|
# audio/drivers/asio_ring.h sizes the ring in front of the ASIO
|
|
# device: the latency setting less the device's reported stage,
|
|
# floored at four periods where that is under 6 ms, 6 ms where it
|
|
# is over, two periods at the least. Arithmetic only, so it runs
|
|
# here without Windows. The expected frames are worked by hand
|
|
# from those rules for the devices that set them - ASIO4ALL at 64
|
|
# frames, a wrapper locked at 528, a USB DAC at 8 and 128 - and
|
|
# the two points where the rules meet.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x asio_ring_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
|
./asio_ring_test
|
|
echo "[pass] asio_ring_test (ASan)"
|
|
|
|
- name: Build and run dsound_pump_test (MinGW, under wine)
|
|
shell: bash
|
|
working-directory: samples/audio/dsound_pump
|
|
run: |
|
|
set -eu
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
# audio/drivers/dsound.c end to end on wine's DirectSound, fed
|
|
# by an ALSA null PCM, which plays at real-time pace on a
|
|
# runner with no sound card. Pins that the pump - which now
|
|
# waits for the play cursor to move the distance it needs,
|
|
# worked out from the rate, instead of retrying every
|
|
# millisecond - still moves audio through blocking writes,
|
|
# fills silence when the writer stops, and lets free() return
|
|
# at once while it waits.
|
|
printf 'pcm.!default { type null }\nctl.!default { type hw card 0 }\n' > ~/.asoundrc
|
|
make clean && make -j$(nproc) all
|
|
WINEDEBUG=-all timeout 300 make check
|
|
echo "[pass] dsound_pump_test (wine)"
|
|
|
|
- name: Build and run wdmks_loop_test (MinGW, under wine)
|
|
shell: bash
|
|
working-directory: samples/audio/wdmks_loop
|
|
run: |
|
|
set -eu
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
# audio/drivers/wdmks.c compiled whole, its WaveRT loop driven
|
|
# by hand: the test owns the driver's state, points the
|
|
# position register at a word of its own and moves the
|
|
# hardware itself, so nothing reaches the kernel and wine
|
|
# needs no sound card. Pins that every byte the cursor passes
|
|
# is cleared, so the loop plays silence when the frontend
|
|
# stops writing - a pause, a menu, a stall - instead of the
|
|
# last loop of audio round and round; that a write after
|
|
# that lands a margin ahead of the hardware rather than a
|
|
# loop behind it; that a full loop is not taken for an
|
|
# overrun; and that a start puts the model back at the top
|
|
# of the loop with the hardware; and that the resync margin is
|
|
# the FIFO the pin reports plus half a millisecond, two
|
|
# milliseconds where it reports none; and that gaps in the
|
|
# audio are counted once each, neither a start nor every
|
|
# sample of a pause.
|
|
make clean && make -j$(nproc) all
|
|
WINEDEBUG=-all timeout 120 make check
|
|
echo "[pass] wdmks_loop_test (wine)"
|
|
|
|
- name: Check MMDevice notification targets on both thread backends
|
|
shell: bash
|
|
working-directory: samples/audio/mmdevice_notification
|
|
run: |
|
|
set -eu
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
for threads in 0 1; do
|
|
make clean check THREADS=$threads SANITIZER=address,undefined
|
|
make clean THREADS=$threads CC=x86_64-w64-mingw32-gcc LDFLAGS=-static && make -j$(nproc) all THREADS=$threads CC=x86_64-w64-mingw32-gcc LDFLAGS=-static
|
|
WINEDEBUG=-all wine ./mmdevice_notification_test.exe
|
|
done
|
|
make clean
|
|
|
|
- name: Build and run wasapi_exclusive_pacing_test (ASan + UBSan, then TSan)
|
|
shell: bash
|
|
working-directory: samples/audio/wasapi_exclusive_pacing
|
|
run: |
|
|
set -eu
|
|
# audio/drivers/wasapi.c, unmodified but for its Windows include
|
|
# lines, against a fake device on a real-time clock: exclusive
|
|
# and event-driven, one period per endpoint buffer, every period
|
|
# taken or counted unanswered. The writer is a frontend with audio
|
|
# sync off: one frame of core audio per non-blocking write, no rate
|
|
# control. Passes when no period goes unanswered in either mode and
|
|
# nothing is dropped once the buffer holds a frame. Before the pump
|
|
# thread and the drain-first shared write this run gave 3-10% of
|
|
# periods unanswered; the first smaller-period attempt without the
|
|
# pump gave 75%.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x wasapi_exclusive_pacing_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
|
|
./wasapi_exclusive_pacing_test 3
|
|
echo "[pass] wasapi_exclusive_pacing_test (ASan)"
|
|
make clean && make -j$(nproc) all SANITIZER=thread
|
|
timeout 180 ./wasapi_exclusive_pacing_test 3
|
|
echo "[pass] wasapi_exclusive_pacing_test (TSan)"
|
|
|
|
- name: Build and run sink_rate_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/audio/sink_rate
|
|
run: |
|
|
set -eu
|
|
# The frontend's sink rate estimator against a scripted driver
|
|
# on a synthetic clock: a device 120 ppm fast and 80 ppm slow are
|
|
# measured and the resampling bias converges onto them; 5000 ppm
|
|
# stops at the clamp; with rate control on the bias absorbs rate
|
|
# control's mean adjustment; a paused window, a count that went
|
|
# backwards, and a stalled window move nothing; disabled, nothing
|
|
# moves at all.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x sink_rate_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 ./sink_rate_test
|
|
echo "[pass] sink_rate_test (ASan)"
|
|
|
|
- name: Build and run pacing_test (ASan + UBSan)
|
|
shell: bash
|
|
working-directory: samples/runloop/pacing
|
|
run: |
|
|
set -eu
|
|
# The three pacing decisions the runloop makes every
|
|
# iteration, run from runloop.h so this is the shipping code
|
|
# rather than a copy of it. The gap limiter over all 128
|
|
# combinations of the pace bits by nonblocking by fastmotion -
|
|
# exactly one may engage, because engaging next to another
|
|
# source double-paces and engaging under fast-forward
|
|
# throttles it to 1x, and both look like performance bugs
|
|
# rather than pacing bugs. The frame period over the rates a
|
|
# core can report including zero and a million, bounded and
|
|
# monotonic. And the filter on the measured loop rate, which
|
|
# has to reject a stall: one 0.9 s sample used to pull a 60
|
|
# fps reading to 8 and take several frames to recover.
|
|
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
|
test -x pacing_test
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 60 ./pacing_test
|
|
echo "[pass] pacing_test (ASan)"
|
|
|
|
- name: Build the ALSA objects through configure (--enable-alsa)
|
|
shell: bash
|
|
run: |
|
|
set -eu
|
|
# The harnesses above compile audio/drivers/alsa.c with
|
|
# -DHAVE_ALSA on the command line. A distribution build takes
|
|
# HAVE_ALSA from config.h instead, and the file gates its
|
|
# alsa-lib includes on it, so build the objects that way too:
|
|
# configure with ALSA on, then compile them. That covers the
|
|
# object a distribution ships as well as the ones the
|
|
# harnesses build (libretro/RetroArch#19525). The embedded
|
|
# driver takes its define the same way and shares the file,
|
|
# so the arrangements below cover it too.
|
|
./configure --enable-alsa --disable-qt --disable-ffmpeg \
|
|
--disable-x11 --disable-wayland
|
|
grep -q '^HAVE_ALSA = 1' config.mk
|
|
make obj-unix/release/audio/drivers/alsa.o \
|
|
obj-unix/release/audio/common/alsa.o
|
|
echo "[pass] alsa-lib alone, through configure"
|
|
|
|
# The embedded driver is in the same file behind its own
|
|
# define, and configure puts HAVE_TINYALSA in config.h as
|
|
# well as on the command line - so it has the same question
|
|
# to answer as HAVE_ALSA did, and one object serves both
|
|
# drivers. The two arrangements a distribution can ask for:
|
|
# the embedded driver with no alsa-lib anywhere, and both in
|
|
# the one object.
|
|
rm -f obj-unix/release/audio/drivers/alsa.o
|
|
./configure --enable-tinyalsa --disable-alsa --disable-qt \
|
|
--disable-ffmpeg --disable-x11 --disable-wayland
|
|
grep -q '^HAVE_TINYALSA = 1' config.mk
|
|
grep -q '^HAVE_ALSA = 0' config.mk
|
|
make obj-unix/release/audio/drivers/alsa.o
|
|
echo "[pass] the embedded driver alone, through configure"
|
|
|
|
rm -f obj-unix/release/audio/drivers/alsa.o \
|
|
obj-unix/release/audio/common/alsa.o
|
|
./configure --enable-alsa --enable-tinyalsa --disable-qt \
|
|
--disable-ffmpeg --disable-x11 --disable-wayland
|
|
grep -q '^HAVE_TINYALSA = 1' config.mk
|
|
grep -q '^HAVE_ALSA = 1' config.mk
|
|
make obj-unix/release/audio/drivers/alsa.o \
|
|
obj-unix/release/audio/common/alsa.o
|
|
echo "[pass] both drivers in the one object, through configure"
|
|
|
|
- name: Build and run stretch and native SRC chain (C89, ASan + UBSan)
|
|
working-directory: samples/audio/stretch
|
|
run: |
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run WSOLA kernel tests (C89, ASan + UBSan)
|
|
working-directory: samples/audio/wsola_search
|
|
run: |
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run native speed LPF tests (C89, ASan + UBSan)
|
|
working-directory: samples/audio/speed_lpf
|
|
run: make check SANITIZER=address,undefined
|
|
|
|
- name: Build and run experimental sinc HQ tests (C89, ASan + UBSan)
|
|
working-directory: samples/audio/sinc_hq
|
|
run: |
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run sinc AVX tests (C89, ASan + UBSan)
|
|
working-directory: samples/audio/sinc_hq
|
|
run: |
|
|
if grep -qw avx /proc/cpuinfo; then
|
|
make clean
|
|
make -j$(nproc) check CC="gcc -mavx" SANITIZER=address,undefined
|
|
else
|
|
echo "AVX unavailable on this runner; scalar/SSE tests ran above."
|
|
fi
|
|
|
|
- name: Build and run sinc kernel dispatch test (C89, ASan + UBSan)
|
|
working-directory: samples/audio/sinc_dispatch
|
|
run: |
|
|
# A baseline build carries the AVX and AVX-512 kernels through
|
|
# per-function targets and runs each one the runner supports
|
|
# against the scalar kernel; the -mavx build must agree.
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
if grep -qw avx /proc/cpuinfo; then
|
|
make clean
|
|
make -j$(nproc) check CC="gcc -mavx" SANITIZER=address,undefined
|
|
fi
|
|
|
|
- name: Build and run sinc fast-forward test (C89, ASan + UBSan, MinGW)
|
|
working-directory: samples/audio/sinc_fastforward
|
|
run: |
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
# Live ratios under the table's design are decimated first:
|
|
# rejection as a table built for that ratio, the nominal path
|
|
# untouched, the output bound, crossfaded stage changes.
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
if grep -qw avx /proc/cpuinfo; then
|
|
make clean
|
|
make -j$(nproc) check CC="gcc -mavx" SANITIZER=address,undefined
|
|
fi
|
|
make clean
|
|
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
|
|
RUN="wine ./sinc_fastforward_test.exe"
|
|
|
|
- name: Build and run sinc sibling test (C89, ASan + UBSan, MinGW)
|
|
working-directory: samples/audio/sinc_sibling
|
|
run: |
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
# Siblings share their parent's tables: a fresh stream either
|
|
# way, own state only, either free order.
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
make clean
|
|
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
|
|
RUN="wine ./sinc_sibling_test.exe"
|
|
|
|
- name: Build and run mic_handshake_test (ASan + UBSan)
|
|
working-directory: samples/audio/mic_handshake
|
|
run: |
|
|
# The shipping capture worker against a clocked fake device, and
|
|
# only the resampler the microphone flush uses being built.
|
|
make clean
|
|
make -j$(nproc) SANITIZER=address,undefined
|
|
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
|
timeout 300 ./mic_handshake_test 1
|
|
|
|
- name: Build and run pipeline_format_test (ASan + UBSan)
|
|
working-directory: samples/audio/pipeline_format
|
|
run: |
|
|
# Real init and one batch through each format and transport;
|
|
# the integer front pair exists only behind its hint.
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run pipeline_rate_change_test (ASan + UBSan, mingw)
|
|
working-directory: samples/audio/pipeline_rate_change
|
|
run: |
|
|
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
|
# A core rate change taken by the running driver: one device
|
|
# open, the tone at its pitch on both sides, the ring grown
|
|
# where the new rate needs it, time stretch refused.
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
make clean
|
|
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
|
|
LDFLAGS="-lm -static" RUN="wine ./pipeline_rate_change_test.exe"
|
|
|
|
- name: Build and run unity_identity_test (plain, ASan + UBSan)
|
|
working-directory: samples/audio/unity_identity
|
|
run: |
|
|
# The unity passthrough end to end: an s16 batch at a pinned
|
|
# ratio of 1.0 reaches an s16 device with zero conversions,
|
|
# counted through linker wraps; the fallbacks still convert.
|
|
make -j$(nproc) check
|
|
make clean
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run lend_write_test (plain, ASan + UBSan)
|
|
working-directory: samples/audio/lend_write
|
|
run: |
|
|
# The driver lend pair: the pipeline produces its output in
|
|
# the driver's own buffer, bit-identical to the written
|
|
# stream, with write() never called while a lend is live.
|
|
make -j$(nproc) check
|
|
make clean
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run extra-channel capacity test (ASan + UBSan)
|
|
working-directory: samples/audio/extra_capacity
|
|
run: |
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
make clean
|
|
make -j$(nproc) check SIMD_MASK=RESAMPLER_SIMD_SSE SANITIZER=address,undefined
|
|
|
|
- name: Build and run the pipeline fill cap lane (plain)
|
|
shell: bash
|
|
working-directory: samples/audio/pipeline_clocked
|
|
run: |
|
|
set -eu
|
|
# The threaded pipeline against a device that drains on a
|
|
# clock, with Audio Sync the only thing pacing the core: the
|
|
# ring sits full and every publish waits behind it. With
|
|
# audio_pipeline_fill_cap the queue has to come down to the
|
|
# device's buffer and no more than a publish past it, where a
|
|
# blocking inline write leaves it, without the device running
|
|
# dry. With Audio Sync off it has to take the queue down too,
|
|
# with a core up to a frame late not reaching the device as
|
|
# silence. And display-paced with Audio Sync on, rate control
|
|
# has to hold the queue where the frame-synchronous path
|
|
# holds it. With audio the only clock, rate control has to
|
|
# leave the ratio alone, inline and threaded, and still hold
|
|
# the fill when a present blocks behind the runloop's back.
|
|
# Plain, not sanitized: it is the clock that is measured,
|
|
# and a sanitizer's slowdown is a host that starves the
|
|
# consumer whatever the cap does. Underruns with the cap are
|
|
# retried, since a host that takes the consumer off its core
|
|
# shows the same with the cap off; only a cap that never runs
|
|
# clean fails.
|
|
make clean && make -j$(nproc)
|
|
test -x pipeline_clocked_test
|
|
PACED_ONLY=1 timeout 600 ./pipeline_clocked_test 3
|
|
echo "[pass] pipeline_clocked_test (fill cap)"
|
|
|
|
- name: Build and run resampler option tests (C89, ASan + UBSan)
|
|
working-directory: samples/audio/resampler_options
|
|
run: |
|
|
make -j$(nproc) check SANITIZER=address,undefined
|
|
|
|
- name: Build and run CoreMIDI input queue test (ASan + UBSan, TSan)
|
|
working-directory: samples/midi/coremidi_queue
|
|
run: |
|
|
# midi/drivers/coremidi_queue.h is the macOS driver's lock-free
|
|
# input queue; it builds and runs anywhere.
|
|
make check SANITIZER=address,undefined
|
|
make clean
|
|
make check SANITIZER=thread
|
|
|
|
- name: ccache statistics
|
|
if: ${{ !cancelled() }}
|
|
continue-on-error: true
|
|
run: ccache -sv
|
|
|
|
- name: Save ccache
|
|
# Only master's pushes keep a cache: pull requests restore
|
|
# master's, so one push does not evict the next one's.
|
|
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: ~/.ccache
|
|
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
|