/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/audiodev.h" static struct LG_AudioDevOps dev; struct LG_AudioDevOps * LG_AudioDevs[] = { &dev, &dev, NULL }; /* Keep the conversion and validation helpers in this unit-test translation * unit. The public provider API is used for all lifecycle tests below. */ #include "../src/audio.c" #include "test.h" #include #include #include #include #include #include #include #include #include #define WAIT_MS 2000U struct AppState g_state; struct AppParams g_params; struct CursorState g_cursor; struct Backend { atomic_uint initN; atomic_uint freeN; atomic_uint setupN; atomic_uint playStartN; atomic_uint playStopN; atomic_uint recStartN; atomic_uint recStopN; atomic_bool setupOK; atomic_bool playOK; atomic_bool recOK; LG_AudioFormat playFormat; LG_AudioPullFn pull; LG_AudioFailureFn playFail; uint32_t playCookie; LG_AudioFormat recFormat; LG_AudioPushFn push; LG_AudioFailureFn recFail; uint32_t recCookie; }; struct Provider { const char * name; bool available; bool attachOK; uint32_t generation; LG_AudioStatusFn status; void * statusOpaque; const LG_AudioEventOps * events; void * eventOpaque; unsigned int listenN; unsigned int unlistenN; unsigned int attachN; unsigned int detachN; unsigned int dataN; atomic_bool badDetach; }; struct UI { MsgBoxConfirmCallback confirm; void * opaque; MsgBoxHandle handle; unsigned int confirmN; unsigned int closeN; atomic_bool block; atomic_bool entered; atomic_bool release; atomic_bool inConfirm; }; static struct Backend b; static struct UI ui; static bool devInit(void) { atomic_fetch_add(&b.initN, 1); return true; } static void devFree(void) { atomic_fetch_add(&b.freeN, 1); } static bool playSetup(const LG_AudioFormat * format, int period, bool requestResampler, bool * resamplerEnabled, int * maxPeriod, int * startFrames, LG_AudioPullFn pull) { (void)period; b.playFormat = *format; b.pull = pull; *resamplerEnabled = requestResampler; *maxPeriod = 256; *startFrames = 0; atomic_fetch_add(&b.setupN, 1); return atomic_load(&b.setupOK); } static bool playStart(LG_AudioFailureFn fail, uint32_t cookie) { b.playFail = fail; b.playCookie = cookie; atomic_fetch_add(&b.playStartN, 1); return atomic_load(&b.playOK); } static void playStop(void) { b.playFail = NULL; b.playCookie = 0; atomic_fetch_add(&b.playStopN, 1); } static bool playRate(double * ratio) { (void)ratio; return true; } static uint64_t playLatency(void) { return 0; } static bool recStart(const LG_AudioFormat * format, LG_AudioPushFn push, LG_AudioFailureFn fail, uint32_t cookie) { b.recFormat = *format; b.push = push; b.recFail = fail; b.recCookie = cookie; atomic_fetch_add(&b.recStartN, 1); return atomic_load(&b.recOK); } static void recStop(void) { b.push = NULL; b.recFail = NULL; b.recCookie = 0; atomic_fetch_add(&b.recStopN, 1); } static void setListener(void * opaque, LG_AudioStatusFn callback, void * callbackOpaque) { struct Provider * p = opaque; p->status = callback; p->statusOpaque = callbackOpaque; if (!callback) { ++p->unlistenN; return; } ++p->listenN; const LG_AudioStatus status = { .available = p->available, .generation = p->generation, }; callback(callbackOpaque, &status); } static bool attach(void * opaque, const LG_AudioEventOps * events, void * eventOpaque) { struct Provider * p = opaque; ++p->attachN; p->events = events; p->eventOpaque = eventOpaque; return p->attachOK; } static void detach(void * opaque) { struct Provider * p = opaque; if (atomic_load(&ui.inConfirm)) atomic_store(&p->badDetach, true); ++p->detachN; p->events = NULL; p->eventOpaque = NULL; } static bool recordData(void * opaque, uint32_t generation, const void * data, size_t frames, const LG_AudioClock * clock) { struct Provider * p = opaque; (void)generation; (void)data; (void)frames; (void)clock; ++p->dataN; return true; } static const LG_AudioOps ops = { .name = "test", .setStatusListener = setListener, .attach = attach, .detach = detach, .recordData = recordData, }; GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max, GraphFormatFn formatFn) { (void)name; (void)buffer; (void)min; (void)max; (void)formatFn; return NULL; } void app_unregisterGraph(GraphHandle handle) { (void)handle; } void app_invalidateGraph(GraphHandle handle) { (void)handle; } void app_showRecord(bool show) { (void)show; } void app_alert(LG_MsgAlert type, const char * format, ...) { (void)type; (void)format; } MsgBoxHandle app_confirmMsgBox(const char * caption, MsgBoxConfirmCallback callback, void * opaque, const char * format, ...) { (void)caption; (void)format; ui.confirm = callback; ui.opaque = opaque; ui.handle = (MsgBoxHandle)(uintptr_t)(++ui.confirmN); if (atomic_load(&ui.block)) { atomic_store(&ui.inConfirm, true); atomic_store(&ui.entered, true); while (!atomic_load(&ui.release)) usleep(1000); atomic_store(&ui.inConfirm, false); } return ui.handle; } void app_msgBoxClose(MsgBoxHandle handle) { if (handle) ++ui.closeN; } static void waitFor(atomic_uint * value, unsigned int target) { for (unsigned int i = 0; i < WAIT_MS; ++i) { if (atomic_load_explicit(value, memory_order_acquire) >= target) return; usleep(1000); } fprintf(stderr, "timeout waiting for %u, got %u\n", target, atomic_load(value)); CHECK(atomic_load(value) >= target); } static void waitBool(atomic_bool * value) { for (unsigned int i = 0; i < WAIT_MS; ++i) { if (atomic_load_explicit(value, memory_order_acquire)) return; usleep(1000); } CHECK(atomic_load(value)); } static LG_AudioFormat makeFormat(LG_AudioSampleFormat sampleFormat) { return (LG_AudioFormat) { .sampleFormat = sampleFormat, .sampleRate = 48000, .channelCount = 2, .channels = { LG_AUDIO_CH_FRONT_LEFT, LG_AUDIO_CH_FRONT_RIGHT, }, }; } static void reset(void) { memset(&audio, 0, sizeof(audio)); memset(&b, 0, sizeof(b)); memset(&ui, 0, sizeof(ui)); memset(&g_state, 0, sizeof(g_state)); memset(&g_params, 0, sizeof(g_params)); memset(&g_cursor, 0, sizeof(g_cursor)); atomic_init(&b.initN, 0); atomic_init(&b.freeN, 0); atomic_init(&b.setupN, 0); atomic_init(&b.playStartN, 0); atomic_init(&b.playStopN, 0); atomic_init(&b.recStartN, 0); atomic_init(&b.recStopN, 0); atomic_init(&b.setupOK, true); atomic_init(&b.playOK, true); atomic_init(&b.recOK, true); atomic_init(&ui.block, false); atomic_init(&ui.entered, false); atomic_init(&ui.release, false); atomic_init(&ui.inConfirm, false); dev = (struct LG_AudioDevOps) { .name = "test", .init = devInit, .free = devFree, .playback = { .setup = playSetup, .start = playStart, .stop = playStop, .setRate = playRate, .latency = playLatency, }, .record = { .start = recStart, .stop = recStop, }, }; g_params.audioResampler = AUDIO_RESAMPLER_BACKEND; g_state.micDefaultState = MIC_DEFAULT_PROMPT; } static void startAudio(void) { lgAudio_init(); CHECK(atomic_load(&b.initN) == 1); CHECK(lgAudio_supportsPlayback()); CHECK(lgAudio_supportsRecord()); } static void stopAudio(void) { lgAudio_free(); CHECK(atomic_load(&b.freeN) == 1); } static void initProvider(struct Provider * p, const char * name, bool available) { memset(p, 0, sizeof(*p)); p->name = name; p->available = available; p->attachOK = true; p->generation = 1; atomic_init(&p->badDetach, false); } static void setStatus(struct Provider * p, bool available, uint32_t generation) { p->available = available; p->generation = generation; CHECK(p->status); const LG_AudioStatus status = { .available = available, .generation = generation, }; p->status(p->statusOpaque, &status); } static bool near(float a, float expected) { return fabsf(a - expected) < 0.000001f; } static void testFormat(void) { LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE); CHECK(audioFormatValid(&format)); CHECK(!audioFormatValid(NULL)); const LG_AudioSampleFormat samples[] = { LG_AUDIO_FMT_U8, LG_AUDIO_FMT_S16_LE, LG_AUDIO_FMT_S24_LE, LG_AUDIO_FMT_S32_LE, LG_AUDIO_FMT_F32_LE, LG_AUDIO_FMT_F64_LE, LG_AUDIO_FMT_F32_NE, }; for (size_t i = 0; i < ARRAY_LENGTH(samples); ++i) { format.sampleFormat = samples[i]; CHECK(audioFormatValid(&format)); } format.sampleFormat = (LG_AudioSampleFormat)-1; CHECK(!audioFormatValid(&format)); format = makeFormat(LG_AUDIO_FMT_S16_LE); format.sampleRate = 7999; CHECK(!audioFormatValid(&format)); format.sampleRate = 384001; CHECK(!audioFormatValid(&format)); format = makeFormat(LG_AUDIO_FMT_S16_LE); format.channelCount = 0; CHECK(!audioFormatValid(&format)); format.channelCount = LG_AUDIO_MAX_CHANNELS + 1; CHECK(!audioFormatValid(&format)); format = makeFormat(LG_AUDIO_FMT_S16_LE); format.channels[1] = (LG_AudioChannel)(LG_AUDIO_CH_TOP_REAR_RIGHT + 1); CHECK(!audioFormatValid(&format)); LG_AudioFormat same = makeFormat(LG_AUDIO_FMT_S16_LE); CHECK(audioFormatEqual(&same, &same)); format = same; format.channels[1] = LG_AUDIO_CH_MONO; CHECK(!audioFormatEqual(&format, &same)); } static void testConvert(void) { float out[4]; const uint8_t u8[] = { 0, 128, 255 }; CHECK(audioConvertToFloat(out, u8, ARRAY_LENGTH(u8), LG_AUDIO_FMT_U8)); CHECK(near(out[0], -1.0f)); CHECK(near(out[1], 0.0f)); CHECK(near(out[2], 127.0f / 128.0f)); const uint8_t s16[] = { 0x00, 0x80, 0xff, 0xff, 0x00, 0x00, 0xff, 0x7f, }; CHECK(audioConvertToFloat(out, s16, 4, LG_AUDIO_FMT_S16_LE)); CHECK(near(out[0], -1.0f)); CHECK(near(out[1], -1.0f / 32768.0f)); CHECK(near(out[2], 0.0f)); CHECK(near(out[3], 32767.0f / 32768.0f)); const uint8_t s24[] = { 0x00, 0x00, 0x80, 0xff, 0xff, 0x7f, }; CHECK(audioConvertToFloat(out, s24, 2, LG_AUDIO_FMT_S24_LE)); CHECK(near(out[0], -1.0f)); CHECK(near(out[1], 8388607.0f / 8388608.0f)); const uint8_t s32[] = { 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x7f, }; CHECK(audioConvertToFloat(out, s32, 2, LG_AUDIO_FMT_S32_LE)); CHECK(near(out[0], -1.0f)); CHECK(near(out[1], 1.0f)); const uint8_t f32le[] = { 0x00, 0x00, 0x80, 0xbe, 0x00, 0x00, 0x40, 0x3f, }; CHECK(audioConvertToFloat(out, f32le, 2, LG_AUDIO_FMT_F32_LE)); CHECK(near(out[0], -0.25f)); CHECK(near(out[1], 0.75f)); const float f32ne[] = { -0.25f, 0.75f }; CHECK(audioConvertToFloat(out, f32ne, 2, LG_AUDIO_FMT_F32_NE)); CHECK(near(out[0], -0.25f)); CHECK(near(out[1], 0.75f)); const uint8_t f64le[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, }; CHECK(audioConvertToFloat(out, f64le, 2, LG_AUDIO_FMT_F64_LE)); CHECK(near(out[0], -0.5f)); CHECK(near(out[1], 0.125f)); CHECK(!audioConvertToFloat(NULL, f32ne, 2, LG_AUDIO_FMT_F32_NE)); CHECK(!audioConvertToFloat(out, NULL, 2, LG_AUDIO_FMT_F32_NE)); CHECK(!audioConvertToFloat(out, f32ne, 2, (LG_AudioSampleFormat)-1)); } static void testProvider(void) { reset(); startAudio(); struct Provider fallback; struct Provider transport; initProvider(&fallback, "fallback", true); initProvider(&transport, "transport", false); lgAudio_setFallback(&ops, &fallback); CHECK(fallback.attachN == 1); CHECK(fallback.detachN == 0); lgAudio_setTransport(&ops, &transport); CHECK(transport.attachN == 0); CHECK(fallback.detachN == 0); transport.attachOK = false; setStatus(&transport, true, 2); CHECK(transport.attachN == 1); CHECK(transport.detachN == 1); CHECK(fallback.detachN == 1); CHECK(fallback.attachN == 2); transport.attachOK = true; setStatus(&transport, true, 3); CHECK(transport.attachN == 2); CHECK(fallback.detachN == 2); setStatus(&transport, false, 4); CHECK(transport.detachN == 2); CHECK(fallback.attachN == 3); const unsigned int detached = fallback.detachN; lgAudio_dropFallback(); CHECK(fallback.detachN == detached); stopAudio(); } static void testPlaybackRetry(void) { reset(); atomic_store(&b.setupOK, false); startAudio(); struct Provider p; initProvider(&p, "provider", true); lgAudio_setFallback(&ops, &p); CHECK(p.events); LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE); p.events->playbackStart(p.eventOpaque, 11, &format, NULL); waitFor(&b.setupN, 2); CHECK(atomic_load(&b.playStartN) == 0); const unsigned int failed = atomic_load(&b.setupN); atomic_store(&b.setupOK, true); uint8_t frames[480 * 2 * 2] = { 0 }; usleep(10000); p.events->playbackData(p.eventOpaque, 11, frames, 480, NULL); waitFor(&b.setupN, failed + 1); for (unsigned int i = 0; i < WAIT_MS; ++i) { if (atomic_load_explicit(&audio.playback.streamGeneration, memory_order_acquire) == 11) break; usleep(1000); } CHECK(atomic_load(&audio.playback.streamGeneration) == 11); for (unsigned int i = 0; i < 8; ++i) p.events->playbackData(p.eventOpaque, 11, frames, 480, NULL); waitFor(&b.playStartN, 1); LG_AudioFailureFn fail = b.playFail; const uint32_t cookie = b.playCookie; CHECK(fail); const unsigned int stopped = atomic_load(&b.playStopN); fail(cookie); waitFor(&b.playStopN, stopped + 1); const unsigned int retry = atomic_load(&b.setupN); usleep(10000); p.events->playbackData(p.eventOpaque, 11, frames, 480, NULL); waitFor(&b.setupN, retry + 1); p.events->playbackStop(p.eventOpaque, 11); stopAudio(); } static void testPlaybackJitter(void) { PlaybackSourceData source = { 0 }; playbackPrepareMediaClock(&source, NULL); bool discontinuity = false; playbackMapMediaTime(&source, NULL, 480, 48000, 0, &discontinuity); source.inputPosition = 480; source.lastArrivalTime = 0; discontinuity = true; playbackMapMediaTime(&source, NULL, 480, 48000, INT64_C(110000000), &discontinuity); CHECK(discontinuity); CHECK(source.arrivalJitterSec == 0.0); memset(&source, 0, sizeof(source)); playbackPrepareMediaClock(&source, NULL); discontinuity = false; playbackMapMediaTime(&source, NULL, 480, 48000, 0, &discontinuity); source.inputPosition = 480; source.lastArrivalTime = 0; playbackMapMediaTime(&source, NULL, 480, 48000, INT64_C(20000000), &discontinuity); CHECK(source.arrivalJitterSec > 0.009); CHECK(source.arrivalJitterSec < 0.011); } static void testPlaybackRecovery(void) { reset(); startAudio(); const LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE); CHECK(playbackStart(&format, NULL, false, false)); CHECK(audio.playback.rateControl == PLAYBACK_RATE_BACKEND); playbackSetState(STREAM_STATE_RUN); PlaybackSourceData * source = &audio.playback.sourceData; const int64_t arrival = nanotime(); playbackClockReset(&source->deviceClock, arrival, 0.0, 1.0 / format.sampleRate); source->deviceClockStable = true; source->outputPosition = 0; source->lastRatio = 0.999; source->lastClockRatio = 1.0002; source->sourcePacketDurationSec = 0.010; playbackPublishDeviceTiming(16, arrival, 0, 0.0, 0); CHECK(ringbuffer_consume(audio.playback.buffer, NULL, 16) == 16); CHECK(ringbuffer_getCount(audio.playback.buffer) == -16); uint8_t frames[1200 * 2 * 2] = { 0 }; CHECK(playbackData(frames, 1200, NULL, arrival) == PLAYBACK_DATA_PROCESSED); CHECK(source->sourcePacketDurationSec < 0.010); CHECK(source->sourcePacketDurationSec > 0.009); CHECK(source->sourcePacketRecovering); CHECK(source->outputPosition == 1770); CHECK(ringbuffer_getCount(audio.playback.buffer) == 1754); CHECK(fabs(atomic_load(&audio.playback.backendResampleRatio) - source->lastClockRatio) < 0.000001); CHECK(playbackData(frames, 1200, NULL, arrival + 1000) == PLAYBACK_DATA_PROCESSED); CHECK(source->sourcePacketDurationSec < 0.010); CHECK(source->sourcePacketRecovering); CHECK(playbackData(frames, 480, NULL, arrival + INT64_C(10000000)) == PLAYBACK_DATA_PROCESSED); CHECK(!source->sourcePacketRecovering); CHECK(fabs(source->sourcePacketDurationSec - 0.010) < 0.000001); stopAudio(); } static void testPlaybackResume(void) { reset(); g_params.audioDebug = true; startAudio(); struct Provider p; initProvider(&p, "provider", true); lgAudio_setFallback(&ops, &p); CHECK(p.events); const LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE); p.events->playbackStart(p.eventOpaque, 11, &format, NULL); for (unsigned int i = 0; i < WAIT_MS; ++i) { if (atomic_load_explicit(&audio.playback.streamGeneration, memory_order_acquire) == 11) break; usleep(1000); } CHECK(atomic_load(&audio.playback.streamGeneration) == 11); playbackSetState(STREAM_STATE_RUN); audio.playback.backendStartTime = nanotime(); uint8_t output[16 * 2 * 2]; CHECK(b.pull(output, 16) == 16); CHECK(b.pull(output, 16) == 16); CHECK(atomic_load(&audio.playback.underruns) == 1); const int refill = -ringbuffer_getCount(audio.playback.buffer) + 16; CHECK(ringbuffer_append(audio.playback.buffer, NULL, refill) == refill); CHECK(b.pull(output, 16) == 16); CHECK(!audio.playback.deviceData.underrunning); CHECK(b.pull(output, 16) == 16); CHECK(b.pull(output, 16) == 16); CHECK(atomic_load(&audio.playback.underruns) == 2); const unsigned int oldSyncEpoch = atomic_load_explicit( &audio.playback.syncDiagnosticsEpoch, memory_order_acquire); LG_LOCK(audio.playback.sourceLock); PlaybackDiagnostics * diagnostics = playbackDiagnosticsLocked(); diagnostics->sync = (PlaybackSyncDiagnostics) { .epoch = oldSyncEpoch, .underruns = 99, }; diagnostics->pending |= PLAYBACK_DIAGNOSTIC_SYNC_LOG; LG_UNLOCK(audio.playback.sourceLock); atomic_store(&audio.playback.backlogTrimTarget, 0); atomic_store(&audio.playback.backlogTrimmedFrames, 123); audio.playback.sourceData.bufferOverruns = 7; audio.playback.sourceData.backlogTrimArmed = false; p.events->playbackStop(p.eventOpaque, 11); CHECK(playbackGetState() == STREAM_STATE_KEEP_ALIVE); CHECK(atomic_load(&audio.playback.underruns) == 0); CHECK(!audio.playback.deviceData.underrunning); CHECK(atomic_load(&audio.playback.backlogTrimTarget) == -1); CHECK(atomic_load(&audio.playback.backlogTrimmedFrames) == 0); CHECK(audio.playback.sourceData.bufferOverruns == 0); CHECK(audio.playback.sourceData.backlogTrimArmed); CHECK(atomic_load(&audio.playback.syncDiagnosticsEpoch) != oldSyncEpoch); CHECK(!(audio.playback.diagnostics.pending & PLAYBACK_DIAGNOSTIC_SYNC_LOG)); CHECK(b.pull(output, 16) == 16); CHECK(b.pull(output, 16) == 16); CHECK(atomic_load(&audio.playback.underruns) == 0); const unsigned int setupN = atomic_load(&b.setupN); p.events->playbackStart(p.eventOpaque, 12, &format, NULL); CHECK(atomic_load(&audio.playback.streamGeneration) == 12); CHECK(playbackGetState() == STREAM_STATE_RESUMING); CHECK(atomic_load(&b.setupN) == setupN); uint8_t input[480 * 2 * 2] = { 0 }; p.events->playbackData(p.eventOpaque, 12, input, 480, NULL); CHECK(atomic_load(&audio.playback.streamGeneration) == 12); CHECK(playbackGetState() == STREAM_STATE_RUN); CHECK(atomic_load(&audio.playback.underruns) == 0); p.events->playbackStop(p.eventOpaque, 12); CHECK(playbackGetState() == STREAM_STATE_KEEP_ALIVE); PlaybackSourceData * source = &audio.playback.sourceData; const int64_t arrival = nanotime(); const int64_t idleFrames = INT64_C(30) * format.sampleRate; const double disciplinedFrameSec = 1.0002 / format.sampleRate; playbackClockReset(&source->deviceClock, arrival - INT64_C(30000000000), 0.0, disciplinedFrameSec); playbackClockReset(&source->outputClock, arrival - INT64_C(30000000000), 0.0, disciplinedFrameSec); source->deviceClockStable = true; source->devicePositionOffsetFrames = 37.5; source->deviceTimingSequence = atomic_load_explicit( &audio.playback.deviceTiming.sequence, memory_order_acquire); const int idleDebt = clamp( (int64_t)ringbuffer_getCount(audio.playback.buffer) + idleFrames, INT64_C(0), (int64_t)INT_MAX); CHECK(ringbuffer_consume( audio.playback.buffer, NULL, idleDebt) == idleDebt); playbackPublishDeviceTiming(16, arrival, idleFrames, (double)idleFrames, source->deviceDiscontinuity); p.events->playbackStart(p.eventOpaque, 13, &format, NULL); CHECK(atomic_load(&audio.playback.streamGeneration) == 13); p.events->playbackData(p.eventOpaque, 13, input, 480, NULL); CHECK(playbackGetState() == STREAM_STATE_RUN); CHECK(source->deviceClock.time == arrival); CHECK(source->deviceClock.position == idleFrames); CHECK(fabs(source->deviceClock.frameSec - disciplinedFrameSec) < 1.0e-15); CHECK(source->devicePositionOffsetFrames == 37.5); p.events->playbackStop(p.eventOpaque, 13); stopAudio(); } static void testConsent(void) { reset(); startAudio(); struct Provider p; initProvider(&p, "provider", true); lgAudio_setFallback(&ops, &p); LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE); p.events->recordStart(p.eventOpaque, 20, &format); CHECK(ui.confirmN == 1); CHECK(ui.confirm); MsgBoxConfirmCallback stale = ui.confirm; void * staleOpaque = ui.opaque; p.events->recordStop(p.eventOpaque, 20); CHECK(ui.closeN == 1); stale(true, staleOpaque); CHECK(atomic_load(&b.recStartN) == 0); p.events->recordStart(p.eventOpaque, 21, &format); CHECK(ui.confirmN == 2); CHECK(ui.confirm); stale(true, staleOpaque); CHECK(atomic_load(&b.recStartN) == 0); ui.confirm(true, ui.opaque); waitFor(&b.recStartN, 1); CHECK(b.recFormat.sampleRate == format.sampleRate); CHECK(b.recFormat.channelCount == format.channelCount); CHECK(b.push); uint8_t frames[16] = { 0 }; CHECK(b.push(frames, 4, NULL)); CHECK(p.dataN == 1); p.events->recordStop(p.eventOpaque, 21); waitFor(&b.recStopN, 1); stopAudio(); } struct RecordCall { struct Provider * provider; LG_AudioFormat format; }; static void * callRecord(void * opaque) { struct RecordCall * call = opaque; call->provider->events->recordStart(call->provider->eventOpaque, 30, &call->format); return NULL; } struct SwitchCall { struct Provider * provider; }; static void * switchProvider(void * opaque) { struct SwitchCall * call = opaque; lgAudio_setTransport(&ops, call->provider); return NULL; } static void testQuiesce(void) { reset(); startAudio(); struct Provider fallback; struct Provider transport; initProvider(&fallback, "fallback", true); initProvider(&transport, "transport", true); lgAudio_setFallback(&ops, &fallback); atomic_store(&ui.block, true); struct RecordCall record = { .provider = &fallback, .format = makeFormat(LG_AUDIO_FMT_S16_LE), }; pthread_t recordThread; CHECK(pthread_create(&recordThread, NULL, callRecord, &record) == 0); waitBool(&ui.entered); struct SwitchCall change = { .provider = &transport }; pthread_t switchThread; CHECK(pthread_create(&switchThread, NULL, switchProvider, &change) == 0); for (unsigned int i = 0; i < WAIT_MS; ++i) { if (atomic_load_explicit(&audio.activeCallbacks, memory_order_acquire) & ACTIVE_CALLBACK_WAITING) break; usleep(1000); } CHECK(atomic_load(&audio.activeCallbacks) & ACTIVE_CALLBACK_WAITING); CHECK(fallback.detachN == 0); CHECK(!atomic_load(&fallback.badDetach)); MsgBoxConfirmCallback stale = ui.confirm; void * staleOpaque = ui.opaque; atomic_store(&ui.release, true); CHECK(pthread_join(recordThread, NULL) == 0); CHECK(pthread_join(switchThread, NULL) == 0); CHECK(fallback.detachN == 1); CHECK(transport.attachN == 1); CHECK(ui.closeN == 1); CHECK(!atomic_load(&fallback.badDetach)); CHECK(stale); stale(true, staleOpaque); CHECK(atomic_load(&b.recStartN) == 0); stopAudio(); } struct Test { const char * name; void (*run)(void); }; static const struct Test tests[] = { { "format" , testFormat }, { "convert" , testConvert }, { "provider" , testProvider }, { "playback-retry", testPlaybackRetry }, { "jitter" , testPlaybackJitter }, { "recovery" , testPlaybackRecovery }, { "resume" , testPlaybackResume }, { "consent" , testConsent }, { "quiesce" , testQuiesce }, }; int main(int argc, char ** argv) { debug_init(); if (argc == 2) { for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i) if (strcmp(argv[1], tests[i].name) == 0) { tests[i].run(); return 0; } fprintf(stderr, "unknown test: %s\n", argv[1]); return EXIT_FAILURE; } if (argc != 1) return EXIT_FAILURE; for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i) tests[i].run(); return 0; }