From 57d9d4eb42be3d1b80e7895b79e7ac9e417f5e28 Mon Sep 17 00:00:00 2001 From: Jun Bo Bi Date: Sun, 27 Aug 2023 04:46:35 -0400 Subject: [PATCH] ao/aaudio: implement aaudio backend for android --- audio/chmap.h | 16 + audio/out/aaudio_functions26.inc | 50 +++ audio/out/aaudio_functions28.inc | 4 + audio/out/aaudio_functions32.inc | 1 + audio/out/ao.c | 4 + audio/out/ao_aaudio.c | 508 +++++++++++++++++++++++++++++++ meson.build | 7 + meson.options | 1 + 8 files changed, 591 insertions(+) create mode 100644 audio/out/aaudio_functions26.inc create mode 100644 audio/out/aaudio_functions28.inc create mode 100644 audio/out/aaudio_functions32.inc create mode 100644 audio/out/ao_aaudio.c diff --git a/audio/chmap.h b/audio/chmap.h index 58a3f71907..6f1f538da4 100644 --- a/audio/chmap.h +++ b/audio/chmap.h @@ -98,6 +98,22 @@ typedef const char * const (mp_ch_layout_tuple)[2]; {7, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g)}} #define MP_CHMAP8(a, b, c, d, e, f, g, h) \ {8, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h)}} +#define MP_CHMAP9(a, b, c, d, e, f, g, h, i) \ + {9, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i)}} +#define MP_CHMAP10(a, b, c, d, e, f, g, h, i, j) \ + {10, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j)}} +#define MP_CHMAP11(a, b, c, d, e, f, g, h, i, j, k) \ + {11, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k)}}, +#define MP_CHMAP12(a, b, c, d, e, f, g, h, i, j, k, l) \ + {12, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l)}} +#define MP_CHMAP13(a, b, c, d, e, f, g, h, i, j, k, l, m) \ + {13, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m)}} +#define MP_CHMAP14(a, b, c, d, e, f, g, h, i, j, k, l, m, n) \ + {14, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n)}} +#define MP_CHMAP15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \ + {15, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n), MP_SP(o)}} +#define MP_CHMAP16(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + {16, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n), MP_SP(o), MP_SP(p)}} #define MP_CHMAP_INIT_MONO {1, {MP_SPEAKER_ID_FC}} #define MP_CHMAP_INIT_STEREO MP_CHMAP2(FL, FR) diff --git a/audio/out/aaudio_functions26.inc b/audio/out/aaudio_functions26.inc new file mode 100644 index 0000000000..0452baf5f6 --- /dev/null +++ b/audio/out/aaudio_functions26.inc @@ -0,0 +1,50 @@ +/* Stream builder functions */ +AAUDIO_FUNCTION(AAudio_createStreamBuilder, aaudio_result_t, AAudioStreamBuilder **builder) +AAUDIO_FUNCTION(AAudioStreamBuilder_setDeviceId, void, AAudioStreamBuilder *builder, int32_t deviceId) +AAUDIO_FUNCTION(AAudioStreamBuilder_setSampleRate, void, AAudioStreamBuilder *builder, int32_t sampleRate) +AAUDIO_FUNCTION(AAudioStreamBuilder_setChannelCount, void, AAudioStreamBuilder *builder, int32_t channelCount) +AAUDIO_FUNCTION(AAudioStreamBuilder_setSamplesPerFrame, void, AAudioStreamBuilder *builder, int32_t samplesPerFrame) +AAUDIO_FUNCTION(AAudioStreamBuilder_setFormat, void, AAudioStreamBuilder *builder, aaudio_format_t format) +AAUDIO_FUNCTION(AAudioStreamBuilder_setSharingMode, void, AAudioStreamBuilder *builder, aaudio_sharing_mode_t sharingMode) +AAUDIO_FUNCTION(AAudioStreamBuilder_setDirection, void, AAudioStreamBuilder *builder, aaudio_direction_t direction) +AAUDIO_FUNCTION(AAudioStreamBuilder_setBufferCapacityInFrames, void, AAudioStreamBuilder *builder, int32_t numFrames) +AAUDIO_FUNCTION(AAudioStreamBuilder_setPerformanceMode, void, AAudioStreamBuilder *builder, aaudio_performance_mode_t mode) +AAUDIO_FUNCTION(AAudioStreamBuilder_setDataCallback, void, AAudioStreamBuilder *builder, AAudioStream_dataCallback callback, void *userData) +AAUDIO_FUNCTION(AAudioStreamBuilder_setFramesPerDataCallback, void, AAudioStreamBuilder *builder, int32_t numFrames) +AAUDIO_FUNCTION(AAudioStreamBuilder_setErrorCallback, void, AAudioStreamBuilder *builder, AAudioStream_errorCallback callback, void *userData) +AAUDIO_FUNCTION(AAudioStreamBuilder_openStream, aaudio_result_t, AAudioStreamBuilder *builder, AAudioStream **stream) +AAUDIO_FUNCTION(AAudioStreamBuilder_delete, aaudio_result_t, AAudioStreamBuilder *builder) + +/* Stream control functions */ +AAUDIO_FUNCTION(AAudioStream_close, aaudio_result_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_requestStart, aaudio_result_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_requestPause, aaudio_result_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_requestFlush, aaudio_result_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_requestStop, aaudio_result_t, AAudioStream *stream) + +/* Stream query functions */ +AAUDIO_FUNCTION(AAudioStream_getState, aaudio_stream_state_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_waitForStateChange, aaudio_result_t, AAudioStream *stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds) +AAUDIO_FUNCTION(AAudioStream_read, aaudio_result_t, AAudioStream *stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) +AAUDIO_FUNCTION(AAudioStream_write, aaudio_result_t, AAudioStream *stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) +AAUDIO_FUNCTION(AAudioStream_setBufferSizeInFrames, aaudio_result_t, AAudioStream *stream, int32_t numFrames) +AAUDIO_FUNCTION(AAudioStream_getBufferSizeInFrames, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getFramesPerBurst, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getBufferCapacityInFrames, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getFramesPerDataCallback, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getXRunCount, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getSampleRate, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getChannelCount, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getSamplesPerFrame, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getDeviceId, int32_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getFormat, aaudio_format_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getSharingMode, aaudio_sharing_mode_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getPerformanceMode, aaudio_performance_mode_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getDirection, aaudio_direction_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getFramesWritten, int64_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getFramesRead, int64_t, AAudioStream *stream) +AAUDIO_FUNCTION(AAudioStream_getTimestamp, aaudio_result_t, AAudioStream *stream, clockid_t clockid, int64_t* framePosition, int64_t* timeNanoseconds) + +/* Utility functions */ +AAUDIO_FUNCTION(AAudio_convertResultToText, const char *, aaudio_result_t returnCode) +AAUDIO_FUNCTION(AAudio_convertStreamStateToText, const char*, aaudio_stream_state_t state) diff --git a/audio/out/aaudio_functions28.inc b/audio/out/aaudio_functions28.inc new file mode 100644 index 0000000000..0bfdb6f670 --- /dev/null +++ b/audio/out/aaudio_functions28.inc @@ -0,0 +1,4 @@ +/* Stream builder functions - API 28 */ +AAUDIO_FUNCTION(AAudioStreamBuilder_setUsage, void, AAudioStreamBuilder *builder, aaudio_usage_t usage) +AAUDIO_FUNCTION(AAudioStreamBuilder_setContentType, void, AAudioStreamBuilder *builder, aaudio_content_type_t contentType) +AAUDIO_FUNCTION(AAudioStreamBuilder_setSessionId, void, AAudioStreamBuilder* builder, aaudio_session_id_t sessionId) diff --git a/audio/out/aaudio_functions32.inc b/audio/out/aaudio_functions32.inc new file mode 100644 index 0000000000..46dd9c77b2 --- /dev/null +++ b/audio/out/aaudio_functions32.inc @@ -0,0 +1 @@ +AAUDIO_FUNCTION(AAudioStreamBuilder_setChannelMask, void, AAudioStreamBuilder *builder, aaudio_channel_mask_t channelMask) diff --git a/audio/out/ao.c b/audio/out/ao.c index 97fd9e7461..a35f0cfcf4 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -54,12 +54,16 @@ extern const struct ao_driver audio_out_wasapi; extern const struct ao_driver audio_out_pcm; extern const struct ao_driver audio_out_lavc; extern const struct ao_driver audio_out_sdl; +extern const struct ao_driver audio_out_aaudio; static const struct ao_driver * const audio_out_drivers[] = { // native: #if HAVE_AUDIOTRACK &audio_out_audiotrack, #endif +#if HAVE_AAUDIO + &audio_out_aaudio, +#endif #if HAVE_AUDIOUNIT &audio_out_audiounit, #endif diff --git a/audio/out/ao_aaudio.c b/audio/out/ao_aaudio.c new file mode 100644 index 0000000000..c506719d04 --- /dev/null +++ b/audio/out/ao_aaudio.c @@ -0,0 +1,508 @@ +/* + * AAudio audio output driver + * + * Copyright (C) 2024 Jun Bo Bi + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include +#include +#include + +#include +#include + +#include "ao.h" +#include "audio/format.h" +#include "common/common.h" +#include "common/msg.h" +#include "internal.h" +#include "options/m_option.h" +#include "osdep/timer.h" + +struct priv { + AAudioStream *stream; + + int32_t device_id; + aaudio_session_id_t session_id; + int32_t buffer_capacity; + aaudio_performance_mode_t performance_mode; + + int64_t presented; + int64_t discarded; + + int device_api; + void *lib_handle; + +#define AAUDIO_FUNCTION(name, ret, ...) ret (*name)(__VA_ARGS__); +#include "aaudio_functions26.inc" +#include "aaudio_functions28.inc" +#include "aaudio_functions32.inc" +#undef AAUDIO_FUNCTION +}; + +struct function_map { + const char *symbol; + int offset; +}; + +#define AAUDIO_FUNCTION(name, ret, ...) {#name, offsetof(struct priv, name)}, +static const struct function_map lib_functions26[] = { +#include "aaudio_functions26.inc" +}; + +static const struct function_map lib_functions28[] = { +#include "aaudio_functions28.inc" +}; + +static const struct function_map lib_functions32[] = { +#include "aaudio_functions32.inc" +}; +#undef AAUDIO_FUNCTION + +static const struct { + int api_level; + int length; + const struct function_map *functions; +} lib_functions[] = { + {26, MP_ARRAY_SIZE(lib_functions26), lib_functions26}, + {28, MP_ARRAY_SIZE(lib_functions28), lib_functions28}, + {32, MP_ARRAY_SIZE(lib_functions32), lib_functions32} +}; + +/* + * There is no documentation in AAudio for the order of positions for AAudio. + * It's assumed to work the same way as AudioTrack (even the order of the bits + * for the position mask is the same for both) + * See https://developer.android.com/reference/android/media/AudioFormat#channelPositionMask + */ +static const struct mp_chmap aaudio_default_chmaps[] = { + {0}, /* empty */ + MP_CHMAP_INIT_MONO, /* mono */ + MP_CHMAP_INIT_STEREO, /* stereo */ + MP_CHMAP3(FL, FR, FC), /* 3.0 */ + MP_CHMAP4(FL, FR, BL, BR), /* quad */ + MP_CHMAP5(FL, FR, FC, BL, BR), /* 5.0 */ + MP_CHMAP6(FL, FR, FC, LFE, BL, BR), /* 5.1 */ + MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), /* 6.1 */ + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), /* 7.1 */ + {0}, + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, SL, SR, TSL, TSR), /* 7.1.2 */ + {0}, + MP_CHMAP12(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR), /* 7.1.4 */ + {0}, + MP_CHMAP14(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, WL, WR), /* 9.1.4 */ + {0}, + MP_CHMAP16(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, TSL, TSR, WL, WR) /* 9.1.6 */ +}; + +static const struct mp_chmap aaudio_chmaps[] = { + {0}, /* empty */ + /* + * This should be `{1, {MP_SP(FL)}}` according to spec + * but `mp_chmap_sel` doesn't like it + */ + MP_CHMAP_INIT_MONO, /* mono */ + MP_CHMAP_INIT_STEREO, /* stereo */ + MP_CHMAP3(FL, FR, LFE), /* 2.1 */ + MP_CHMAP3(FL, FR, FC), /* 3.0 */ + MP_CHMAP3(FL, FR, BC), /* 3.0 (back) */ + MP_CHMAP4(FL, FR, FC, LFE), /* 3.1 */ + MP_CHMAP4(FL, FR, TSL, TSR), /* 2.0.2 */ + MP_CHMAP5(FL, FR, LFE, TSL, TSR), /* 2.1.2 */ + MP_CHMAP5(FL, FR, FC, TSL, TSR), /* 3.0.2 */ + MP_CHMAP6(FL, FR, FC, LFE, TSL, TSR), /* 3.1.2 */ + MP_CHMAP4(FL, FR, BL, BR), /* quad */ + MP_CHMAP4(FL, FR, SL, SR), /* quad (side) */ + MP_CHMAP4(FL, FR, FC, BC), /* quad (center) */ + MP_CHMAP5(FL, FR, FC, BL, BR), /* 5.0 */ + MP_CHMAP6(FL, FR, FC, LFE, BL, BR), /* 5.1 */ + MP_CHMAP6(FL, FR, FC, LFE, SL, SR), /* 5.1 (side) */ + MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), /* 6.1 */ + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), /* 7.1 */ + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, TSL, TSR), /* 5.1.2 */ + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, TFL, TFR, TBL, TBR), /* 5.1.4 */ + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, SL, SR, TSL, TSR), /* 7.1.2 */ + MP_CHMAP12(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR), /* 7.1.4 */ + MP_CHMAP14(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, WL, WR), /* 9.1.4 */ + MP_CHMAP16(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, TSL, TSR, WL, WR) /* 9.1.6 */ +}; + +static const aaudio_channel_mask_t aaudio_masks[] = { + AAUDIO_CHANNEL_INVALID, + AAUDIO_CHANNEL_MONO, + AAUDIO_CHANNEL_STEREO, + AAUDIO_CHANNEL_2POINT1, + AAUDIO_CHANNEL_TRI, + AAUDIO_CHANNEL_TRI_BACK, + AAUDIO_CHANNEL_3POINT1, + AAUDIO_CHANNEL_2POINT0POINT2, + AAUDIO_CHANNEL_2POINT1POINT2, + AAUDIO_CHANNEL_3POINT0POINT2, + AAUDIO_CHANNEL_3POINT1POINT2, + AAUDIO_CHANNEL_QUAD, + AAUDIO_CHANNEL_QUAD_SIDE, + AAUDIO_CHANNEL_SURROUND, + AAUDIO_CHANNEL_PENTA, + AAUDIO_CHANNEL_5POINT1, + AAUDIO_CHANNEL_5POINT1_SIDE, + AAUDIO_CHANNEL_6POINT1, + AAUDIO_CHANNEL_7POINT1, + AAUDIO_CHANNEL_5POINT1POINT2, + AAUDIO_CHANNEL_5POINT1POINT4, + AAUDIO_CHANNEL_7POINT1POINT2, + AAUDIO_CHANNEL_7POINT1POINT4, + AAUDIO_CHANNEL_9POINT1POINT4, + AAUDIO_CHANNEL_9POINT1POINT6 +}; + +static_assert(MP_ARRAY_SIZE(aaudio_chmaps) == MP_ARRAY_SIZE(aaudio_masks), + "`aaudio_masks` and `aaudio_chmaps` MUST have the same number of entries."); + +/* + * The values corresponds to indexes of `aaudio_chmaps` + * + * Different devices may support different number of max channels + * depending on what they set `FCC_LIMIT` to + * See https://cs.android.com/android/platform/superproject/+/android-latest-release:system/media/audio/include/system/audio.h;l=234 + */ +static const uint8_t aaudio_max_chnums[] = { + MP_ARRAY_SIZE(aaudio_chmaps), + 22, /* FCC_12 */ + 19, /* FCC_8 */ + 2, /* FCC_2 */ + 1 /* FCC_1 */ +}; + +static bool load_lib_functions(struct ao *ao) +{ + struct priv *p = ao->priv; + + p->device_api = android_get_device_api_level(); + p->lib_handle = dlopen("libaaudio.so", RTLD_NOW | RTLD_GLOBAL); + if (!p->lib_handle) + return false; + + for (int i = 0; i < MP_ARRAY_SIZE(lib_functions); i++) { + if (p->device_api < lib_functions[i].api_level) + break; + + for (int j = 0; j < lib_functions[i].length; j++) { + const char *sym = lib_functions[i].functions[j].symbol; + void *fun = dlsym(p->lib_handle, sym); + if (!fun) + fun = dlsym(RTLD_DEFAULT, sym); + if (!fun) { + MP_WARN(ao, "Could not resolve symbol %s\n", sym); + return false; + } + *(void **)((uint8_t *)p + lib_functions[i].functions[j].offset) = fun; + } + } + return true; +} + +static void error_callback(AAudioStream *stream, void *context, aaudio_result_t error) +{ + struct ao *ao = context; + struct priv *p = ao->priv; + + MP_ERR(ao, "%s, trying to reload...\n", p->AAudio_convertResultToText(error)); + ao_request_reload(ao); +} + +static aaudio_data_callback_result_t data_callback(AAudioStream *stream, void *context, + void *data, int32_t nframes) +{ + struct ao *ao = context; + struct priv *p = ao->priv; + + aaudio_result_t result; + int64_t presented, present_time; + int64_t written = p->AAudioStream_getFramesWritten(stream); + + if ((result = p->AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &presented, &present_time)) < 0) { + MP_TRACE(ao, "AAudioStream_getTimestamp() returned %s\n", + p->AAudio_convertResultToText(result)); + presented = p->presented; + } else { + p->presented = presented; + } + + int64_t end_time = mp_time_ns(); + end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate; + end_time += MP_TIME_S_TO_NS(written - (presented + p->discarded)) / ao->samplerate; + + ao_read_data(ao, &data, nframes, end_time, NULL, true, true); + + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +static void uninit(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (p->stream) { + p->AAudioStream_close(p->stream); + p->stream = NULL; + } + + if (p->lib_handle) { + dlclose(p->lib_handle); + p->lib_handle = NULL; + } +} + +static int init(struct ao *ao) +{ + if (!load_lib_functions(ao)) + return -1; + + struct priv *p = ao->priv; + + aaudio_result_t result; + AAudioStreamBuilder *builder; + + if ((result = p->AAudio_createStreamBuilder(&builder)) < 0) { + MP_ERR(ao, "AAudio_createStreamBuilder() returned %s\n", + p->AAudio_convertResultToText(result)); + return -1; + } + + aaudio_format_t format; + + if (p->device_api >= 34 && af_fmt_is_spdif(ao->format)) { + format = AAUDIO_FORMAT_IEC61937; + } else if (af_fmt_is_float(ao->format)) { + ao->format = AF_FORMAT_FLOAT; + format = AAUDIO_FORMAT_PCM_FLOAT; + } else if (af_fmt_is_int(ao->format)) { + if (af_fmt_to_bytes(ao->format) > 2 && p->device_api >= 31) { + ao->format = AF_FORMAT_S32; + format = AAUDIO_FORMAT_PCM_I32; + } else { + ao->format = AF_FORMAT_S16; + format = AAUDIO_FORMAT_PCM_I16; + } + } else { + ao->format = AF_FORMAT_S16; + format = AAUDIO_FORMAT_PCM_I16; + } + + p->AAudioStreamBuilder_setDeviceId(builder, p->device_id); + p->AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); + p->AAudioStreamBuilder_setSharingMode(builder, + (ao->init_flags & AO_INIT_EXCLUSIVE) + ? AAUDIO_SHARING_MODE_EXCLUSIVE + : AAUDIO_SHARING_MODE_SHARED); + p->AAudioStreamBuilder_setFormat(builder, format); + p->AAudioStreamBuilder_setSampleRate(builder, ao->samplerate); + p->AAudioStreamBuilder_setErrorCallback(builder, error_callback, ao); + p->AAudioStreamBuilder_setBufferCapacityInFrames(builder, + p->buffer_capacity); + p->AAudioStreamBuilder_setPerformanceMode(builder, p->performance_mode); + p->AAudioStreamBuilder_setDataCallback(builder, data_callback, ao); + + if (p->device_api >= 28) { + p->AAudioStreamBuilder_setContentType(builder, + (ao->init_flags & + AO_INIT_MEDIA_ROLE_MUSIC) + ? AAUDIO_CONTENT_TYPE_MUSIC + : AAUDIO_CONTENT_TYPE_MOVIE); + p->AAudioStreamBuilder_setUsage(builder, AAUDIO_USAGE_MEDIA); + p->AAudioStreamBuilder_setSessionId(builder, p->session_id); + } + + if (p->device_api >= 32) { + uint8_t i = 0; + struct mp_chmap channels = ao->channels; + + do { + struct mp_chmap_sel sel = {0, .tmp = p}; + aaudio_channel_mask_t channel_mask = AAUDIO_CHANNEL_INVALID; + + for (int j = 1; j < aaudio_max_chnums[i]; j++) + mp_chmap_sel_add_map(&sel, &aaudio_chmaps[j]); + + if (!ao_chmap_sel_adjust(ao, &sel, &channels)) { + MP_ERR(ao, "Failed to find channel map\n"); + goto err; + } + + for (uint8_t j = 0; j < aaudio_max_chnums[i]; j++) { + if (mp_chmap_equals(&aaudio_chmaps[j], &channels)) { + channel_mask = aaudio_masks[j]; + break; + } + } + + assert(channel_mask != AAUDIO_CHANNEL_INVALID); + p->AAudioStreamBuilder_setChannelMask(builder, channel_mask); + + result = p->AAudioStreamBuilder_openStream(builder, &p->stream); + + if (result != AAUDIO_ERROR_OUT_OF_RANGE) { + break; + } + } while (i++ < MP_ARRAY_SIZE(aaudio_max_chnums)); + + ao->channels = channels; + } else { + result = p->AAudioStreamBuilder_openStream(builder, &p->stream); + } + + if (result < 0) { + MP_ERR(ao, "AAudioStreamBuilder_openStream() returned %s\n", + p->AAudio_convertResultToText(result)); + goto err; + } + + if (p->device_api < 32) { + int32_t channel_count = p->AAudioStream_getChannelCount(p->stream); + + if (channel_count >= MP_ARRAY_SIZE(aaudio_default_chmaps) || + (ao->channels = aaudio_default_chmaps[channel_count]).num == 0) { + MP_ERR(ao, "Unknown layout for channel count: %" PRId32 "\n", + channel_count); + goto err; + } + } + + ao->device_buffer = p->AAudioStream_getBufferCapacityInFrames(p->stream); + + p->AAudioStreamBuilder_delete(builder); + return 1; +err: + p->AAudioStreamBuilder_delete(builder); + return -1; +} + +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + + aaudio_result_t result; + p->discarded = p->AAudioStream_getFramesWritten(p->stream) - p->presented; + + if ((result = p->AAudioStream_requestStart(p->stream)) < 0) { + MP_ERR(ao, "AAudioStream_requestStart() returned %s\n", + p->AAudio_convertResultToText(result)); + return ao_request_reload(ao); + } +} + +static bool set_pause(struct ao *ao, bool paused) +{ + struct priv *p = ao->priv; + + aaudio_result_t result = paused + ? p->AAudioStream_requestPause(p->stream) + : p->AAudioStream_requestStart(p->stream); + + if (result < 0) { + MP_ERR(ao, "AAudioStream_request%s() returned %s\n", + paused ? "Pause" : "Start", + p->AAudio_convertResultToText(result)); + ao_request_reload(ao); + return false; + } + + return true; +} + +static void reset(struct ao *ao) +{ + struct priv *p = ao->priv; + + aaudio_result_t result; + aaudio_stream_state_t state = p->AAudioStream_getState(p->stream); + + switch (state) { + case AAUDIO_STREAM_STATE_STARTING: + case AAUDIO_STREAM_STATE_STOPPING: + case AAUDIO_STREAM_STATE_PAUSING: + case AAUDIO_STREAM_STATE_FLUSHING: + if ((result = p->AAudioStream_waitForStateChange(p->stream, + state, &state, INT64_MAX)) < 0) { + MP_ERR(ao, "AAudioStream_waitForStateChange() returned %s\n", + p->AAudio_convertResultToText(result)); + return ao_request_reload(ao); + } + } + + if (state != AAUDIO_STREAM_STATE_PAUSED) { + if ((result = p->AAudioStream_requestPause(p->stream)) < 0) { + MP_ERR(ao, "AAudioStream_requestPause() %s\n", + p->AAudio_convertResultToText(result)); + return ao_request_reload(ao); + } + + if ((result = p->AAudioStream_waitForStateChange(p->stream, + AAUDIO_STREAM_STATE_PAUSING, &state, INT64_MAX)) < 0) { + MP_ERR(ao, "AAudioStream_waitForStateChange() returned %s\n", + p->AAudio_convertResultToText(result)); + return ao_request_reload(ao); + } + } + + if ((result = p->AAudioStream_requestFlush(p->stream)) < 0) { + MP_ERR(ao, "AAudioStream_requestFlush() returned %s\n", + p->AAudio_convertResultToText(result)); + return ao_request_reload(ao); + } +} + +#define OPT_BASE_STRUCT struct priv + +const struct ao_driver audio_out_aaudio = { + .description = "AAudio audio output", + .name = "aaudio", + .init = init, + .uninit = uninit, + .start = start, + .reset = reset, + .set_pause = set_pause, + + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .device_id = AAUDIO_UNSPECIFIED, + .session_id = AAUDIO_SESSION_ID_NONE, + .buffer_capacity = AAUDIO_UNSPECIFIED, + .performance_mode = AAUDIO_PERFORMANCE_MODE_NONE, + .presented = 0, + .discarded = 0, + .stream = NULL, + .lib_handle = NULL + }, + .options_prefix = "aaudio", + .options = + (const struct m_option[]){ + {"device-id", OPT_CHOICE(device_id, + {"auto", AAUDIO_UNSPECIFIED}), + M_RANGE(INT32_MIN, INT32_MAX)}, + {"session-id", OPT_CHOICE(session_id, + {"none", AAUDIO_SESSION_ID_NONE}), + M_RANGE(INT32_MIN, INT32_MAX)}, + {"buffer-capacity", OPT_CHOICE(buffer_capacity, + {"auto", AAUDIO_UNSPECIFIED}), + M_RANGE(1, INT32_MAX)}, + {"performance-mode", OPT_CHOICE(performance_mode, + {"none", AAUDIO_PERFORMANCE_MODE_NONE}, + {"low-latency", AAUDIO_PERFORMANCE_MODE_LOW_LATENCY}, + {"power-saving", AAUDIO_PERFORMANCE_MODE_POWER_SAVING})}, + {0}}, +}; diff --git a/meson.build b/meson.build index 75609575f8..e37564b034 100644 --- a/meson.build +++ b/meson.build @@ -866,6 +866,13 @@ if features['audiotrack'] sources += files('audio/out/ao_audiotrack.c') endif +aaudio_opt = get_option('aaudio').require(features['android']) +features += {'aaudio': cc.has_header_symbol('aaudio/AAudio.h', + 'AAudioStream', required: aaudio_opt)} +if features['aaudio'] + sources += files('audio/out/ao_aaudio.c') +endif + opensles = cc.find_library('OpenSLES', required: get_option('opensles')) features += {'opensles': opensles.found()} if features['opensles'] diff --git a/meson.options b/meson.options index e501d98fd3..52782f73d6 100644 --- a/meson.options +++ b/meson.options @@ -46,6 +46,7 @@ option('avfoundation', type: 'feature', value: 'auto', description: 'AVFoundatio option('jack', type: 'feature', value: 'auto', description: 'JACK audio output') option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output') option('audiotrack', type: 'feature', value: 'auto', description: 'Android AudioTrack audio output') +option('aaudio', type: 'feature', value: 'auto', description: 'Android AAudio audio output') option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') option('oss-audio', type: 'feature', value: 'auto', description: 'OSSv4 audio output') option('pipewire', type: 'feature', value: 'auto', description: 'PipeWire audio output')