Skip to content

Commit

Permalink
ao/aaudio: implement aaudio backend for android
Browse files Browse the repository at this point in the history
  • Loading branch information
jambonmcyeah committed Mar 3, 2025
1 parent 3609a44 commit 92a6460
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 0 deletions.
50 changes: 50 additions & 0 deletions audio/out/aaudio_functions26.inc
Original file line number Diff line number Diff line change
@@ -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, int64_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)
3 changes: 3 additions & 0 deletions audio/out/aaudio_functions28.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* 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)
4 changes: 4 additions & 0 deletions audio/out/ao.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
303 changes: 303 additions & 0 deletions audio/out/ao_aaudio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
/*
* AAudio audio output driver
*
* Copyright (C) 2024 Jun Bo Bi <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <dlfcn.h>

#include <android/api-level.h>
#include <aaudio/AAudio.h>

#include "ao.h"
#include "audio/format.h"
#include "osdep/timer.h"
#include "common/msg.h"
#include "internal.h"
#include "options/m_option.h"

struct priv {
AAudioStreamBuilder *builder;
AAudioStream *stream;

int32_t device_id;
int32_t buffer_capacity;
aaudio_performance_mode_t performance_mode;

int device_api;
void *lib_handle;

#define AAUDIO_FUNCTION(name, ret, ...) ret (*name)(__VA_ARGS__);
#include "aaudio_functions26.inc"
#include "aaudio_functions28.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"
};
#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}
};

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;

int64_t written = p->AAudioStream_getFramesWritten(stream);

int64_t presented;
int64_t present_time;
p->AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &presented, &present_time);

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) / ao->samplerate;

bool eof;
ao_read_data(ao, &data, nframes, end_time, &eof, true, true);

return eof ? AAUDIO_CALLBACK_RESULT_STOP : AAUDIO_CALLBACK_RESULT_CONTINUE;
}

static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;

if (p->builder) {
p->AAudioStreamBuilder_delete(p->builder);
p->builder = NULL;
}

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)) {
struct priv *p = ao->priv;

aaudio_result_t result;
AAudioStreamBuilder *builder;

if ((result = p->AAudio_createStreamBuilder(&builder)) >= 0) {
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_setChannelCount(builder, ao->channels.num);
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, AAUDIO_CONTENT_TYPE_MOVIE);
p->AAudioStreamBuilder_setUsage(builder, AAUDIO_USAGE_MEDIA);
}

p->builder = builder;

if ((result = p->AAudioStreamBuilder_openStream(p->builder, &p->stream)) >= 0)
ao->device_buffer = p->AAudioStream_getBufferCapacityInFrames(p->stream);
}
if (result >= 0)
return 1;

MP_ERR(ao, "Failed to open stream: %s\n", p->AAudio_convertResultToText(result));
}

return -1;
}

static void start(struct ao *ao)
{
struct priv *p = ao->priv;

aaudio_result_t result = AAUDIO_OK;
if (!p->stream) {
if ((result = p->AAudioStreamBuilder_openStream(p->builder, &p->stream)) >= 0)
ao->device_buffer = p->AAudioStream_getBufferCapacityInFrames(p->stream);
}

if (result >= 0) {
aaudio_stream_state_t next;

if ((result = p->AAudioStream_requestStart(p->stream)) >= 0)
result = p->AAudioStream_waitForStateChange(
p->stream, AAUDIO_STREAM_STATE_STARTING, &next, INT64_MAX);
}

if (result < 0)
MP_ERR(ao, "Failed to start stream: %s\n", p->AAudio_convertResultToText(result));
}

static bool set_pause(struct ao *ao, bool paused)
{
struct priv *p = ao->priv;

aaudio_result_t result;
aaudio_stream_state_t state, next;

if (paused) {
result = p->AAudioStream_requestPause(p->stream);
state = AAUDIO_STREAM_STATE_PAUSING;
} else {
result = p->AAudioStream_requestStart(p->stream);
state = AAUDIO_STREAM_STATE_STARTING;
}

if (result >= 0) {
result = p->AAudioStream_waitForStateChange(p->stream, state, &next, INT64_MAX);
return true;
}

MP_ERR(ao, "Failed to pause stream: %s\n", p->AAudio_convertResultToText(result));
return false;
}

static void reset(struct ao *ao)
{
struct priv *p = ao->priv;

if (p->stream) {
p->AAudioStream_close(p->stream);
p->stream = NULL;
}
}

#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,
.buffer_capacity = AAUDIO_UNSPECIFIED,
.performance_mode = AAUDIO_PERFORMANCE_MODE_NONE,
.stream = NULL,
.lib_handle = NULL},
.options_prefix = "aaudio",
.options =
(const struct m_option[]){
{"device-id", OPT_CHOICE(device_id, {"auto", AAUDIO_UNSPECIFIED}),
M_RANGE(1, 96000)},
{"buffer-capacity", OPT_CHOICE(buffer_capacity, {"auto", AAUDIO_UNSPECIFIED}),
M_RANGE(1, 96000)},
{"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}},
};
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,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']
Expand Down
1 change: 1 addition & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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')
Expand Down

0 comments on commit 92a6460

Please sign in to comment.