[vlc-devel] [PATCH 3/3] android/audio: add AAudio
Thomas Guillem
thomas at gllm.fr
Thu Dec 3 18:16:04 CET 2020
https://developer.android.com/ndk/guides/audio/aaudio/aaudio
AAudio is a native PCM-only audio API starting Android 8.0 (but enabled
in VLC starting Android 9.0 due to some bugs).
Having tested it with few device. The latency reported by this new API is
correct and without any irregularity, contrary to the
AudioTrack.getTimeStamp() API that is very broken.
Plus side, a huge gain of performance due to the absence of JNI C->JAVA->C++.
---
modules/audio_output/Makefile.am | 1 +
modules/audio_output/android/aaudio.c | 625 ++++++++++++++++++++++++++
modules/audio_output/android/device.c | 5 +
modules/audio_output/android/device.h | 3 +
4 files changed, 634 insertions(+)
create mode 100644 modules/audio_output/android/aaudio.c
diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am
index 35b11e7e3aa..f4d38d9ff68 100644
--- a/modules/audio_output/Makefile.am
+++ b/modules/audio_output/Makefile.am
@@ -3,6 +3,7 @@ aout_LTLIBRARIES =
libandroid_audio_plugin_la_SOURCES = audio_output/android/audiotrack.c \
audio_output/android/opensles.c \
+ audio_output/android/aaudio.c \
audio_output/android/device.c audio_output/android/device.h \
video_output/android/utils.c video_output/android/utils.h
libandroid_audio_plugin_la_LIBADD = $(LIBDL) $(LIBM)
diff --git a/modules/audio_output/android/aaudio.c b/modules/audio_output/android/aaudio.c
new file mode 100644
index 00000000000..15c3c193b3f
--- /dev/null
+++ b/modules/audio_output/android/aaudio.c
@@ -0,0 +1,625 @@
+/*****************************************************************************
+ * aaudio.c: Android AAudio audio stream module
+ *****************************************************************************
+ * Copyright © 2018-2020 VLC authors, VideoLAN and VideoLabs
+ *
+ * Authors: Romain Vimont <rom1v at videolabs.io>
+ * Thomas Guillem <thomas at gllm.fr>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_aout.h>
+#include <assert.h>
+#include <dlfcn.h>
+
+#include "device.h"
+
+#include <aaudio/AAudio.h>
+
+struct sys
+{
+ AAudioStream *as;
+
+ jobject dp;
+ float volume;
+ bool muted;
+
+ audio_sample_format_t fmt;
+ int64_t frames_written;
+ int64_t frames_flush_pos;
+ bool error;
+};
+
+/* dlopen/dlsym symbols */
+static struct {
+ void *handle;
+ aaudio_result_t (*AAudio_createStreamBuilder)(AAudioStreamBuilder **);
+ const char *(*AAudio_convertResultToText)(aaudio_result_t);
+ void (*AAudioStreamBuilder_setFormat)(AAudioStreamBuilder *, aaudio_format_t);
+ void (*AAudioStreamBuilder_setChannelCount)(AAudioStreamBuilder *, int32_t);
+ void (*AAudioStreamBuilder_setBufferCapacityInFrames)(AAudioStreamBuilder *, int32_t);
+ void (*AAudioStreamBuilder_setPerformanceMode)(AAudioStreamBuilder *, aaudio_performance_mode_t);
+ void (*AAudioStreamBuilder_setSessionId)(AAudioStreamBuilder *, aaudio_session_id_t);
+ void (*AAudioStreamBuilder_setUsage)(AAudioStreamBuilder *, aaudio_usage_t);
+ aaudio_result_t (*AAudioStreamBuilder_openStream)(AAudioStreamBuilder *, AAudioStream **);
+ void (*AAudioStreamBuilder_delete)(AAudioStreamBuilder *);
+ aaudio_result_t (*AAudioStream_requestStart)(AAudioStream *);
+ aaudio_result_t (*AAudioStream_requestStop)(AAudioStream *);
+ aaudio_result_t (*AAudioStream_requestPause)(AAudioStream *);
+ aaudio_result_t (*AAudioStream_requestFlush)(AAudioStream *);
+ int32_t (*AAudioStream_getSampleRate)(AAudioStream *);
+ aaudio_result_t (*AAudioStream_getTimestamp)(AAudioStream *, clockid_t,
+ int64_t *framePosition, int64_t *timeNanoseconds);
+ aaudio_result_t (*AAudioStream_write)(AAudioStream *, void *, int32_t numFrames, int64_t timeoutNanoseconds);
+ aaudio_result_t (*AAudioStream_close)(AAudioStream *);
+ aaudio_stream_state_t (*AAudioStream_getState)(AAudioStream *);
+ aaudio_result_t (*AAudioStream_waitForStateChange)(AAudioStream *, aaudio_stream_state_t current_state, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds);
+ aaudio_session_id_t (*AAudioStream_getSessionId)(AAudioStream *);
+} vt;
+
+static int
+LoadSymbols(aout_stream_t *stream)
+{
+ /* Force failure on Android < 8.1, where multiple restarts may cause
+ * segfault (unless we leak the AAudioStream on Close). The problem is
+ * fixed in AOSP by:
+ * <https://android.googlesource.com/platform/frameworks/av/+/8a8a9e5d91c8cc110b9916982f4c5242efca33e3%5E%21/>
+ *
+ * Require AAudioStreamBuilder_setSessionId() that is available in Android
+ * 9 to enforce that the previous issue is fixed and to allow us attaching
+ * an AudioEffect to modify the gain.
+ */
+
+ static vlc_mutex_t lock = VLC_STATIC_MUTEX;
+ static int init_state = -1;
+
+ vlc_mutex_lock(&lock);
+
+ if (init_state != -1)
+ goto end;
+
+ vt.handle = dlopen("libaaudio.so", RTLD_NOW);
+ if (vt.handle == NULL)
+ {
+ msg_Err(stream, "Failed to load libaaudio");
+ init_state = 0;
+ goto end;
+ }
+
+#define AAUDIO_DLSYM(name) \
+ do { \
+ void *sym = dlsym(vt.handle, #name); \
+ if (unlikely(!sym)) { \
+ msg_Err(stream, "Failed to load symbol "#name); \
+ init_state = 0; \
+ goto end; \
+ } \
+ *(void **) &vt.name = sym; \
+ } while(0)
+
+ AAUDIO_DLSYM(AAudio_createStreamBuilder);
+ AAUDIO_DLSYM(AAudio_convertResultToText);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setChannelCount);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setFormat);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setBufferCapacityInFrames);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setPerformanceMode);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setSessionId);
+ AAUDIO_DLSYM(AAudioStreamBuilder_setUsage);
+ AAUDIO_DLSYM(AAudioStreamBuilder_openStream);
+ AAUDIO_DLSYM(AAudioStreamBuilder_delete);
+ AAUDIO_DLSYM(AAudioStream_requestStart);
+ AAUDIO_DLSYM(AAudioStream_requestStop);
+ AAUDIO_DLSYM(AAudioStream_requestPause);
+ AAUDIO_DLSYM(AAudioStream_requestFlush);
+ AAUDIO_DLSYM(AAudioStream_getSampleRate);
+ AAUDIO_DLSYM(AAudioStream_getTimestamp);
+ AAUDIO_DLSYM(AAudioStream_write);
+ AAUDIO_DLSYM(AAudioStream_close);
+ AAUDIO_DLSYM(AAudioStream_getState);
+ AAUDIO_DLSYM(AAudioStream_waitForStateChange);
+ AAUDIO_DLSYM(AAudioStream_getSessionId);
+#undef AAUDIO_DLSYM
+
+ init_state = 1;
+end:
+ vlc_mutex_unlock(&lock);
+ return init_state == 1 ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+struct role
+{
+ char vlc[16];
+ aaudio_usage_t aaudio;
+};
+
+static const struct role roles[] =
+{
+ { "accessibility", AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY },
+ { "animation", AAUDIO_USAGE_ASSISTANCE_SONIFICATION },
+ { "communication", AAUDIO_USAGE_VOICE_COMMUNICATION },
+ { "game", AAUDIO_USAGE_GAME },
+ { "music", AAUDIO_USAGE_MEDIA },
+ { "notification", AAUDIO_USAGE_NOTIFICATION_EVENT },
+ { "production", AAUDIO_USAGE_MEDIA },
+ { "test", AAUDIO_USAGE_MEDIA },
+ { "video", AAUDIO_USAGE_MEDIA },
+};
+
+static int
+role_cmp(const void *vlc_role, const void *entry)
+{
+ const struct role *role = entry;
+ return strcmp(vlc_role, role->vlc);
+}
+
+static aaudio_usage_t
+GetUsageFromVLCRole(const char *vlc_role)
+{
+ struct role *role = bsearch(vlc_role, roles, ARRAY_SIZE(roles),
+ sizeof(*roles), role_cmp);
+
+ return role == NULL ? AAUDIO_USAGE_MEDIA : role->aaudio;
+}
+
+static inline void
+LogAAudioError(aout_stream_t *stream, const char *msg, aaudio_result_t result)
+{
+ msg_Err(stream, "%s: %s", msg, vt.AAudio_convertResultToText(result));
+}
+
+static int
+WaitState(aout_stream_t *stream, aaudio_result_t wait_state)
+{
+#define WAIT_TIMEOUT VLC_TICK_FROM_SEC(2)
+ struct sys *sys = stream->sys;
+
+ if (sys->error)
+ return VLC_EGENERIC;
+
+ aaudio_stream_state_t next_state = vt.AAudioStream_getState(sys->as);
+ aaudio_stream_state_t current_state = next_state;
+
+ while (next_state != wait_state)
+ {
+ aaudio_result_t result =
+ vt.AAudioStream_waitForStateChange(sys->as, current_state,
+ &next_state, WAIT_TIMEOUT);
+ if (result != AAUDIO_OK)
+ {
+ sys->error = true;
+ return VLC_EGENERIC;
+ }
+ current_state = next_state;
+ }
+ return VLC_SUCCESS;
+}
+
+static aaudio_stream_state_t
+GetState(aout_stream_t *stream)
+{
+ struct sys *sys = stream->sys;
+
+ return sys->error ? AAUDIO_STREAM_STATE_UNINITIALIZED
+ : vt.AAudioStream_getState(sys->as);
+}
+
+#define Request(x) do { \
+ struct sys *sys = stream->sys; \
+ aaudio_result_t result = vt.AAudioStream_request##x(sys->as); \
+ if (result == AAUDIO_OK) \
+ return VLC_SUCCESS; \
+ LogAAudioError(stream, "Failed to start AAudio stream", result); \
+ sys->error = true; \
+ return VLC_EGENERIC; \
+} while (0)
+
+static inline int
+RequestStart(aout_stream_t *stream)
+{
+ Request(Start);
+}
+
+static inline int
+RequestStop(aout_stream_t *stream)
+{
+ Request(Stop);
+}
+
+static inline int
+RequestPause(aout_stream_t *stream)
+{
+ Request(Pause);
+}
+
+static inline int
+RequestFlush(aout_stream_t *stream)
+{
+ Request(Flush);
+}
+
+static bool
+GetFrameTimestamp(aout_stream_t *stream, int64_t *frame_position,
+ vlc_tick_t *frame_time_us)
+{
+ struct sys *sys = stream->sys;
+
+ int64_t time_ns;
+ aaudio_result_t result =
+ vt.AAudioStream_getTimestamp(sys->as, CLOCK_MONOTONIC,
+ frame_position, &time_ns);
+ if (result != AAUDIO_OK)
+ {
+ LogAAudioError(stream, "Failed to get timestamp", result);
+ return VLC_EGENERIC;
+ }
+
+ *frame_time_us = VLC_TICK_FROM_NS(time_ns);
+ return VLC_SUCCESS;
+}
+
+static int
+TimeGet(aout_stream_t *stream, vlc_tick_t *delay)
+{
+ struct sys *sys = stream->sys;
+
+ aaudio_stream_state_t state = GetState(stream);
+ if (state != AAUDIO_STREAM_STATE_STARTED)
+ return VLC_EGENERIC;
+
+ int64_t ref_position;
+ vlc_tick_t ref_time_us;
+ if (GetFrameTimestamp(stream, &ref_position, &ref_time_us) != VLC_SUCCESS)
+ return VLC_EGENERIC;
+ ref_position -= sys->frames_flush_pos;
+
+ int64_t diff_frames = sys->frames_written - ref_position;
+ vlc_tick_t target_time = ref_time_us
+ + vlc_tick_from_samples(diff_frames, sys->fmt.i_rate);
+ *delay = target_time - vlc_tick_now();
+
+ //fprintf(stderr, "timeget: ref: %"PRId64 " delay: %"PRId64 "\n", ref_position, *delay);
+ return VLC_SUCCESS;
+}
+
+static void
+Play(aout_stream_t *stream, block_t *block, vlc_tick_t date)
+{
+ struct sys *sys = stream->sys;
+ assert(sys->as);
+
+ aaudio_stream_state_t state = GetState(stream);
+ if (state == AAUDIO_STREAM_STATE_OPEN
+ || state == AAUDIO_STREAM_STATE_FLUSHED)
+ {
+ if (RequestStart(stream) != VLC_SUCCESS)
+ goto bailout;
+ }
+ else if (state == AAUDIO_STREAM_STATE_UNINITIALIZED)
+ goto bailout;
+ else
+ {
+ assert(state == AAUDIO_STREAM_STATE_STARTING
+ || state == AAUDIO_STREAM_STATE_STARTED);
+ }
+
+ while (block->i_nb_samples != 0)
+ {
+ static const vlc_tick_t timeout = VLC_TICK_FROM_SEC(1);
+ aaudio_result_t result =
+ vt.AAudioStream_write(sys->as, block->p_buffer,
+ block->i_nb_samples, timeout);
+ if (result > 0)
+ {
+ sys->frames_written += result;
+ block->i_nb_samples -= result;
+
+ size_t bytes_written = result * sys->fmt.i_bytes_per_frame
+ / sys->fmt.i_frame_length;
+ block->p_buffer += bytes_written;
+ block->i_buffer -= bytes_written;
+ }
+ else if (result < 0)
+ {
+ sys->error = true;
+ LogAAudioError(stream, "Failed to write audio block to AAudio stream",
+ result);
+ goto bailout;
+ }
+
+ if (unlikely(block->i_nb_samples != 0))
+ msg_Warn(stream, "wrote %d samples over %d, waiting %"PRId64 "us",
+ result, block->i_nb_samples, timeout);
+ }
+
+bailout:
+ block_Release(block);
+}
+
+static void
+Pause(aout_stream_t *stream, bool pause, vlc_tick_t pause_date)
+{
+ (void) pause_date;
+
+ aaudio_stream_state_t state = GetState(stream);
+ if (state == AAUDIO_STREAM_STATE_FLUSHED)
+ return; /* already pause r will be resumed from Play() */
+
+ if (pause)
+ {
+ if (state == AAUDIO_STREAM_STATE_STARTING
+ || state == AAUDIO_STREAM_STATE_STARTED)
+ RequestPause(stream);
+ }
+ else
+ {
+ if (state == AAUDIO_STREAM_STATE_PAUSING
+ || state == AAUDIO_STREAM_STATE_PAUSED)
+ RequestStart(stream);
+ }
+}
+
+static void
+Flush(aout_stream_t *stream)
+{
+ struct sys *sys = stream->sys;
+ aaudio_stream_state_t state = GetState(stream);
+
+ if (state == AAUDIO_STREAM_STATE_UNINITIALIZED
+ || state == AAUDIO_STREAM_STATE_FLUSHED
+ || state == AAUDIO_STREAM_STATE_OPEN)
+ return;
+
+ /* Flush must be requested while PAUSED */
+
+ if (state != AAUDIO_STREAM_STATE_PAUSING
+ && state != AAUDIO_STREAM_STATE_PAUSED
+ && RequestPause(stream) != VLC_SUCCESS)
+ return;
+
+ state = GetState(stream);
+ if (state == AAUDIO_STREAM_STATE_PAUSING
+ && WaitState(stream, AAUDIO_STREAM_STATE_PAUSED) != VLC_SUCCESS)
+ return;
+
+ vlc_tick_t unused;
+ if (GetFrameTimestamp(stream, &sys->frames_flush_pos, &unused) != VLC_SUCCESS)
+ msg_Warn(stream, "Flush: can't get paused position");
+
+ if (RequestFlush(stream) != VLC_SUCCESS)
+ return;
+
+ if (WaitState(stream, AAUDIO_STREAM_STATE_FLUSHED) != VLC_SUCCESS)
+ return;
+
+ sys->frames_written = 0;
+}
+
+static void
+Drain(aout_stream_t *stream)
+{
+ struct sys *sys = stream->sys;
+ aaudio_stream_state_t state = GetState(stream);
+
+ /* In case of differed start, the stream may not have been started yet */
+ if (unlikely(state != AAUDIO_STREAM_STATE_STARTED))
+ {
+ if (state != AAUDIO_STREAM_STATE_STARTING
+ && RequestStart(stream) != VLC_SUCCESS)
+ return;
+
+ if (WaitState(stream, AAUDIO_STREAM_STATE_STARTED) != VLC_SUCCESS)
+ return;
+
+ vlc_tick_sleep(vlc_tick_from_samples(sys->frames_written,
+ sys->fmt.i_rate));
+ }
+
+ vlc_tick_t delay;
+ if (TimeGet(stream, &delay) == VLC_SUCCESS)
+ vlc_tick_sleep(delay);
+}
+
+static void
+VolumeSet(aout_stream_t *stream, float vol)
+{
+ struct sys *sys = stream->sys;
+
+ if (vol > 1.0f)
+ vol = vol * vol * vol;
+
+ sys->volume = vol;
+
+ if (sys->muted)
+ return;
+
+ int ret = DynamicsProcessing_SetVolume(stream, sys->dp, vol);
+ if (ret != VLC_SUCCESS)
+ msg_Warn(stream, "failed to set the volume via DynamicsProcessing");
+}
+
+static void
+MuteSet(aout_stream_t *stream, bool mute)
+{
+ struct sys *sys = stream->sys;
+
+ sys->muted = mute;
+ int ret = DynamicsProcessing_SetVolume(stream, sys->dp,
+ mute ? 0.0f : sys->volume);
+ if (ret != VLC_SUCCESS)
+ msg_Warn(stream, "failed to mute via DynamicsProcessing");
+}
+
+static int OpenAAudioStream(aout_stream_t *stream, audio_sample_format_t *fmt)
+{
+ struct sys *sys = stream->sys;
+ aaudio_result_t result;
+
+ AAudioStreamBuilder *builder;
+ result = vt.AAudio_createStreamBuilder(&builder);
+ if (result != AAUDIO_OK)
+ {
+ LogAAudioError(stream, "Failed to create AAudio stream builder", result);
+ return VLC_EGENERIC;
+ }
+
+ aaudio_format_t format;
+ if (fmt->i_format == VLC_CODEC_S16N)
+ format = AAUDIO_FORMAT_PCM_I16;
+ else
+ {
+ if (fmt->i_format != VLC_CODEC_FL32)
+ {
+ /* override to request conversion */
+ fmt->i_format = VLC_CODEC_FL32;
+ fmt->i_bytes_per_frame = 4 * fmt->i_channels;
+ }
+ format = AAUDIO_FORMAT_PCM_FLOAT;
+ }
+
+ vt.AAudioStreamBuilder_setFormat(builder, format);
+ vt.AAudioStreamBuilder_setChannelCount(builder, fmt->i_channels);
+
+ /* Setup the session-id */
+ int32_t session_id = var_InheritInteger(stream, "audiotrack-session-id");
+ if (session_id == 0)
+ session_id = AAUDIO_SESSION_ID_ALLOCATE;
+ vt.AAudioStreamBuilder_setSessionId(builder, session_id);
+
+ /* Setup the role */
+ char *vlc_role = var_InheritString(stream, "role");
+ if (vlc_role != NULL)
+ {
+ aaudio_usage_t usage = GetUsageFromVLCRole(vlc_role);
+ vt.AAudioStreamBuilder_setUsage(builder, usage);
+ free(vlc_role);
+ } /* else if not set, default is MEDIA usage */
+
+ bool low_latency = false;
+ if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
+ {
+ fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
+ low_latency = true;
+ }
+
+ if (low_latency)
+ vt.AAudioStreamBuilder_setPerformanceMode(builder,
+ AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ else
+ {
+ /* Setup arround 2seconds of buffer. We don't know the native rate as
+ * this state yet, let assume a good default one */
+ int32_t frames_max = samples_from_vlc_tick(AOUT_MAX_PREPARE_TIME, 44100);
+ vt.AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames_max);
+ }
+
+
+ AAudioStream *as;
+ result = vt.AAudioStreamBuilder_openStream(builder, &as);
+ if (result != AAUDIO_OK)
+ {
+ LogAAudioError(stream, "Failed to open AAudio stream", result);
+ vt.AAudioStreamBuilder_delete(builder);
+ return VLC_EGENERIC;
+ }
+ vt.AAudioStreamBuilder_delete(builder);
+
+ /* Use the native sample rate of the device */
+ fmt->i_rate = vt.AAudioStream_getSampleRate(as);
+ assert(fmt->i_rate > 0);
+
+ sys->as = as;
+ sys->fmt = *fmt;
+ return VLC_SUCCESS;
+}
+
+static void
+Stop(aout_stream_t *stream)
+{
+ struct sys *sys = stream->sys;
+
+ RequestStop(stream);
+
+ if (WaitState(stream, AAUDIO_STREAM_STATE_STOPPED) == VLC_SUCCESS)
+ vt.AAudioStream_close(sys->as);
+ else
+ msg_Warn(stream, "Error waiting for stopped state");
+
+ DynamicsProcessing_Delete(stream, sys->dp);
+
+ free(sys);
+}
+
+int
+AAudio_Start(aout_stream_t *stream, audio_sample_format_t *fmt,
+ enum android_audio_device_type adev)
+{
+ (void) adev;
+
+ if (!AOUT_FMT_LINEAR(fmt))
+ return VLC_EGENERIC;
+
+ if (LoadSymbols(stream) != VLC_SUCCESS)
+ return VLC_EGENERIC;
+
+ struct sys *sys = stream->sys = malloc(sizeof(*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ sys->volume = 1.0f;
+ sys->muted = false;
+ sys->frames_written = 0;
+ sys->frames_flush_pos = 0;
+ sys->error = false;
+
+ int ret = OpenAAudioStream(stream, fmt);
+ if (ret != VLC_SUCCESS)
+ {
+ free(sys);
+ return ret;
+ }
+
+ int32_t session_id = vt.AAudioStream_getSessionId(sys->as);
+
+ if (session_id != AAUDIO_SESSION_ID_NONE)
+ sys->dp = DynamicsProcessing_New(stream, session_id);
+ else
+ sys->dp = NULL;
+
+ if (sys->dp == NULL)
+ {
+ msg_Err(stream, "failed to attach DynamicsProcessing to the stream");
+ vt.AAudioStream_close(sys->as);
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ stream->stop = Stop;
+ stream->time_get = TimeGet;
+ stream->play = Play;
+ stream->pause = Pause;
+ stream->flush = Flush;
+ stream->drain = Drain;
+ stream->volume_set = VolumeSet;
+ stream->mute_set = MuteSet;
+
+ return VLC_SUCCESS;
+}
diff --git a/modules/audio_output/android/device.c b/modules/audio_output/android/device.c
index 969d7bb6e50..54f8f31afa2 100644
--- a/modules/audio_output/android/device.c
+++ b/modules/audio_output/android/device.c
@@ -310,6 +310,11 @@ vlc_module_begin ()
N_("Output back-end"), N_("Audio output back-end interface."))
set_callback(Open)
+ add_submodule()
+ set_capability("aout stream", 190)
+ set_callback(AAudio_Start)
+ add_shortcut("aaudio")
+
add_submodule()
set_capability("aout stream", 180)
set_callback(AudioTrack_Start)
diff --git a/modules/audio_output/android/device.h b/modules/audio_output/android/device.h
index 4b315368e62..48beaeb637a 100644
--- a/modules/audio_output/android/device.h
+++ b/modules/audio_output/android/device.h
@@ -74,6 +74,9 @@ typedef int (*aout_stream_start)(aout_stream_t *s, audio_sample_format_t *fmt,
enum android_audio_device_type dev);
int
+AAudio_Start(aout_stream_t *, audio_sample_format_t *,
+ enum android_audio_device_type);
+int
AudioTrack_Start(aout_stream_t *, audio_sample_format_t *,
enum android_audio_device_type);
int
--
2.28.0
More information about the vlc-devel
mailing list