mirror of
https://github.com/mpv-player/mpv.git
synced 2026-05-07 20:02:49 +00:00
ao/aaudio: implement aaudio backend for android
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
AAUDIO_FUNCTION(AAudioStreamBuilder_setChannelMask, void, AAudioStreamBuilder *builder, aaudio_channel_mask_t channelMask)
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
* AAudio audio output driver
|
||||
*
|
||||
* Copyright (C) 2024 Jun Bo Bi <jambonmcyeah@gmail.com>
|
||||
*
|
||||
* 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 <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <aaudio/AAudio.h>
|
||||
#include <android/api-level.h>
|
||||
|
||||
#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}},
|
||||
};
|
||||
@@ -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']
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user