audio: add AAudio driver for Android 8.0 and up

AAudio is resolved from libaaudio.so at runtime, so the build needs no
API 26 headers, the library gains no dependency on it, and the binary
still runs on older releases. Below API 26 the frontend takes the
driver out of the list at startup, before the configuration or the
menu reads it; OpenSL stays the default.

The stream is a low-latency callback stream at the device's own rate,
fed from a mirrored retro_spsc ring the writer parks on through the
eventcount. The device stage is two bursts and the ring holds the rest
of the latency setting. The driver provides the lend pair,
wait_writable, frames consumed on the device's clock and underruns.
Layouts wider than stereo open as a channel mask on API 32 and as a
count with Android's canonical positions before it. Outputs can be
picked from AudioManager's list. A disconnect frees the writer and
asks the frontend to reopen, and a late report from a closed stream is
dropped.

A wider layout whose output takes fewer PCM channels than asked, but
lists AC-3 among its encodings, goes as AC-3 over IEC 61937 (API 34),
encoded with rac3 as WASAPI does. Underruns are filled with pause
bursts, and a burst is written whole or not at all. Android.mk gains
HAVE_RAC3 for it.

samples/audio/aaudio runs the driver against a scripted libaaudio
behind dylib_load, on Linux and with mingw-w64.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-19 07:03:11 +02:00
1 parent 23aed644e1
commit b8e8f1ae92
16 files changed
+2531 -1

No files matched your search

