mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
781 lines
19 KiB
C
781 lines
19 KiB
C
/**
|
|
* 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 <math.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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 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 },
|
|
{ "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;
|
|
}
|