diff --git a/audio/out/aaudio_functions26.inc b/audio/out/aaudio_functions26.inc new file mode 100644 index 0000000000000..8eddb2ebfb412 --- /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, 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) diff --git a/audio/out/aaudio_functions28.inc b/audio/out/aaudio_functions28.inc new file mode 100644 index 0000000000000..50760fbf352d7 --- /dev/null +++ b/audio/out/aaudio_functions28.inc @@ -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) diff --git a/audio/out/ao.c b/audio/out/ao.c index 0544da2644b86..c7f655e154d03 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 0000000000000..2e298fef0cf2f --- /dev/null +++ b/audio/out/ao_aaudio.c @@ -0,0 +1,303 @@ +/* + * 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 + +#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}}, +}; diff --git a/meson.build b/meson.build index b9c102b5d90f9..eedaf87b80875 100644 --- a/meson.build +++ b/meson.build @@ -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'] diff --git a/meson.options b/meson.options index a2fd8524e01ca..78eb9de193d9e 100644 --- a/meson.options +++ b/meson.options @@ -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')