+12
View File
@@ -286,6 +286,9 @@ audio_driver_t *audio_drivers[] = {
#ifdef HAVE_SL
&audio_opensl,
#endif
#ifdef HAVE_AAUDIO
&audio_aaudio,
#endif
#ifdef HAVE_ROAR
&audio_roar,
#endif
@@ -367,6 +370,15 @@ audio_driver_t *audio_drivers[] = {
NULL,
};
void audio_driver_list_remove(const audio_driver_t *drv)
{
size_t i, j;
for (i = j = 0; audio_drivers[i]; i++)
if (audio_drivers[i] != drv)
audio_drivers[j++] = audio_drivers[i];
audio_drivers[j] = NULL;
}
#ifdef HAVE_MICROPHONE
static microphone_driver_state_t mic_driver_st;
+5
View File
@@ -1483,6 +1483,7 @@ extern audio_driver_t audio_tinyalsa;
extern audio_driver_t audio_roar;
extern audio_driver_t audio_openal;
extern audio_driver_t audio_opensl;
extern audio_driver_t audio_aaudio;
extern audio_driver_t audio_jack;
extern audio_driver_t audio_sdl1;
extern audio_driver_t audio_sdl2;
@@ -1596,6 +1597,10 @@ bool audio_driver_get_device_clock_ppm(double *ppm);
extern audio_driver_t *audio_drivers[];
/* Takes a driver the platform turns out not to have out of
* audio_drivers[], before the configuration or the menu reads it. */
void audio_driver_list_remove(const audio_driver_t *drv);
RETRO_END_DECLS
#endif /* __AUDIO_DRIVER__H */
+39
View File
@@ -0,0 +1,39 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2026 - The RetroArch team
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANDROID_AUDIO_H
#define __ANDROID_AUDIO_H
#include <stdint.h>
#include <boolean.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
struct string_list;
/* The outputs AudioManager lists, as "<id> (<name>, <type>)" entries;
* NULL when they cannot be read. Caller frees. */
struct string_list *frontend_android_audio_outputs_new(void);
/* What the output with this id says it takes: the most PCM channels
* (0 when it lists none, which means any count) and whether AC-3 is
* among its encodings. False when no output has the id. */
bool frontend_android_audio_device_caps(int32_t id,
unsigned *max_channels, bool *ac3);
RETRO_END_DECLS
#endif
File diff suppressed because it is too large. Load diff
+223
View File
@@ -51,6 +51,11 @@
#ifdef ANDROID
#include <android/log.h>
#include <sys/system_properties.h>
#include <lists/string_list.h>
#include "../../audio/common/android_audio.h"
#ifdef HAVE_AAUDIO
#include "../../audio/audio_driver.h"
#endif
#ifdef HAVE_SAF
#include <vfs/vfs_implementation_saf.h>
#include "../../menu/menu_cbs.h"
@@ -835,6 +840,214 @@ void frontend_android_get_version_sdk(int32_t *sdk)
*sdk = (int32_t)strtol(os_version_str, NULL, 10);
}
static const char *android_audio_device_type(jint type)
{
switch (type)
{
case 1: return "earpiece";
case 2: return "speaker";
case 3: return "wired headset";
case 4: return "wired headphones";
case 5: return "analog line";
case 6: return "digital line";
case 7: return "Bluetooth SCO";
case 8: return "Bluetooth A2DP";
case 9: return "HDMI";
case 10: return "HDMI ARC";
case 11: return "USB device";
case 12: return "USB accessory";
case 13: return "dock";
case 21: return "bus";
case 22: return "USB headset";
case 23: return "hearing aid";
case 26: return "BLE headset";
case 27: return "BLE speaker";
case 29: return "HDMI eARC";
case 30: return "BLE broadcast";
default: break;
}
return NULL;
}
/* The largest entry of an int[] from Java; 0 when it is empty. With
* find, whether find is among them instead (1 or 0). */
static jint android_int_array_scan(JNIEnv *env, jobject arr, jint find,
bool want_find)
{
jsize n, k;
jint best = 0;
jint *v;
if (!arr)
return 0;
n = (*env)->GetArrayLength(env, (jintArray)arr);
if (n <= 0 || !(v = (*env)->GetIntArrayElements(env, (jintArray)arr, NULL)))
return 0;
for (k = 0; k < n; k++)
{
if (want_find && v[k] == find)
best = 1;
else if (!want_find && v[k] > best)
best = v[k];
}
(*env)->ReleaseIntArrayElements(env, (jintArray)arr, v, JNI_ABORT);
return best;
}
/* Walks AudioManager's outputs: appends each to list where one is
* given, and fills the caps of the output with want_id where that is
* not zero. True when that output was found. */
static bool android_audio_outputs_walk(struct string_list *list,
int32_t want_id, unsigned *max_channels, bool *ac3)
{
union string_list_elem_attr attr;
jclass ctx_class, am_class, dev_class = NULL;
jmethodID get_service, get_devices;
jmethodID get_id = NULL, get_type = NULL, get_name = NULL, to_string = NULL;
jmethodID get_counts = NULL, get_encodings = NULL;
jobject am, devices;
jstring svc;
jsize i, count;
bool found = false;
JNIEnv *env = jni_thread_getenv();
if (!env || !g_android || !g_android->activity)
return false;
if ((*env)->PushLocalFrame(env, 16) != 0)
{
JNI_EXCEPTION(env);
return false;
}
attr.i = 0;
svc = (*env)->NewStringUTF(env, "audio");
GET_OBJECT_CLASS(env, ctx_class, g_android->activity->clazz);
if (!svc || !ctx_class)
goto done;
GET_METHOD_ID(env, get_service, ctx_class, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
if (!get_service)
goto done;
CALL_OBJ_METHOD_PARAM(env, am, g_android->activity->clazz,
get_service, svc);
if (!am)
goto done;
GET_OBJECT_CLASS(env, am_class, am);
if (!am_class)
goto done;
GET_METHOD_ID(env, get_devices, am_class, "getDevices",
"(I)[Landroid/media/AudioDeviceInfo;");
if (!get_devices)
goto done;
/* AudioManager.GET_DEVICES_OUTPUTS */
CALL_OBJ_METHOD_PARAM(env, devices, am, get_devices, (jint)2);
if (!devices)
goto done;
count = (*env)->GetArrayLength(env, (jobjectArray)devices);
for (i = 0; i < count && !found; i++)
{
jobject dev;
jint id = 0;
jint type = 0;
if ((*env)->PushLocalFrame(env, 8) != 0)
{
JNI_EXCEPTION(env);
break;
}
dev = (*env)->GetObjectArrayElement(env, (jobjectArray)devices, i);
JNI_EXCEPTION(env);
if (dev && !dev_class)
{
jclass obj_class;
/* Popped with the frame below; the IDs outlive it. */
GET_OBJECT_CLASS(env, dev_class, dev);
FIND_CLASS(env, obj_class, "java/lang/Object");
if (dev_class)
{
GET_METHOD_ID(env, get_id, dev_class, "getId", "()I");
GET_METHOD_ID(env, get_type, dev_class, "getType", "()I");
GET_METHOD_ID(env, get_name, dev_class, "getProductName",
"()Ljava/lang/CharSequence;");
GET_METHOD_ID(env, get_counts, dev_class, "getChannelCounts", "()[I");
GET_METHOD_ID(env, get_encodings, dev_class, "getEncodings", "()[I");
}
if (obj_class)
GET_METHOD_ID(env, to_string, obj_class, "toString",
"()Ljava/lang/String;");
}
if (!dev || !get_id || !get_type)
{
(*env)->PopLocalFrame(env, NULL);
continue;
}
CALL_INT_METHOD(env, id, dev, get_id);
CALL_INT_METHOD(env, type, dev, get_type);
if (want_id && id == want_id)
{
jobject counts = NULL, encodings = NULL;
found = true;
if (get_counts)
CALL_OBJ_METHOD(env, counts, dev, get_counts);
if (get_encodings)
CALL_OBJ_METHOD(env, encodings, dev, get_encodings);
if (max_channels)
*max_channels = (unsigned)android_int_array_scan(env, counts, 0, false);
/* AudioFormat.ENCODING_AC3 */
if (ac3)
*ac3 = android_int_array_scan(env, encodings, 5, true) != 0;
}
/* Telephony and remote submix are not places to play to. */
if (list && get_name && to_string && id > 0 && type != 18 && type != 25)
{
char entry[256];
const char *name = NULL;
const char *kind = android_audio_device_type(type);
jobject cs = NULL;
jstring str = NULL;
CALL_OBJ_METHOD(env, cs, dev, get_name);
if (cs)
CALL_OBJ_METHOD(env, str, cs, to_string);
if (str)
name = (*env)->GetStringUTFChars(env, str, NULL);
if (kind)
snprintf(entry, sizeof(entry), "%d (%s, %s)", (int)id,
(name && *name) ? name : "Unknown", kind);
else
snprintf(entry, sizeof(entry), "%d (%s)", (int)id,
(name && *name) ? name : "Unknown");
if (name)
(*env)->ReleaseStringUTFChars(env, str, name);
string_list_append(list, entry, attr);
}
(*env)->PopLocalFrame(env, NULL);
}
done:
(*env)->PopLocalFrame(env, NULL);
return found;
}
struct string_list *frontend_android_audio_outputs_new(void)
{
struct string_list *list = string_list_new();
if (list)
android_audio_outputs_walk(list, 0, NULL, NULL);
return list;
}
bool frontend_android_audio_device_caps(int32_t id,
unsigned *max_channels, bool *ac3)
{
*max_channels = 0;
*ac3 = false;
if (id <= 0)
return false;
return android_audio_outputs_walk(NULL, id, max_channels, ac3);
}
static bool device_is_xperia_play(const char *name)
{
if (
@@ -3338,6 +3551,16 @@ static void frontend_unix_init(void *data)
if (!android_app)
return;
#ifdef HAVE_AAUDIO
/* AAudio is Android 8.0 (API 26) and up. */
{
int32_t sdk = 0;
frontend_android_get_version_sdk(&sdk);
if (sdk < 26)
audio_driver_list_remove(&audio_aaudio);
}
#endif
android_app->config = AConfiguration_new();
AConfiguration_fromAssetManager(android_app->config,
android_app->activity->assetManager);
+4
View File
@@ -1194,6 +1194,10 @@ AUDIO
#include "../audio/drivers/opensl.c"
#endif
#ifdef HAVE_AAUDIO
#include "../audio/drivers/aaudio.c"
#endif
#ifdef HAVE_PIPEWIRE
#include "../audio/drivers/pipewire.c"
#include "../audio/common/pipewire.c"
+4
View File
@@ -101,6 +101,10 @@ MSG_HASH(
MENU_ENUM_LABEL_AUDIO_DRIVER_SL,
MENU_ENUM_LABEL_AUDIO_DRIVER_SL_STR
)
MSG_HASH(
MENU_ENUM_LABEL_AUDIO_DRIVER_AAUDIO,
MENU_ENUM_LABEL_AUDIO_DRIVER_AAUDIO_STR
)
MSG_HASH(
MENU_ENUM_LABEL_AUDIO_DRIVER_SDL,
MENU_ENUM_LABEL_AUDIO_DRIVER_SDL_STR
+2
View File
@@ -352,6 +352,8 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_AL), len);
else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_SL)))
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_SL), len);
else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_AAUDIO)))
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_AAUDIO), len);
else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_DSOUND)))
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_DSOUND), len);
else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_WASAPI)))
+4
View File
@@ -3361,6 +3361,10 @@ MSG_HASH(
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_SL,
"OpenSL driver."
)
MSG_HASH(
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_AAUDIO,
"AAudio driver, Android 8.0 and later. Lower latency than OpenSL; surround layouts go to an output that takes only stereo PCM as AC-3 where it decodes it (Android 14 and later)."
)
MSG_HASH(
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_DSOUND,
"DirectSound driver. DirectSound is used mainly from Windows 95 to Windows XP."
+2
View File
@@ -23948,6 +23948,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_AUDIO_DRIVER_COREAUDIO,
MENU_ENUM_LABEL_AUDIO_DRIVER_AL,
MENU_ENUM_LABEL_AUDIO_DRIVER_SL,
MENU_ENUM_LABEL_AUDIO_DRIVER_AAUDIO,
MENU_ENUM_LABEL_AUDIO_DRIVER_SDL,
MENU_ENUM_LABEL_AUDIO_DRIVER_SDL2,
MENU_ENUM_LABEL_AUDIO_DRIVER_DSOUND,
@@ -23979,6 +23980,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_COREAUDIO,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_AL,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_SL,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_AAUDIO,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_SDL,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_SDL2,
MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_DSOUND,
+1
View File
@@ -29,6 +29,7 @@
#define MENU_ENUM_LABEL_AUDIO_DRIVER_COREAUDIO_STR "coreaudio"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_AL_STR "openal"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_SL_STR "opensl"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_AAUDIO_STR "aaudio"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_SDL_STR "sdl"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_SDL2_STR "sdl2"
#define MENU_ENUM_LABEL_AUDIO_DRIVER_DSOUND_STR "dsound"
+3 -1
View File
@@ -216,7 +216,9 @@ endif
DEFINES += -DHAVE_7ZIP \
\
-DHAVE_SL
-DHAVE_SL \
-DHAVE_AAUDIO \
-DHAVE_RAC3
ifeq ($(HAVE_CHEEVOS),1)
DEFINES += -DHAVE_CHEEVOS \
+72
View File
@@ -0,0 +1,72 @@
# audio/drivers/aaudio.c against a scripted libaaudio behind
# dylib_load: the link carries no AAudio library, as on a device the
# driver only has what it resolves at runtime. Builds on Linux and with
# mingw-w64: make CC=x86_64-w64-mingw32-gcc check
include ../../sample_objdir.mk
CC ?= gcc
TARGET := aaudio_test
CORE_DIR = ../../..
LIBRETRO_COMM_DIR = $(CORE_DIR)/libretro-common
INCDIRS := -I. -I$(LIBRETRO_COMM_DIR)/include -I$(CORE_DIR)
SOURCES_C := \
aaudio_test.c \
stubs_retroarch.c \
$(CORE_DIR)/audio/drivers/aaudio.c \
$(LIBRETRO_COMM_DIR)/queues/retro_spsc.c \
$(LIBRETRO_COMM_DIR)/formats/ac3/rac3_frame.c \
$(LIBRETRO_COMM_DIR)/formats/ac3/rac3_encode.c \
$(LIBRETRO_COMM_DIR)/formats/iec61937/iec61937.c \
$(LIBRETRO_COMM_DIR)/lists/string_list.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \
$(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strldup.c \
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c
CFLAGS += -std=gnu99 -Wall -Werror=incompatible-pointer-types \
-Werror=implicit-function-declaration
CFLAGS += -DRARCH_INTERNAL -DHAVE_AAUDIO -DHAVE_RAC3 -DHAVE_DYLIB -DHAVE_THREADS -D_GNU_SOURCE
ifeq ($(findstring mingw,$(CC)),)
LDFLAGS += -lpthread
RUN := ./$(TARGET)
else
TARGET := $(TARGET).exe
RUN := wine ./$(TARGET)
endif
LDFLAGS += -lm
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer -g1 $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
all: $(TARGET)
%.o: %.c
$(CC) $(INCDIRS) $(CFLAGS) -c -o $@ $<
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) $(INCDIRS) $(CFLAGS) -c -o $@ $<
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS)
check: $(TARGET)
$(RUN)
$(RUN) nolib
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS) aaudio_test aaudio_test.exe
.PHONY: all clean check
+955
View File
@@ -0,0 +1,955 @@
/* audio/drivers/aaudio.c against a scripted libaaudio behind
* dylib_load: the link carries no AAudio library, so the driver only
* reaches the device through the symbols it resolves at runtime.
*
* Covers the open sequence (format, rate, channel mask on API 32 and
* the count-only path before it, device selection and its fallback),
* the ring and device-stage sizing, data reaching the device in order,
* underruns, the lend pair, pause/resume, a device that stops calling
* back, and a disconnect - which must free the writer and ask the
* frontend to reopen, while a late report from a stream already gone
* must not.
*
* aaudio_test the suite
* aaudio_test nolib libaaudio.so absent: init fails cleanly */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <retro_atomic.h>
#include <retro_timers.h>
#include <dynamic/dylib.h>
#include <rthreads/rthreads.h>
#include <lists/string_list.h>
#include <formats/iec61937.h>
#include "../../../configuration.h"
#include "../../../audio/audio_driver.h"
extern audio_driver_t audio_aaudio;
extern size_t stub_device_latency;
extern uint32_t stub_requested_layout;
extern int32_t stub_caps_id;
extern unsigned stub_caps_channels;
extern bool stub_caps_ac3;
static unsigned failures;
#define CHECK(cond, ...) do { if (!(cond)) { printf(" FAIL: "); printf(__VA_ARGS__); printf("\n"); failures++; } } while (0)
/* ---- the scripted library ---------------------------------------- */
typedef int32_t (*data_cb_t)(void*, void*, void*, int32_t);
typedef void (*err_cb_t)(void*, void*, int32_t);
#define MOCK_ERR_ILLEGAL (-898)
#define MOCK_ERR_DISCONNECTED (-899)
#define ST_OPEN 2
#define ST_STARTED 4
#define ST_PAUSED 6
#define ST_STOPPED 10
#define ST_CLOSED 12
typedef struct mock_builder
{
data_cb_t data_cb;
void *data_user;
err_cb_t err_cb;
void *err_user;
uint32_t mask;
int32_t device_id;
int32_t rate;
int32_t channels;
int32_t format;
int32_t sharing;
int32_t perf;
int32_t usage;
int32_t direction;
bool mask_set;
} mock_builder_t;
typedef struct mock_stream
{
mock_builder_t b;
uint32_t granted_mask;
int32_t granted_channels;
int32_t state;
int32_t buffer_size;
int starts, pauses, stops;
} mock_stream_t;
static int mock_api_level = 32;
static bool mock_lib_missing;
static int32_t mock_refuse_device;
static bool mock_refuse_mask;
static int32_t mock_burst = 192;
/* Frames the device asks for per callback; the burst unless set. */
static int32_t mock_cb_frames;
/* The output takes IEC 61937 (API 34 and a sink that decodes AC-3). */
static bool mock_iec_ok;
#define MOCK_DEFAULT_DEVICE 3
static int32_t mock_capacity = 4096;
static int mock_opens, mock_closes;
static mock_builder_t mock_last;
/* The device thread: runs the data callback a burst at a time while
* the stream is started, under a lock requestStop and close take, as
* AAudio's own stop does. */
static slock_t *dev_lock;
static sthread_t *dev_thread;
static retro_atomic_int_t dev_run;
static bool dev_pump;
static mock_stream_t *dev_stream;
static uint8_t dev_capture[1 << 20];
static size_t dev_captured;
static int32_t mk_create(mock_builder_t **b)
{
*b = (mock_builder_t*)calloc(1, sizeof(**b));
return *b ? 0 : -1;
}
static int32_t mk_delete(mock_builder_t *b) { free(b); return 0; }
static void mk_dev(mock_builder_t *b, int32_t v) { b->device_id = v; }
static void mk_rate(mock_builder_t *b, int32_t v) { b->rate = v; }
static void mk_ch(mock_builder_t *b, int32_t v) { b->channels = v; }
static void mk_fmt(mock_builder_t *b, int32_t v) { b->format = v; }
static void mk_share(mock_builder_t *b, int32_t v){ b->sharing = v; }
static void mk_dir(mock_builder_t *b, int32_t v) { b->direction = v; }
static void mk_perf(mock_builder_t *b, int32_t v) { b->perf = v; }
static void mk_usage(mock_builder_t *b, int32_t v){ b->usage = v; }
static void mk_ctype(mock_builder_t *b, int32_t v){ (void)b; (void)v; }
static void mk_mask(mock_builder_t *b, uint32_t v){ b->mask = v; b->mask_set = true; }
static void mk_data(mock_builder_t *b, data_cb_t cb, void *u) { b->data_cb = cb; b->data_user = u; }
static void mk_err(mock_builder_t *b, err_cb_t cb, void *u) { b->err_cb = cb; b->err_user = u; }
static unsigned popcount32(uint32_t m)
{
unsigned n = 0;
for (; m; m &= m - 1)
n++;
return n;
}
static int32_t mk_open(mock_builder_t *b, mock_stream_t **out)
{
mock_stream_t *s;
mock_opens++;
*out = NULL;
if (b->device_id && b->device_id == mock_refuse_device)
return MOCK_ERR_ILLEGAL;
if (b->mask_set && mock_refuse_mask)
return MOCK_ERR_ILLEGAL;
if (b->format == 5 && (!mock_iec_ok || b->channels != 2 || b->rate != 48000))
return MOCK_ERR_ILLEGAL;
if (!(s = (mock_stream_t*)calloc(1, sizeof(*s))))
return -1;
s->b = *b;
s->state = ST_OPEN;
if (b->mask_set)
{
s->granted_mask = b->mask;
s->granted_channels = (int32_t)popcount32(b->mask);
}
else
{
s->granted_mask = 0xFFFFFFFFu;
s->granted_channels = b->channels ? b->channels : 2;
}
if (!s->b.format)
s->b.format = 2;
if (!s->b.rate)
s->b.rate = 48000;
if (!s->b.device_id)
s->b.device_id = MOCK_DEFAULT_DEVICE;
s->buffer_size = mock_capacity;
/* What the driver ended up with: refused opens are not recorded. */
mock_last = *b;
*out = s;
return 0;
}
static int32_t mk_close(mock_stream_t *s)
{
slock_lock(dev_lock);
if (dev_stream == s)
dev_stream = NULL;
slock_unlock(dev_lock);
mock_closes++;
free(s);
return 0;
}
static int32_t mk_start(mock_stream_t *s)
{
slock_lock(dev_lock);
s->starts++;
s->state = ST_STARTED;
dev_stream = s;
slock_unlock(dev_lock);
return 0;
}
static int32_t mk_pause(mock_stream_t *s)
{
slock_lock(dev_lock);
s->pauses++;
s->state = ST_PAUSED;
slock_unlock(dev_lock);
return 0;
}
static int32_t mk_stop(mock_stream_t *s)
{
slock_lock(dev_lock);
s->stops++;
s->state = ST_STOPPED;
slock_unlock(dev_lock);
return 0;
}
static int32_t mk_wait(mock_stream_t *s, int32_t in, int32_t *next, int64_t ns)
{
(void)in; (void)ns;
*next = s->state;
return 0;
}
static int32_t mk_set_bufsize(mock_stream_t *s, int32_t f)
{
if (f > mock_capacity)
f = mock_capacity;
s->buffer_size = f;
return f;
}
static int32_t mk_get_bufsize(mock_stream_t *s) { return s->buffer_size; }
static int32_t mk_get_cap(mock_stream_t *s) { (void)s; return mock_capacity; }
static int32_t mk_get_burst(mock_stream_t *s) { (void)s; return mock_burst; }
static int32_t mk_get_rate(mock_stream_t *s) { return s->b.rate; }
static int32_t mk_get_ch(mock_stream_t *s) { return s->granted_channels; }
static int32_t mk_get_fmt(mock_stream_t *s) { return s->b.format; }
static int32_t mk_get_share(mock_stream_t *s) { return s->b.sharing; }
static int32_t mk_get_perf(mock_stream_t *s) { return s->b.perf; }
static int32_t mk_get_state(mock_stream_t *s) { return s->state; }
static int32_t mk_get_dev(mock_stream_t *s) { return s->b.device_id; }
static uint32_t mk_get_mask(mock_stream_t *s) { return s->granted_mask; }
static const char *mk_text(int32_t r) { (void)r; return "mock error"; }
static size_t frame_bytes_of(const mock_stream_t *s)
{
return (size_t)s->granted_channels * (s->b.format == 2 ? 4 : 2);
/* format 5, the IEC 61937 carrier, is 2 x 16 bits */
}
/* One callback of the current stream, if started. The device thread
* passes its pump flag through, read under the same lock. */
static int32_t dev_pump_locked(bool from_thread)
{
uint8_t buf[192 * 8 * 4];
int32_t n = 0;
int32_t want;
slock_lock(dev_lock);
want = mock_cb_frames ? mock_cb_frames : mock_burst;
if ( (!from_thread || dev_pump)
&& dev_stream && dev_stream->state == ST_STARTED && dev_stream->b.data_cb)
{
size_t fb = frame_bytes_of(dev_stream);
size_t len = (size_t)want * fb;
if (len > sizeof(buf))
len = sizeof(buf);
n = (int32_t)(len / fb);
dev_stream->b.data_cb(dev_stream, dev_stream->b.data_user, buf, n);
if (dev_captured + len <= sizeof(dev_capture))
{
memcpy(dev_capture + dev_captured, buf, len);
dev_captured += len;
}
}
slock_unlock(dev_lock);
return n;
}
static int32_t dev_pump_once(void) { return dev_pump_locked(false); }
static void dev_set_cb_frames(int32_t frames)
{
slock_lock(dev_lock);
mock_cb_frames = frames;
slock_unlock(dev_lock);
}
static void dev_set_pump(bool on)
{
slock_lock(dev_lock);
dev_pump = on;
slock_unlock(dev_lock);
}
static size_t dev_get_captured(void)
{
size_t n;
slock_lock(dev_lock);
n = dev_captured;
slock_unlock(dev_lock);
return n;
}
static void dev_thread_fn(void *u)
{
(void)u;
while (retro_atomic_load_acquire_int(&dev_run))
{
dev_pump_locked(true);
retro_sleep(1);
}
}
/* A disconnect, on a thread of its own as AAudio raises it. */
static void dev_disconnect(const mock_builder_t *b)
{
b->err_cb(NULL, b->err_user, MOCK_ERR_DISCONNECTED);
}
/* ---- dylib, as the driver sees it ---------------------------------- */
#define SYM(n, f) { n, (function_t)f, 0 }
static const struct { const char *name; function_t fn; int api; } syms[] = {
SYM("AAudio_createStreamBuilder", mk_create),
SYM("AAudioStreamBuilder_delete", mk_delete),
SYM("AAudioStreamBuilder_openStream", mk_open),
SYM("AAudioStreamBuilder_setDeviceId", mk_dev),
SYM("AAudioStreamBuilder_setSampleRate", mk_rate),
SYM("AAudioStreamBuilder_setChannelCount", mk_ch),
SYM("AAudioStreamBuilder_setFormat", mk_fmt),
SYM("AAudioStreamBuilder_setSharingMode", mk_share),
SYM("AAudioStreamBuilder_setDirection", mk_dir),
SYM("AAudioStreamBuilder_setPerformanceMode", mk_perf),
SYM("AAudioStreamBuilder_setDataCallback", mk_data),
SYM("AAudioStreamBuilder_setErrorCallback", mk_err),
SYM("AAudioStream_close", mk_close),
SYM("AAudioStream_requestStart", mk_start),
SYM("AAudioStream_requestPause", mk_pause),
SYM("AAudioStream_requestStop", mk_stop),
SYM("AAudioStream_waitForStateChange", mk_wait),
SYM("AAudioStream_setBufferSizeInFrames", mk_set_bufsize),
SYM("AAudioStream_getBufferSizeInFrames", mk_get_bufsize),
SYM("AAudioStream_getBufferCapacityInFrames", mk_get_cap),
SYM("AAudioStream_getFramesPerBurst", mk_get_burst),
SYM("AAudioStream_getSampleRate", mk_get_rate),
SYM("AAudioStream_getChannelCount", mk_get_ch),
SYM("AAudioStream_getFormat", mk_get_fmt),
SYM("AAudioStream_getSharingMode", mk_get_share),
SYM("AAudioStream_getPerformanceMode", mk_get_perf),
SYM("AAudioStream_getState", mk_get_state),
SYM("AAudioStream_getDeviceId", mk_get_dev),
SYM("AAudio_convertResultToText", mk_text),
{ "AAudioStreamBuilder_setUsage", (function_t)mk_usage, 28 },
{ "AAudioStreamBuilder_setContentType", (function_t)mk_ctype, 28 },
{ "AAudioStreamBuilder_setChannelMask", (function_t)mk_mask, 32 },
{ "AAudioStream_getChannelMask", (function_t)mk_get_mask, 32 },
{ NULL, NULL, 0 }
};
static int mock_lib_handle;
dylib_t dylib_load(const char *path)
{
if (mock_lib_missing || strcmp(path, "libaaudio.so"))
return NULL;
return &mock_lib_handle;
}
function_t dylib_proc(dylib_t lib, const char *name)
{
int i;
if (lib != &mock_lib_handle)
return NULL;
for (i = 0; syms[i].name; i++)
if (!strcmp(syms[i].name, name))
return syms[i].api > mock_api_level ? NULL : syms[i].fn;
return NULL;
}
void dylib_close(dylib_t lib) { (void)lib; }
char *dylib_error(void) { return NULL; }
/* ---- cases ---------------------------------------------------------- */
static void *open_driver(const char *device, unsigned latency, unsigned *rate)
{
*rate = 0;
return audio_aaudio.init(device, 44100, latency, rate);
}
static void case_open_defaults(void)
{
unsigned rate;
void *h;
size_t fb, ring;
printf(" open: float, native rate, low latency, two-burst device stage\n");
h = open_driver(NULL, 64, &rate);
CHECK(h != NULL, "init failed");
if (!h)
return;
CHECK(mock_last.rate == 0, "a rate was forced (%d)", (int)mock_last.rate);
CHECK(rate == 48000, "new_rate %u, not the device's 48000", rate);
CHECK(mock_last.format == 2 && audio_aaudio.use_float(h), "not opened as float");
CHECK(mock_last.perf == 12, "not low latency");
CHECK(mock_last.sharing == 1, "not shared");
CHECK(mock_last.direction == 0, "not output");
CHECK(mock_last.usage == 14, "usage %d, not GAME", (int)mock_last.usage);
CHECK(stub_device_latency == (size_t)mock_burst * 2,
"device stage %u frames, not two bursts", (unsigned)stub_device_latency);
fb = 8;
ring = audio_aaudio.buffer_size(h);
CHECK(ring % fb == 0, "buffer_size %u is not whole frames", (unsigned)ring);
/* 64 ms at 48 kHz, less what the device stage holds. */
CHECK(ring / fb == 3072 - 384, "ring %u frames, not %u", (unsigned)(ring / fb), 3072u - 384u);
CHECK(audio_aaudio.layout(h) == AUDIO_LAYOUT_STEREO, "layout 0x%x", audio_aaudio.layout(h));
CHECK(audio_aaudio.alive(h), "not alive after init");
/* Starts at the rate control's setpoint. */
CHECK(audio_aaudio.write_avail(h) == ring - (ring / fb / 2) * fb,
"write_avail %u at start, ring %u", (unsigned)audio_aaudio.write_avail(h), (unsigned)ring);
audio_aaudio.free(h);
printf(" open: a latency below the device stage keeps a two-burst ring\n");
h = open_driver(NULL, 2, &rate);
CHECK(h && audio_aaudio.buffer_size(h) == (size_t)mock_burst * 2 * 8,
"ring %u bytes", h ? (unsigned)audio_aaudio.buffer_size(h) : 0);
if (h)
audio_aaudio.free(h);
}
static void case_int16(void)
{
unsigned rate;
void *h;
printf(" open: 16-bit when the negotiation hint asks for it\n");
config_get_ptr()->uints.audio_format_negotiation = AUDIO_FORMAT_NEGOTIATION_INT16;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format == 1 && !audio_aaudio.use_float(h), "not opened as 16-bit");
CHECK(h && audio_aaudio.buffer_size(h) % 4 == 0, "ring not in 16-bit stereo frames");
if (h)
audio_aaudio.free(h);
config_get_ptr()->uints.audio_format_negotiation = AUDIO_FORMAT_NEGOTIATION_FLOAT;
}
static void case_layouts(void)
{
unsigned rate;
void *h;
uint32_t side51 = AUDIO_LAYOUT_5POINT1_SURROUND;
stub_requested_layout = side51;
printf(" layout: API 32 takes the mask as asked\n");
mock_api_level = 32;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.mask_set && mock_last.mask == side51, "mask not passed");
CHECK(h && audio_aaudio.layout(h) == side51, "layout 0x%x", h ? audio_aaudio.layout(h) : 0);
CHECK(h && audio_aaudio.buffer_size(h) % 24 == 0, "ring not in 6-channel float frames");
if (h)
audio_aaudio.free(h);
printf(" layout: a refused mask falls back to the count\n");
mock_refuse_mask = true;
h = open_driver(NULL, 64, &rate);
CHECK(h && !mock_last.mask_set && mock_last.channels == 6, "count path not taken");
CHECK(h && audio_aaudio.layout(h) == AUDIO_LAYOUT_5POINT1,
"layout 0x%x, not the count's canonical 5.1", h ? audio_aaudio.layout(h) : 0);
if (h)
audio_aaudio.free(h);
mock_refuse_mask = false;
/* The library is resolved once per process, so a lower API level is
* modelled by the mask being refused, as above; the count path and
* its canonical positions are the same code. */
printf(" layout: past what the frontend has, stereo\n");
stub_requested_layout = 0x1000u | AUDIO_LAYOUT_STEREO;
h = open_driver(NULL, 64, &rate);
CHECK(h && audio_aaudio.layout(h) == AUDIO_LAYOUT_STEREO, "not stereo");
CHECK(h && !mock_last.mask_set && mock_last.channels == 2, "not opened as stereo");
if (h)
audio_aaudio.free(h);
stub_requested_layout = AUDIO_LAYOUT_STEREO;
}
static void case_devices(void)
{
unsigned rate;
void *h;
printf(" device: the id at the front of the entry\n");
h = open_driver("7 (USB DAC, USB device)", 64, &rate);
CHECK(h && mock_last.device_id == 7, "device %d", (int)mock_last.device_id);
if (h)
audio_aaudio.free(h);
printf(" device: a refused device falls back to the default\n");
mock_refuse_device = 9;
mock_opens = 0;
h = open_driver("9 (Gone, Bluetooth A2DP)", 64, &rate);
CHECK(h != NULL, "no fallback");
CHECK(mock_last.device_id == 0 && mock_opens == 2,
"device %d after %d opens", (int)mock_last.device_id, mock_opens);
if (h)
audio_aaudio.free(h);
mock_refuse_device = 0;
printf(" device: a name that is not an id opens the default\n");
h = open_driver("default", 64, &rate);
CHECK(h && mock_last.device_id == 0, "device %d", (int)mock_last.device_id);
if (h)
audio_aaudio.free(h);
}
static void case_stream(void)
{
unsigned rate;
size_t i, ring, prefill, sent = 0, consumed;
float ramp[4096];
void *h;
ssize_t w;
int tries;
bool in_order = true;
printf(" stream: blocking writes reach the device in order\n");
h = open_driver(NULL, 32, &rate);
if (!h)
{
CHECK(0, "init failed");
return;
}
ring = audio_aaudio.buffer_size(h);
prefill = ring - audio_aaudio.write_avail(h);
for (i = 0; i < sizeof(ramp) / sizeof(ramp[0]); i++)
ramp[i] = (float)(i + 1);
slock_lock(dev_lock);
dev_captured = 0;
slock_unlock(dev_lock);
dev_set_pump(true);
audio_aaudio.set_nonblock_state(h, false);
for (i = 0; i < 8; i++)
{
w = audio_aaudio.write(h, ramp, sizeof(ramp));
CHECK(w == (ssize_t)sizeof(ramp), "write %u returned %ld", (unsigned)i, (long)w);
if (w > 0)
sent += (size_t)w;
}
/* Let the device drain what was written. */
for (tries = 0; tries < 2000 && dev_get_captured() < prefill + sent; tries++)
retro_sleep(1);
/* Off under the lock, so nothing pumps from here on. */
dev_set_pump(false);
CHECK(dev_captured >= prefill + sent, "device took %u of %u bytes",
(unsigned)dev_captured, (unsigned)(prefill + sent));
for (i = 0; i < prefill && i < dev_captured; i++)
if (dev_capture[i])
{
CHECK(0, "prefill byte %u is not silence", (unsigned)i);
break;
}
for (i = 0; prefill + (i + 1) * sizeof(float) <= dev_captured
&& i < sent / sizeof(float); i++)
{
float v;
memcpy(&v, dev_capture + prefill + i * sizeof(float), sizeof(v));
if (v != ramp[i % (sizeof(ramp) / sizeof(ramp[0]))])
{
in_order = false;
break;
}
}
CHECK(in_order, "sample %u out of order", (unsigned)i);
consumed = audio_aaudio.frames_consumed(h);
CHECK(consumed * 8 == dev_captured, "frames_consumed %u, device took %u frames",
(unsigned)consumed, (unsigned)(dev_captured / 8));
printf(" stream: an empty ring pads with silence and counts it\n");
{
size_t before = audio_aaudio.underruns(h);
size_t n = 0;
while (audio_aaudio.write_avail(h) < ring && n++ < 10000)
dev_pump_once();
dev_pump_once();
dev_pump_once();
CHECK(audio_aaudio.underruns(h) > before, "no underrun counted");
}
printf(" stream: nonblocking writes take what fits\n");
audio_aaudio.set_nonblock_state(h, true);
{
size_t room = audio_aaudio.write_avail(h);
size_t got = 0;
while ((w = audio_aaudio.write(h, ramp, sizeof(ramp))) > 0)
got += (size_t)w;
CHECK(w == 0, "a full ring returned %ld", (long)w);
CHECK(got == room, "took %u of %u bytes of room", (unsigned)got, (unsigned)room);
CHECK(audio_aaudio.write_avail(h) == 0, "room left after filling");
}
printf(" stream: a device that stops calling back ends the wait\n");
audio_aaudio.set_nonblock_state(h, false);
w = audio_aaudio.write(h, ramp, 64);
CHECK(w == 0, "a write to a stalled full ring returned %ld", (long)w);
CHECK(audio_aaudio.wait_writable(h, 64) == 0, "wait_writable on a stall");
printf(" stream: the lend pair, in whole frames\n");
{
void *region = NULL;
size_t span;
dev_pump_once();
dev_pump_once();
span = audio_aaudio.write_begin(h, 1003, &region);
CHECK(span > 0 && region, "nothing lent");
CHECK(span % 8 == 0 && span <= 1003, "span %u", (unsigned)span);
if (span)
{
memset(region, 0, span);
CHECK(audio_aaudio.write_end(h, span) == (ssize_t)span, "write_end");
}
}
printf(" stream: pause and resume\n");
CHECK(audio_aaudio.stop(h) && !audio_aaudio.alive(h), "not paused");
CHECK(dev_stream && dev_stream->pauses == 1, "requestPause not called");
CHECK(audio_aaudio.stop(h), "a second stop failed");
CHECK(dev_stream && dev_stream->pauses == 1, "paused twice");
CHECK(audio_aaudio.start(h, false) && audio_aaudio.alive(h), "not resumed");
CHECK(dev_stream && dev_stream->starts == 2, "requestStart not called");
mock_closes = 0;
audio_aaudio.free(h);
CHECK(mock_closes == 1, "%d closes at free", mock_closes);
}
static void case_disconnect(void)
{
unsigned rate;
mock_builder_t old;
float buf[512];
void *h;
retro_atomic_int_t *reinit = &audio_state_get_ptr()->reinit_request;
memset(buf, 0, sizeof(buf));
printf(" disconnect: the writer is freed and the frontend asked to reopen\n");
retro_atomic_store_release_int(reinit, 0);
h = open_driver(NULL, 32, &rate);
if (!h)
{
CHECK(0, "init failed");
return;
}
/* Full, and nothing draining it. */
audio_aaudio.set_nonblock_state(h, true);
while (audio_aaudio.write(h, buf, sizeof(buf)) > 0) ;
audio_aaudio.set_nonblock_state(h, false);
old = mock_last;
dev_disconnect(&mock_last);
CHECK(retro_atomic_load_acquire_int(reinit) == 1, "no reinit request");
CHECK(audio_aaudio.write(h, buf, sizeof(buf)) == (ssize_t)sizeof(buf),
"a write to a lost stream did not return at once");
CHECK(audio_aaudio.wait_writable(h, 64) == 0, "wait_writable on a lost stream");
CHECK(audio_aaudio.start(h, false), "start on a lost stream failed");
audio_aaudio.free(h);
printf(" disconnect: a late report from a closed stream is dropped\n");
retro_atomic_store_release_int(reinit, 0);
h = open_driver(NULL, 32, &rate);
if (!h)
{
CHECK(0, "init failed");
return;
}
dev_disconnect(&old);
CHECK(retro_atomic_load_acquire_int(reinit) == 0, "a stale report asked for a reinit");
CHECK(audio_aaudio.write(h, buf, 64) == 64, "a stale report broke the new stream");
audio_aaudio.free(h);
}
static void case_device_list(void)
{
struct string_list *list;
printf(" device list: the platform's outputs\n");
list = (struct string_list*)audio_aaudio.device_list_new(NULL);
CHECK(list && list->size == 2, "no list");
audio_aaudio.device_list_free(NULL, list);
}
/* The offset of the first burst of this type in what the device took,
* at 64-byte steps from start; -1 if none. */
static long find_burst(size_t start, size_t end, unsigned want)
{
size_t off;
for (off = start; off + 8 <= end; off += IEC61937_PAUSE_BURST_BYTES)
{
unsigned type = 0;
if (iec61937_probe(dev_capture + off, end - off, &type, NULL)
&& type == want)
return (long)off;
}
return -1;
}
/* Walks what the device took from the first AC-3 burst: counts the
* whole AC-3 bursts and says whether pause bursts follow the last.
* False on anything that is not a burst on a boundary. */
static bool walk_bursts(size_t captured, unsigned *bursts, bool *gap_after)
{
long first = find_burst(0, captured, IEC61937_AC3);
size_t off;
*bursts = 0;
*gap_after = false;
if (first < 0)
return true;
off = (size_t)first;
while (off + 8 <= captured)
{
unsigned t = 0;
if (!iec61937_probe(dev_capture + off, 8, &t, NULL))
return false;
if (t == IEC61937_AC3)
{
if (off + IEC61937_AC3_BURST_BYTES > captured)
break;
(*bursts)++;
off += IEC61937_AC3_BURST_BYTES;
*gap_after = false;
}
else if (t == IEC61937_PAUSE)
{
off += IEC61937_PAUSE_BURST_BYTES;
*gap_after = true;
}
else
return false;
}
return true;
}
static void case_ac3(void)
{
unsigned rate;
void *h;
size_t cap, fb, i, captured;
long first;
float pcm[1536 * 6];
ssize_t w;
int tries;
for (i = 0; i < sizeof(pcm) / sizeof(pcm[0]); i++)
pcm[i] = (float)((int)(i % 97) - 48) / 64.0f;
stub_requested_layout = AUDIO_LAYOUT_5POINT1_SURROUND;
stub_caps_id = MOCK_DEFAULT_DEVICE;
stub_caps_channels = 2;
stub_caps_ac3 = true;
mock_iec_ok = true;
printf(" ac3: 5.1 to a stereo-only output that decodes AC-3\n");
h = open_driver(NULL, 64, &rate);
if (!h)
{
CHECK(0, "init failed");
goto out;
}
CHECK(mock_last.format == 5 && mock_last.channels == 2 && mock_last.rate == 48000,
"not opened as IEC 61937 (format %d, %d ch, %d Hz)",
(int)mock_last.format, (int)mock_last.channels, (int)mock_last.rate);
CHECK(mock_last.device_id == MOCK_DEFAULT_DEVICE, "not on the output the PCM stream had");
CHECK(rate == 48000, "new_rate %u", rate);
CHECK(audio_aaudio.layout(h) == AUDIO_LAYOUT_5POINT1_SURROUND,
"layout 0x%x", audio_aaudio.layout(h));
CHECK(audio_aaudio.use_float(h), "the frontend does not write float");
fb = 6 * sizeof(float);
cap = audio_aaudio.buffer_size(h);
CHECK(cap % fb == 0 && (cap / fb) % 1536 == 0 && cap / fb >= 4 * 1536,
"buffer_size %u is not whole bursts of 5.1 frames", (unsigned)cap);
CHECK(audio_aaudio.write_begin(h, 4096, (void**)&w) == 0, "lent a span on the AC-3 path");
printf(" ac3: the device starts on pause bursts, never zeros\n");
dev_set_cb_frames(101); /* cuts pause bursts across callbacks */
slock_lock(dev_lock);
dev_captured = 0;
slock_unlock(dev_lock);
dev_pump_once();
dev_pump_once();
CHECK(find_burst(0, dev_get_captured(), IEC61937_PAUSE) == 0, "no pause burst at the start");
for (i = 64; i + 8 <= dev_get_captured(); i += 64)
{
unsigned t = 0;
if (!iec61937_probe(dev_capture + i, 8, &t, NULL) || t != IEC61937_PAUSE)
{
CHECK(0, "pause bursts not back to back at byte %u", (unsigned)i);
break;
}
}
CHECK(audio_aaudio.underruns(h) == 0, "pause bursts before the first write counted as underruns");
printf(" ac3: written audio reaches the device as whole AC-3 bursts\n");
{
size_t before = audio_aaudio.write_avail(h);
w = audio_aaudio.write(h, pcm, 100 * fb);
CHECK(w == (ssize_t)(100 * fb), "a part burst returned %ld", (long)w);
CHECK(audio_aaudio.write_avail(h) == before - 100 * fb,
"write_avail does not count frames in the encoder");
}
dev_set_pump(true);
audio_aaudio.set_nonblock_state(h, false);
for (i = 0; i < 6; i++)
{
w = audio_aaudio.write(h, pcm, sizeof(pcm));
CHECK(w == (ssize_t)sizeof(pcm), "write %u returned %ld", (unsigned)i, (long)w);
}
/* Until the device has taken all six and run on into the gap. */
for (tries = 0; tries < 5000; tries++)
{
unsigned n;
bool gap;
dev_set_pump(false);
if (!walk_bursts(dev_get_captured(), &n, &gap) || (n == 6 && gap))
break;
dev_set_pump(true);
retro_sleep(1);
}
dev_set_pump(false);
captured = dev_get_captured();
first = find_burst(0, captured, IEC61937_AC3);
CHECK(first > 0 && first % 64 == 0, "first AC-3 burst at %ld", first);
/* Every burst whole, on a boundary; where the writer fell behind
* the device, pause bursts between them, never anything else. */
{
unsigned bursts = 0;
bool gap_after = false;
CHECK(walk_bursts(captured, &bursts, &gap_after),
"something other than whole bursts on a boundary");
CHECK(bursts == 6, "%u whole AC-3 bursts, not 6", bursts);
printf(" ac3: a gap after them is pause bursts, on the burst boundary\n");
CHECK(gap_after, "no pause burst where the audio ran out");
CHECK(audio_aaudio.underruns(h) > 0, "the gap was not counted");
}
CHECK(audio_aaudio.frames_consumed(h) * 4 == captured,
"frames_consumed %u, device took %u carrier frames",
(unsigned)audio_aaudio.frames_consumed(h), (unsigned)(captured / 4));
printf(" ac3: nonblocking, a burst is taken whole or not at all\n");
audio_aaudio.set_nonblock_state(h, true);
{
size_t got = 0;
int laps = 0;
while (laps++ < 64 && (w = audio_aaudio.write(h, pcm, sizeof(pcm))) > 0)
got += (size_t)w;
CHECK(laps < 64, "a full ring kept taking audio");
CHECK(audio_aaudio.write_avail(h) < 1536 * fb, "room for a burst left in a full ring");
CHECK(got % fb == 0, "took part of a frame");
}
audio_aaudio.free(h);
dev_set_cb_frames(0);
printf(" ac3: 7.1 goes as 5.1 with the pair at the sides\n");
stub_requested_layout = AUDIO_LAYOUT_7POINT1;
h = open_driver(NULL, 64, &rate);
CHECK(h && audio_aaudio.layout(h) == AUDIO_LAYOUT_5POINT1_SURROUND,
"layout 0x%x", h ? audio_aaudio.layout(h) : 0);
if (h)
audio_aaudio.free(h);
stub_requested_layout = AUDIO_LAYOUT_5POINT1_SURROUND;
printf(" ac3: quad's back pair goes to the sides, as A/52 has it\n");
stub_requested_layout = AUDIO_LAYOUT_QUAD;
h = open_driver(NULL, 64, &rate);
CHECK(h && audio_aaudio.layout(h)
== (AUDIO_LAYOUT_STEREO | AUDIO_SPEAKER_SIDE_LEFT | AUDIO_SPEAKER_SIDE_RIGHT),
"layout 0x%x", h ? audio_aaudio.layout(h) : 0);
if (h)
audio_aaudio.free(h);
stub_requested_layout = AUDIO_LAYOUT_5POINT1_SURROUND;
printf(" ac3: PCM where the output takes the channels\n");
stub_caps_channels = 8;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format == 2, "AC-3 to an 8-channel output");
if (h)
audio_aaudio.free(h);
stub_caps_channels = 0; /* lists none: any count */
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format == 2, "AC-3 to an output with any count");
if (h)
audio_aaudio.free(h);
stub_caps_channels = 2;
printf(" ac3: PCM where the output does not list AC-3\n");
stub_caps_ac3 = false;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format == 2 && audio_aaudio.layout(h) == AUDIO_LAYOUT_5POINT1_SURROUND,
"not 5.1 PCM");
if (h)
audio_aaudio.free(h);
stub_caps_ac3 = true;
printf(" ac3: PCM where IEC 61937 will not open (before Android 14)\n");
mock_iec_ok = false;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format != 5 && audio_aaudio.layout(h) == AUDIO_LAYOUT_5POINT1_SURROUND,
"no PCM fallback");
CHECK(h && audio_aaudio.write_begin(h, 64, (void**)&w) > 0, "the PCM fallback lends nothing");
if (h)
audio_aaudio.free(h);
printf(" ac3: stereo never goes as AC-3\n");
mock_iec_ok = true;
stub_requested_layout = AUDIO_LAYOUT_STEREO;
h = open_driver(NULL, 64, &rate);
CHECK(h && mock_last.format == 2, "stereo encoded");
if (h)
audio_aaudio.free(h);
out:
dev_set_cb_frames(0);
mock_iec_ok = false;
stub_caps_id = 0;
stub_requested_layout = AUDIO_LAYOUT_STEREO;
}
int main(int argc, char **argv)
{
unsigned rate;
if (argc > 1 && !strcmp(argv[1], "nolib"))
{
printf("aaudio_test nolib\n");
mock_lib_missing = true;
CHECK(audio_aaudio.init(NULL, 48000, 64, &rate) == NULL,
"init without libaaudio.so");
printf(failures ? "FAILED\n" : "PASS\n");
return failures ? 1 : 0;
}
printf("aaudio_test\n");
config_get_ptr()->uints.audio_format_negotiation = AUDIO_FORMAT_NEGOTIATION_FLOAT;
dev_lock = slock_new();
retro_atomic_int_init(&dev_run, 1);
dev_thread = sthread_create(dev_thread_fn, NULL);
if (!dev_lock || !dev_thread)
{
printf("could not start the device thread\n");
return 1;
}
case_open_defaults();
case_int16();
case_layouts();
case_devices();
case_stream();
case_disconnect();
case_device_list();
case_ac3();
retro_atomic_store_release_int(&dev_run, 0);
sthread_join(dev_thread);
slock_free(dev_lock);
printf(failures ? "FAILED (%u)\n" : "PASS\n", failures);
return failures ? 1 : 0;
}
+75
View File
@@ -0,0 +1,75 @@
/* The frontend entry points audio/drivers/aaudio.c reaches for. */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "../../../configuration.h"
#include "../../../audio/audio_driver.h"
size_t stub_device_latency;
uint32_t stub_requested_layout = AUDIO_LAYOUT_STEREO;
settings_t *config_get_ptr(void)
{
static settings_t settings;
return &settings;
}
audio_driver_state_t *audio_state_get_ptr(void)
{
static audio_driver_state_t state;
return &state;
}
uint32_t audio_driver_requested_layout(void) { return stub_requested_layout; }
unsigned audio_layout_channels(uint32_t layout)
{
unsigned n = 0;
for (; layout; layout &= layout - 1)
n++;
return n;
}
void audio_driver_set_device_latency(size_t frames) { stub_device_latency = frames; }
static void stub_log(const char *fmt, va_list ap) { printf(" "); vprintf(fmt, ap); }
void RARCH_LOG(const char *fmt, ...) { va_list ap; va_start(ap, fmt); stub_log(fmt, ap); va_end(ap); }
void RARCH_WARN(const char *fmt, ...) { va_list ap; va_start(ap, fmt); stub_log(fmt, ap); va_end(ap); }
void RARCH_ERR(const char *fmt, ...) { va_list ap; va_start(ap, fmt); stub_log(fmt, ap); va_end(ap); }
void RARCH_DBG(const char *fmt, ...) { (void)fmt; }
/* The platform's view of its outputs, scripted. */
#include <lists/string_list.h>
#include "../../../audio/common/android_audio.h"
int32_t stub_caps_id;
unsigned stub_caps_channels;
bool stub_caps_ac3;
struct string_list *frontend_android_audio_outputs_new(void)
{
union string_list_elem_attr attr;
struct string_list *list = string_list_new();
attr.i = 0;
if (list)
{
string_list_append(list, "3 (Pixel, speaker)", attr);
string_list_append(list, "7 (USB DAC, USB device)", attr);
}
return list;
}
bool frontend_android_audio_device_caps(int32_t id,
unsigned *max_channels, bool *ac3)
{
*max_channels = 0;
*ac3 = false;
if (!stub_caps_id || id != stub_caps_id)
return false;
*max_channels = stub_caps_channels;
*ac3 = stub_caps_ac3;
return true;
}
+2
View File
@@ -122,6 +122,8 @@ DEVICE_CALLBACK_ENTRIES = (
"coreaudio_mic_default_listener",
"coreaudio_mic_default_listener_old",
"opensl_callback",
"aaudio_data_cb",
"aaudio_error_cb",
"ctr_dsp_audio_frame_cb",
"gx_audio_dma_callback",
"wiiu_ax_callback",