[vlc-commits] [Git][videolan/vlc][master] 13 commits: modules: move android aout to a specific folder

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Fri Oct 14 18:38:37 UTC 2022



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
45b55b01 by Thomas Guillem at 2022-10-14T18:25:55+00:00
modules: move android aout to a specific folder

- - - - -
8f6fd3f1 by Thomas Guillem at 2022-10-14T18:25:55+00:00
opensles: re-order to avoid forward declarations

Adding forward declarations for Start and Stop temporarily.

- - - - -
0b1040c5 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: split device from stream handling

Like mmdevice with wasapi/directsound.

To get support for multiple audio tracks playback at once, all aout
modules need to be split between device and stream. This is the first
step in that direction (second step is to add aout_stream_t in the CORE).

This will allow a single aout module to choose between audiotrack,
opensles and the future aaudio plugin depending on the audio
format/codec. Indeed, only AudioTrack can handle pass-through, and PCM
playback should be done by aaudio in priority.

- - - - -
50248407 by Thomas Guillem at 2022-10-14T18:25:55+00:00
audiotrack: rename p_aout to stream

- - - - -
30b950d5 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: export DynamicsProcessing helpers

- - - - -
79243d19 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: add support for stream drain

- - - - -
6adc22d9 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: add aout_stream_TimingReport

- - - - -
684a3979 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: add aout_stream_RestartRequest

- - - - -
df3d0ae7 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: time_get is not mandatory

aout_stream_TimingReport() is the way to go.

- - - - -
41d82b8e by Thomas Guillem at 2022-10-14T18:25:55+00:00
opensles: log when using OpenSLES

- - - - -
a1c27171 by Thomas Guillem at 2022-10-14T18:25:55+00:00
audiotrack: log when using AudioTrack

- - - - -
a53eb42e by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: add AAudio

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++.

-40ms of latency reported by Sync-One2 versus -150ms with AudioTrack.

The remaining 40ms of latency to fix are likely on the mediacodec/vout side.

- - - - -
d696b099 by Thomas Guillem at 2022-10-14T18:25:55+00:00
android/audio: forward "aout" var to "aout stream"

- - - - -


7 changed files:

- modules/audio_output/Makefile.am
- + modules/audio_output/android/aaudio.c
- modules/audio_output/audiotrack.c → modules/audio_output/android/audiotrack.c
- + modules/audio_output/android/device.c
- + modules/audio_output/android/device.h
- modules/audio_output/opensles_android.c → modules/audio_output/android/opensles.c
- po/POTFILES.in


Changes:

=====================================
modules/audio_output/Makefile.am
=====================================
@@ -1,14 +1,20 @@
 aoutdir = $(pluginsdir)/audio_output
 aout_LTLIBRARIES =
 
-libopensles_android_plugin_la_SOURCES = audio_output/opensles_android.c
+libopensles_android_plugin_la_SOURCES = audio_output/android/opensles.c
 libopensles_android_plugin_la_LIBADD = libandroid_utils.la $(LIBDL) $(LIBM)
 
-libandroid_audiotrack_plugin_la_SOURCES = audio_output/audiotrack.c
+libandroid_audiotrack_plugin_la_SOURCES = audio_output/android/audiotrack.c audio_output/android/device.h
 libandroid_audiotrack_plugin_la_LIBADD = libandroid_utils.la
 
+libandroid_aaudio_plugin_la_SOURCES = audio_output/android/aaudio.c audio_output/android/device.h
+libandroid_aaudio_plugin_la_LIBADD = libandroid_utils.la $(LIBDL)
+
+libandroid_audiodevice_plugin_la_SOURCES = audio_output/android/device.c audio_output/android/device.h
+
 if HAVE_ANDROID
-aout_LTLIBRARIES += libandroid_audiotrack_plugin.la libopensles_android_plugin.la
+aout_LTLIBRARIES += libandroid_audiodevice_plugin.la libopensles_android_plugin.la \
+	libandroid_audiotrack_plugin.la libandroid_aaudio_plugin.la
 endif
 
 libadummy_plugin_la_SOURCES = audio_output/adummy.c


=====================================
modules/audio_output/android/aaudio.c
=====================================
@@ -0,0 +1,824 @@
+/*****************************************************************************
+ * aaudio.c: Android AAudio audio stream module
+ *****************************************************************************
+ * Copyright © 2018-2022 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>
+
+#define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)
+
+struct sys
+{
+    AAudioStream *as;
+
+    jobject dp;
+    float volume;
+    bool muted;
+
+    audio_sample_format_t fmt;
+    int64_t frames_flush_pos;
+    bool error;
+
+    /* Spinlock that protect all the following variables */
+    vlc_mutex_t lock;
+
+    /* Frame FIFO */
+    vlc_frame_t *frame_chain;
+    vlc_frame_t **frame_last;
+
+    /* Size of the frame FIFO */
+    size_t frames_total_bytes;
+    /* Bytes of silence written until it started */
+    size_t start_silence_bytes;
+    /* Bytes of silence written after it started */
+    size_t underrun_bytes;
+    /* Date when the data callback should start to process audio */
+    vlc_tick_t first_play_date;
+    /* True is the data callback started to process audio from the frame FIFO */
+    bool started;
+    bool draining;
+    /* Bytes written since the last timing report */
+    size_t timing_report_last_written_bytes;
+    /* Number of bytes to write before sending a timing report */
+    size_t timing_report_delay_bytes;
+};
+
+/* 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_setDataCallback)(AAudioStreamBuilder *, AAudioStream_dataCallback, void *);
+    void                  (*AAudioStreamBuilder_setErrorCallback)(AAudioStreamBuilder *, AAudioStream_errorCallback,  void *);
+    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_setDataCallback);
+    AAUDIO_DLSYM(AAudioStreamBuilder_setErrorCallback);
+    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 inline uint64_t
+BytesToFrames(struct sys *sys, size_t bytes)
+{
+    return bytes * sys->fmt.i_frame_length / sys->fmt.i_bytes_per_frame;
+}
+
+static inline vlc_tick_t
+FramesToTicks(struct sys *sys, int64_t frames)
+{
+    return vlc_tick_from_samples(frames, sys->fmt.i_rate);
+}
+
+
+static inline vlc_tick_t
+BytesToTicks(struct sys *sys, size_t bytes)
+{
+    return FramesToTicks(sys, BytesToFrames(sys, bytes));
+}
+
+static inline size_t
+FramesToBytes(struct sys *sys, uint64_t frames)
+{
+    return frames * sys->fmt.i_bytes_per_frame / sys->fmt.i_frame_length;
+}
+
+static inline int64_t
+TicksToFrames(struct sys *sys, vlc_tick_t ticks)
+{
+    return samples_from_vlc_tick(ticks, sys->fmt.i_rate);
+}
+
+static inline size_t
+TicksToBytes(struct sys *sys, vlc_tick_t ticks)
+{
+    return FramesToBytes(sys, TicksToFrames(sys, ticks));
+}
+
+static int
+GetFrameTimestampLocked(aout_stream_t *stream, int64_t *pos_frames,
+                        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,
+                                         pos_frames, &time_ns);
+    if (result != AAUDIO_OK)
+        return VLC_EGENERIC;
+
+    assert(*pos_frames > 0);
+
+    *frame_time_us = VLC_TICK_FROM_NS(time_ns);
+    return VLC_SUCCESS;
+}
+
+static aaudio_data_callback_result_t
+DataCallback(AAudioStream *as, void *user, void *data_, int32_t num_frames)
+{
+    aout_stream_t *stream = user;
+    struct sys *sys = stream->sys;
+    assert(as == sys->as); (void) as;
+
+    size_t bytes = FramesToBytes(sys, num_frames);
+    uint8_t *data = data_;
+
+    vlc_mutex_lock(&sys->lock);
+
+    if (!sys->started)
+    {
+        size_t tocopy;
+
+        if (sys->first_play_date == VLC_TICK_INVALID)
+            tocopy = bytes;
+        else
+        {
+            vlc_tick_t now = vlc_tick_now();
+            vlc_tick_t data_ticks = FramesToTicks(sys, num_frames);
+            vlc_tick_t silence_ticks = now + data_ticks - sys->first_play_date;
+
+            /* Write silence to reach the first play date */
+            silence_ticks = data_ticks - silence_ticks;
+            if (silence_ticks > 0)
+            {
+                tocopy = TicksToBytes(sys, silence_ticks);
+                if (tocopy > bytes)
+                    tocopy = bytes;
+            }
+            else
+                tocopy = 0;
+        }
+
+        if (tocopy > 0)
+        {
+            memset(data, 0, tocopy);
+
+            data += tocopy;
+            bytes -= tocopy;
+
+            sys->start_silence_bytes += tocopy;
+        }
+    }
+
+    while (bytes > 0)
+    {
+        vlc_frame_t *f = sys->frame_chain;
+        if (f == NULL)
+        {
+            aaudio_data_callback_result_t res;
+
+            if (!sys->draining)
+            {
+                sys->underrun_bytes += bytes;
+                res = AAUDIO_CALLBACK_RESULT_CONTINUE;
+            }
+            else
+                res = AAUDIO_CALLBACK_RESULT_STOP;
+
+            vlc_mutex_unlock(&sys->lock);
+
+            memset(data, 0, bytes);
+
+            if (res == AAUDIO_CALLBACK_RESULT_STOP)
+                aout_stream_DrainedReport(stream);
+            return res;
+        }
+
+        size_t tocopy = f->i_buffer > bytes ? bytes : f->i_buffer;
+
+        sys->started = true;
+
+        sys->frames_total_bytes -= tocopy;
+        sys->timing_report_last_written_bytes += tocopy;
+
+        if (sys->timing_report_last_written_bytes >= sys->timing_report_delay_bytes)
+        {
+            sys->timing_report_last_written_bytes = 0;
+
+            int64_t pos_frames;
+            vlc_tick_t system_ts;
+            if (GetFrameTimestampLocked(stream, &pos_frames, &system_ts) == VLC_SUCCESS)
+            {
+                pos_frames -= sys->frames_flush_pos;
+                assert(pos_frames > 0);
+                vlc_tick_t pos_ticks = FramesToTicks(sys, pos_frames);
+
+                /* Add the start silence to the system time and don't subtract
+                 * it from pos_ticks to avoid (unlikely) negatives ts */
+                system_ts += BytesToTicks(sys, sys->start_silence_bytes);
+                aout_stream_TimingReport(stream, system_ts, pos_ticks);
+            }
+        }
+
+        memcpy(data, f->p_buffer, tocopy);
+
+        data += tocopy;
+        bytes -= tocopy;
+        f->i_buffer -= tocopy;
+        f->p_buffer += tocopy;
+
+        if (f->i_buffer == 0)
+        {
+            sys->frame_chain = f->p_next;
+            if (sys->frame_chain == NULL)
+                sys->frame_last = &sys->frame_chain;
+
+            vlc_frame_Release(f);
+        }
+    }
+
+    vlc_mutex_unlock(&sys->lock);
+
+    return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static void
+ErrorCallback(AAudioStream *as, void *user, aaudio_result_t error)
+{
+    aout_stream_t *stream = user;
+    (void) as;
+
+    LogAAudioError(stream, "AAudio stream error or disconnected:", error);
+    aout_stream_RestartRequest(stream, AOUT_RESTART_OUTPUT);
+}
+
+static void
+Play(aout_stream_t *stream, vlc_frame_t *frame, 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);
+    }
+
+    vlc_mutex_lock(&sys->lock);
+
+    if (!sys->started)
+    {
+        vlc_tick_t now = vlc_tick_now();
+        sys->first_play_date = date - BytesToTicks(sys, sys->frames_total_bytes);
+
+        if (sys->first_play_date > now)
+            msg_Dbg(stream, "deferring start (%"PRId64" us)",
+                    sys->first_play_date - now);
+        else
+            msg_Dbg(stream, "starting late (%"PRId64" us)",
+                    sys->first_play_date - now);
+    }
+
+
+    vlc_frame_ChainLastAppend(&sys->frame_last, frame);
+    sys->frames_total_bytes += frame->i_buffer;
+
+    size_t underrun_bytes = sys->underrun_bytes;
+    sys->underrun_bytes = 0;
+    vlc_mutex_unlock(&sys->lock);
+
+    if (underrun_bytes > 0)
+        msg_Warn(stream, "underflow of %" PRId64 " us",
+                 BytesToTicks(sys, underrun_bytes));
+
+    return;
+bailout:
+    vlc_frame_Release(frame);
+}
+
+static void
+Pause(aout_stream_t *stream, bool pause, vlc_tick_t pause_date)
+{
+    struct sys *sys = stream->sys;
+    (void) pause_date;
+
+    aaudio_stream_state_t state = GetState(stream);
+    if (state == AAUDIO_STREAM_STATE_FLUSHED)
+        return; /* already paused, will be resumed from Play() */
+
+    if (pause)
+    {
+        if (state == AAUDIO_STREAM_STATE_STARTING
+         || state == AAUDIO_STREAM_STATE_STARTED)
+            RequestPause(stream);
+
+        state = GetState(stream);
+        if (state == AAUDIO_STREAM_STATE_PAUSING
+         && WaitState(stream, AAUDIO_STREAM_STATE_PAUSED) != VLC_SUCCESS)
+
+        sys->started = false;
+    }
+    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;
+
+    /* No lock needed since the DataCallback stop being called (paused) */
+    vlc_frame_ChainRelease(sys->frame_chain);
+    sys->frame_chain = NULL;
+    sys->frame_last = &sys->frame_chain;
+    sys->frames_total_bytes = 0;
+
+    sys->started = false;
+    sys->draining = false;
+    sys->first_play_date = VLC_TICK_INVALID;
+    sys->start_silence_bytes = 0;
+    sys->timing_report_last_written_bytes = 0;
+    sys->underrun_bytes = 0;
+
+    vlc_tick_t unused;
+    if (GetFrameTimestampLocked(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;
+}
+
+static void
+Drain(aout_stream_t *stream)
+{
+    struct sys *sys = stream->sys;
+    aaudio_stream_state_t state = GetState(stream);
+
+    vlc_mutex_lock(&sys->lock);
+    sys->draining = true;
+    vlc_mutex_unlock(&sys->lock);
+
+    /* 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;
+    }
+}
+
+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 || sys->dp == NULL)
+        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;
+
+    if (sys->dp == NULL)
+        return;
+
+    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;
+    }
+
+    vt.AAudioStreamBuilder_setDataCallback(builder, DataCallback, stream);
+    vt.AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, stream);
+
+    if (low_latency)
+        vt.AAudioStreamBuilder_setPerformanceMode(builder,
+            AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+
+    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;
+
+    sys->timing_report_delay_bytes = TicksToBytes(sys, TIMING_REPORT_DELAY_TICKS);
+
+    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");
+
+    if (sys->dp != NULL)
+        DynamicsProcessing_Delete(stream, sys->dp);
+
+    vlc_frame_ChainRelease(sys->frame_chain);
+
+    free(sys);
+}
+
+static int
+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_flush_pos = 0;
+    sys->error = false;
+
+    vlc_mutex_init(&sys->lock);
+    sys->frame_chain = NULL;
+    sys->frame_last = &sys->frame_chain;
+    sys->frames_total_bytes = 0;
+    sys->start_silence_bytes = 0;
+    sys->underrun_bytes = 0;
+    sys->started = false;
+    sys->draining = false;
+    sys->first_play_date = VLC_TICK_INVALID;
+    sys->timing_report_last_written_bytes = 0;
+
+    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_Warn(stream, "failed to attach DynamicsProcessing to the stream)");
+
+    stream->stop = Stop;
+    stream->play = Play;
+    stream->pause = Pause;
+    stream->flush = Flush;
+    stream->drain = Drain;
+    stream->volume_set = VolumeSet;
+    stream->mute_set = MuteSet;
+
+    msg_Dbg(stream, "using AAudio API");
+    return VLC_SUCCESS;
+}
+
+vlc_module_begin ()
+    set_shortname("AAudio")
+    set_description("Android AAudio output")
+    set_subcategory(SUBCAT_AUDIO_AOUT)
+    set_capability("aout android stream", 190)
+    set_callback(Start)
+vlc_module_end ()


=====================================
modules/audio_output/audiotrack.c → modules/audio_output/android/audiotrack.c
=====================================
@@ -34,6 +34,7 @@
 #include <vlc_plugin.h>
 #include <vlc_aout.h>
 #include "../video_output/android/utils.h"
+#include "device.h"
 
 #define SMOOTHPOS_SAMPLE_COUNT 10
 #define SMOOTHPOS_INTERVAL_US VLC_TICK_FROM_MS(30) // 30ms
@@ -42,49 +43,16 @@
 
 #define AUDIOTRACK_MAX_BUFFER_US VLC_TICK_FROM_MS(750) // 750ms
 
-static int  Open( vlc_object_t * );
-static void Close( vlc_object_t * );
-static void Stop( audio_output_t * );
-static int Start( audio_output_t *, audio_sample_format_t * );
+static void Stop( aout_stream_t * );
+static void Play( aout_stream_t *, block_t *, vlc_tick_t );
+static void Flush( aout_stream_t * );
+static void Pause( aout_stream_t *, bool, vlc_tick_t );
+static void VolumeSet( aout_stream_t *, float );
+static void MuteSet( aout_stream_t *, bool );
 static void *AudioTrack_Thread( void * );
 
-/* There is an undefined behavior when configuring AudioTrack with SPDIF or
- * more than 2 channels when there is no HDMI out. It may succeed and the
- * Android ressampler will be used to downmix to stereo. It may fails cleanly,
- * and this module will be able to recover and fallback to stereo. Finally, in
- * some rare cases, it may crash during init or while ressampling. Because of
- * the last case we don't try up to 8 channels and we use AT_DEV_STEREO device
- * per default */
-enum at_dev {
-    AT_DEV_STEREO = 0,
-    AT_DEV_PCM,
-    AT_DEV_ENCODED,
-};
-#define AT_DEV_DEFAULT AT_DEV_STEREO
-#define AT_DEV_MAX_CHANNELS 8
-
-static const struct {
-    const char *id;
-    const char *name;
-    enum at_dev at_dev;
-} at_devs[] = {
-    { "stereo", "Up to 2 channels (compat mode).", AT_DEV_STEREO },
-    { "pcm", "Up to 8 channels.", AT_DEV_PCM },
-
-    /* With "encoded", the module will try to play every audio codecs via
-     * passthrough.
-     *
-     * With "encoded:ENCODING_FLAGS_MASK", the module will try to play only
-     * codecs specified by ENCODING_FLAGS_MASK. This extra value is a long long
-     * that contains binary-shifted AudioFormat.ENCODING_* values. */
-    { "encoded", "Up to 8 channels, passthrough if available.", AT_DEV_ENCODED },
-    {  NULL, NULL, AT_DEV_DEFAULT },
-};
-
 typedef struct
 {
-    enum at_dev at_dev;
-
     jobject p_audiotrack; /* AudioTrack ref */
     jobject p_dp;
     float volume;
@@ -179,22 +147,8 @@ typedef struct
  * will be done by VLC */
 #define AUDIOTRACK_NATIVE_SAMPLERATE
 
-#define AUDIOTRACK_SESSION_ID_TEXT " Id of audio session the AudioTrack must be attached to"
-
-vlc_module_begin ()
-    set_shortname( "AudioTrack" )
-    set_description( "Android AudioTrack audio output" )
-    set_capability( "audio output", 180 )
-    set_subcategory( SUBCAT_AUDIO_AOUT )
-    add_integer( "audiotrack-session-id", 0,
-            AUDIOTRACK_SESSION_ID_TEXT, NULL )
-        change_private()
-    add_shortcut( "audiotrack" )
-    set_callbacks( Open, Close )
-vlc_module_end ()
-
 #define THREAD_NAME "android_audiotrack"
-#define GET_ENV() android_getEnv( VLC_OBJECT(p_aout), THREAD_NAME )
+#define GET_ENV() android_getEnv( VLC_OBJECT(stream), THREAD_NAME )
 
 static struct
 {
@@ -287,24 +241,23 @@ static struct
         jfieldID framePosition;
         jfieldID nanoTime;
     } AudioTimestamp;
-    struct {
-        jclass clazz;
-        jmethodID ctor;
-        jmethodID setInputGainAllChannelsTo;
-        jmethodID setEnabled;
-    } DynamicsProcessing;
+    struct DynamicsProcessing_fields DynamicsProcessing;
 } jfields;
 
 /* init all jni fields.
  * Done only one time during the first initialisation */
-static bool
-InitJNIFields( audio_output_t *p_aout, JNIEnv* env )
+int
+AudioTrack_InitJNI( audio_output_t *p_aout,
+                    struct DynamicsProcessing_fields *dp_fields )
 {
     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
     static int i_init_state = -1;
-    bool ret;
     jclass clazz;
     jfieldID field;
+    JNIEnv *env = android_getEnv( VLC_OBJECT(p_aout), THREAD_NAME );
+
+    if( env == NULL )
+        return VLC_EGENERIC;
 
     vlc_mutex_lock( &lock );
 
@@ -531,6 +484,7 @@ InitJNIFields( audio_output_t *p_aout, JNIEnv* env )
                     "setEnabled", "(Z)I", true );
         }
     }
+    *dp_fields = jfields.DynamicsProcessing;
 
 #undef CHECK_EXCEPTION
 #undef GET_CLASS
@@ -539,33 +493,30 @@ InitJNIFields( audio_output_t *p_aout, JNIEnv* env )
 
     i_init_state = 1;
 end:
-    ret = i_init_state == 1;
-    if( !ret )
-        msg_Err( p_aout, "AudioTrack jni init failed" );
     vlc_mutex_unlock( &lock );
-    return ret;
+    return i_init_state == 1 ? VLC_SUCCESS : VLC_EGENERIC;
 }
 
 static inline bool
-check_exception( JNIEnv *env, audio_output_t *p_aout,
+check_exception( JNIEnv *env, aout_stream_t *stream,
                  const char *class, const char *method )
 {
     if( (*env)->ExceptionCheck( env ) )
     {
-        aout_sys_t *p_sys = p_aout->sys;
+        aout_sys_t *p_sys = stream->sys;
 
         p_sys->b_audiotrack_exception = true;
         p_sys->b_error = true;
         (*env)->ExceptionDescribe( env );
         (*env)->ExceptionClear( env );
-        msg_Err( p_aout, "%s.%s triggered an exception !", class, method );
+        msg_Err( stream, "%s.%s triggered an exception !", class, method );
         return true;
     } else
         return false;
 }
 
-#define CHECK_EXCEPTION( class, method ) check_exception( env, p_aout, class, method )
-#define CHECK_AT_EXCEPTION( method ) check_exception( env, p_aout, "AudioTrack", method )
+#define CHECK_EXCEPTION( class, method ) check_exception( env, stream, class, method )
+#define CHECK_AT_EXCEPTION( method ) check_exception( env, stream, "AudioTrack", method )
 
 #define JNI_CALL( what, obj, method, ... ) (*env)->what( env, obj, method, ##__VA_ARGS__ )
 
@@ -620,7 +571,7 @@ us_to_frames( aout_sys_t *p_sys, vlc_tick_t i_us )
  * true for all devices or Android versions.
  */
 static uint64_t
-AudioTrack_getPlaybackHeadPosition( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_getPlaybackHeadPosition( JNIEnv *env, aout_stream_t *stream )
 {
     /* Android doc:
      * getPlaybackHeadPosition: Returns the playback head position expressed in
@@ -632,7 +583,7 @@ AudioTrack_getPlaybackHeadPosition( JNIEnv *env, audio_output_t *p_aout )
      * zero by flush(), reload(), and stop().
      */
 
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     uint32_t i_pos;
 
     /* int32_t to uint32_t */
@@ -651,10 +602,10 @@ AudioTrack_getPlaybackHeadPosition( JNIEnv *env, audio_output_t *p_aout )
  * Called after flush, or start
  */
 static void
-AudioTrack_ResetWrapCount( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_ResetWrapCount( JNIEnv *env, aout_stream_t *stream )
 {
     (void) env;
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     p_sys->headpos.i_last = 0;
     p_sys->headpos.i_wrap_count = 0;
@@ -667,9 +618,9 @@ AudioTrack_ResetWrapCount( JNIEnv *env, audio_output_t *p_aout )
  * Reset AudioTrack SmoothPosition and TimestampPosition
  */
 static void
-AudioTrack_ResetPositions( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_ResetPositions( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     VLC_UNUSED( env );
 
     p_sys->timestamp.i_last_time = 0;
@@ -686,19 +637,19 @@ AudioTrack_ResetPositions( JNIEnv *env, audio_output_t *p_aout )
  * Reset all AudioTrack positions and internal state
  */
 static void
-AudioTrack_Reset( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_Reset( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
-    AudioTrack_ResetPositions( env, p_aout );
-    AudioTrack_ResetWrapCount( env, p_aout );
+    AudioTrack_ResetPositions( env, stream );
+    AudioTrack_ResetWrapCount( env, stream );
     p_sys->i_samples_written = 0;
 }
 
 static vlc_tick_t
-AudioTrack_GetLatencyUs( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_GetLatencyUs( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     if( jfields.AudioTrack.getLatency )
     {
@@ -725,16 +676,16 @@ AudioTrack_GetLatencyUs( JNIEnv *env, audio_output_t *p_aout )
  * precision (+/- 20ms on old devices).
  */
 static vlc_tick_t
-AudioTrack_GetSmoothPositionUs( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_GetSmoothPositionUs( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     uint64_t i_audiotrack_us;
     vlc_tick_t i_now = vlc_tick_now();
 
     /* Fetch an AudioTrack position every SMOOTHPOS_INTERVAL_US (30ms) */
     if( i_now - p_sys->smoothpos.i_last_time >= SMOOTHPOS_INTERVAL_US )
     {
-        i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, p_aout ) );
+        i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, stream ) );
         if( i_audiotrack_us == 0 )
             goto bailout;
 
@@ -755,16 +706,16 @@ AudioTrack_GetSmoothPositionUs( JNIEnv *env, audio_output_t *p_aout )
 
     }
     if( p_sys->smoothpos.i_us != 0 )
-        return p_sys->smoothpos.i_us + i_now - AudioTrack_GetLatencyUs( env, p_aout );
+        return p_sys->smoothpos.i_us + i_now - AudioTrack_GetLatencyUs( env, stream );
 
 bailout:
     return 0;
 }
 
 static vlc_tick_t
-AudioTrack_GetTimestampPositionUs( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_GetTimestampPositionUs( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     vlc_tick_t i_now;
 
     if( !p_sys->timestamp.p_obj )
@@ -831,9 +782,9 @@ AudioTrack_GetTimestampPositionUs( JNIEnv *env, audio_output_t *p_aout )
 }
 
 static int
-TimeGet( audio_output_t *p_aout, vlc_tick_t *restrict p_delay )
+TimeGet( aout_stream_t *stream, vlc_tick_t *restrict p_delay )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     vlc_tick_t i_audiotrack_us;
     JNIEnv *env;
 
@@ -845,19 +796,19 @@ TimeGet( audio_output_t *p_aout, vlc_tick_t *restrict p_delay )
     if( p_sys->b_error || !p_sys->i_samples_written || !( env = GET_ENV() ) )
         goto bailout;
 
-    i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, p_aout );
+    i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, stream );
 
     if( i_audiotrack_us <= 0 )
-        i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, p_aout );
+        i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, stream );
 
 /* Debug log for both delays */
 #if 0
 {
-    vlc_tick_t i_ts_us = AudioTrack_GetTimestampPositionUs( env, p_aout );
-    vlc_tick_t i_smooth_us = AudioTrack_GetSmoothPositionUs(env, p_aout );
-    vlc_tick_t i_latency_us = AudioTrack_GetLatencyUs( env, p_aout );
+    vlc_tick_t i_ts_us = AudioTrack_GetTimestampPositionUs( env, stream );
+    vlc_tick_t i_smooth_us = AudioTrack_GetSmoothPositionUs(env, stream );
+    vlc_tick_t i_latency_us = AudioTrack_GetLatencyUs( env, stream );
 
-    msg_Err( p_aout, "TimeGet: TimeStamp: %"PRId64", Smooth: %"PRId64" (latency: %"PRId64")",
+    msg_Err( stream, "TimeGet: TimeStamp: %"PRId64", Smooth: %"PRId64" (latency: %"PRId64")",
              i_ts_us, i_smooth_us, i_latency_us );
 }
 #endif
@@ -878,8 +829,8 @@ TimeGet( audio_output_t *p_aout, vlc_tick_t *restrict p_delay )
         }
         else
         {
-            msg_Warn( p_aout, "timing screwed, reset positions" );
-            AudioTrack_ResetPositions( env, p_aout );
+            msg_Warn( stream, "timing screwed, reset positions" );
+            AudioTrack_ResetPositions( env, stream );
         }
     }
 
@@ -924,7 +875,7 @@ AudioTrack_GetChanOrder( uint16_t i_physical_channels, uint32_t p_chans_out[] )
 }
 
 static jobject
-AudioTrack_New21( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
+AudioTrack_New21( JNIEnv *env, aout_stream_t *stream, unsigned int i_rate,
                   int i_channel_config, int i_format, int i_size,
                   jint session_id )
 {
@@ -1006,11 +957,11 @@ del_local_refs:
 }
 
 static jobject
-AudioTrack_NewLegacy( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
+AudioTrack_NewLegacy( JNIEnv *env, aout_stream_t *stream, unsigned int i_rate,
                       int i_channel_config, int i_format, int i_size,
                       jint session_id )
 {
-    VLC_UNUSED( p_aout );
+    VLC_UNUSED( stream );
     return JNI_AT_NEW( jfields.AudioManager.STREAM_MUSIC, i_rate,
                        i_channel_config, i_format, i_size,
                        jfields.AudioTrack.MODE_STREAM, session_id );
@@ -1021,23 +972,23 @@ AudioTrack_NewLegacy( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
  * returns -1 on error, 0 on success.
  */
 static int
-AudioTrack_New( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
+AudioTrack_New( JNIEnv *env, aout_stream_t *stream, unsigned int i_rate,
                 int i_channel_config, int i_format, int i_size )
 {
-    aout_sys_t *p_sys = p_aout->sys;
-    jint session_id = var_InheritInteger( p_aout, "audiotrack-session-id" );
+    aout_sys_t *p_sys = stream->sys;
+    jint session_id = var_InheritInteger( stream, "audiotrack-session-id" );
 
     jobject p_audiotrack;
     if( jfields.AudioTrack.has_ctor_21 )
-        p_audiotrack = AudioTrack_New21( env, p_aout, i_rate, i_channel_config,
+        p_audiotrack = AudioTrack_New21( env, stream, i_rate, i_channel_config,
                                          i_format, i_size, session_id );
     else
-        p_audiotrack = AudioTrack_NewLegacy( env, p_aout, i_rate,
+        p_audiotrack = AudioTrack_NewLegacy( env, stream, i_rate,
                                              i_channel_config, i_format, i_size,
                                              session_id );
     if( CHECK_AT_EXCEPTION( "AudioTrack<init>" ) || !p_audiotrack )
     {
-        msg_Warn( p_aout, "AudioTrack Init failed" );
+        msg_Warn( stream, "AudioTrack Init failed" );
         return -1;
     }
     if( JNI_CALL_INT( p_audiotrack, jfields.AudioTrack.getState )
@@ -1045,7 +996,7 @@ AudioTrack_New( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
     {
         JNI_CALL_VOID( p_audiotrack, jfields.AudioTrack.release );
         (*env)->DeleteLocalRef( env, p_audiotrack );
-        msg_Err( p_aout, "AudioTrack getState failed" );
+        msg_Err( stream, "AudioTrack getState failed" );
         return -1;
     }
 
@@ -1054,22 +1005,13 @@ AudioTrack_New( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
     if( !p_sys->p_audiotrack )
         return -1;
 
-    if( jfields.DynamicsProcessing.clazz && !p_sys->b_passthrough )
+    if( !p_sys->b_passthrough )
     {
         if (session_id == 0 )
             session_id = JNI_AT_CALL_INT( getAudioSessionId );
 
         if( session_id != 0 )
-        {
-            jobject dp = JNI_CALL( NewObject, jfields.DynamicsProcessing.clazz,
-                                   jfields.DynamicsProcessing.ctor, session_id );
-
-            if( !CHECK_EXCEPTION( "DynamicsProcessing", "ctor" ) )
-            {
-                p_sys->p_dp = (*env)->NewGlobalRef( env, dp );
-                (*env)->DeleteLocalRef( env, dp );
-            }
-        }
+            p_sys->p_dp = DynamicsProcessing_New( stream, session_id );
     }
 
     return 0;
@@ -1080,24 +1022,24 @@ AudioTrack_New( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
  * returns -1 on error, 0 on success.
  */
 static int
-AudioTrack_Recreate( JNIEnv *env, audio_output_t *p_aout )
+AudioTrack_Recreate( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     JNI_AT_CALL_VOID( release );
     (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
     p_sys->p_audiotrack = NULL;
 
-    int ret = AudioTrack_New( env, p_aout, p_sys->audiotrack_args.i_rate,
+    int ret = AudioTrack_New( env, stream, p_sys->audiotrack_args.i_rate,
                               p_sys->audiotrack_args.i_channel_config,
                               p_sys->audiotrack_args.i_format,
                               p_sys->audiotrack_args.i_size );
 
     if( ret == 0 )
     {
-        p_aout->volume_set(p_aout, p_sys->volume);
+        stream->volume_set(stream, p_sys->volume);
         if (p_sys->mute)
-            p_aout->mute_set(p_aout, true);
+            stream->mute_set(stream, true);
     }
 
     return ret;
@@ -1108,12 +1050,12 @@ AudioTrack_Recreate( JNIEnv *env, audio_output_t *p_aout )
  * returns -1 on configuration error, 0 on success.
  */
 static int
-AudioTrack_Create( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_Create( JNIEnv *env, aout_stream_t *stream,
                    unsigned int i_rate,
                    int i_format,
                    uint16_t i_physical_channels )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     int i_size, i_min_buffer_size, i_channel_config;
 
     switch( i_physical_channels )
@@ -1142,7 +1084,7 @@ AudioTrack_Create( JNIEnv *env, audio_output_t *p_aout,
                                                 i_channel_config, i_format );
     if( i_min_buffer_size <= 0 )
     {
-        msg_Warn( p_aout, "getMinBufferSize returned an invalid size" ) ;
+        msg_Warn( stream, "getMinBufferSize returned an invalid size" ) ;
         return -1;
     }
     i_size = i_min_buffer_size * 2;
@@ -1154,7 +1096,7 @@ AudioTrack_Create( JNIEnv *env, audio_output_t *p_aout,
         i_size = i_max_size;
 
     /* create AudioTrack object */
-    if( AudioTrack_New( env, p_aout, i_rate, i_channel_config,
+    if( AudioTrack_New( env, stream, i_rate, i_channel_config,
                         i_format , i_size ) != 0 )
         return -1;
 
@@ -1166,13 +1108,11 @@ AudioTrack_Create( JNIEnv *env, audio_output_t *p_aout,
     return 0;
 }
 
-static bool
-AudioTrack_HasEncoding( audio_output_t *p_aout, vlc_fourcc_t i_format )
+bool
+AudioTrack_HasEncoding( long long encoding_flags, vlc_fourcc_t i_format )
 {
-    aout_sys_t *p_sys = p_aout->sys;
-
 #define MATCH_ENCODING_FLAG(x) jfields.AudioFormat.has_##x && \
-    ( p_sys->i_encoding_flags == 0 || p_sys->i_encoding_flags & (1 << jfields.AudioFormat.x) )
+    (encoding_flags == 0 || encoding_flags & (1 << jfields.AudioFormat.x) )
 
     switch( i_format )
     {
@@ -1188,7 +1128,7 @@ AudioTrack_HasEncoding( audio_output_t *p_aout, vlc_fourcc_t i_format )
         case VLC_CODEC_MLP:
             return MATCH_ENCODING_FLAG( ENCODING_DOLBY_TRUEHD );
         default:
-            return false;
+            return true;
     }
 }
 
@@ -1265,11 +1205,11 @@ static int GetPassthroughFmt( bool compat, audio_sample_format_t *fmt, int *at_f
 }
 
 static int
-StartPassthrough( JNIEnv *env, audio_output_t *p_aout )
+StartPassthrough( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
-    if( !AudioTrack_HasEncoding( p_aout, p_sys->fmt.i_format ) )
+    if( !AudioTrack_HasEncoding( p_sys->i_encoding_flags, p_sys->fmt.i_format ) )
         return VLC_EGENERIC;
 
     /* Try ENCODING_IEC61937 first, then fallback to ENCODING_[AC3|DTS|...] */
@@ -1286,12 +1226,12 @@ StartPassthrough( JNIEnv *env, audio_output_t *p_aout )
             return i_ret;
 
         p_sys->b_passthrough = true;
-        i_ret = AudioTrack_Create( env, p_aout, fmt.i_rate, i_at_format,
+        i_ret = AudioTrack_Create( env, stream, fmt.i_rate, i_at_format,
                                    fmt.i_physical_channels );
 
         if( i_ret == VLC_SUCCESS )
         {
-            msg_Dbg( p_aout, "Using passthrough format: %d", i_at_format );
+            msg_Dbg( stream, "Using passthrough format: %d", i_at_format );
             p_sys->i_chans_to_reorder = 0;
             p_sys->fmt = fmt;
             return VLC_SUCCESS;
@@ -1299,14 +1239,14 @@ StartPassthrough( JNIEnv *env, audio_output_t *p_aout )
     }
 
     p_sys->b_passthrough = false;
-    msg_Warn( p_aout, "SPDIF configuration failed" );
+    msg_Warn( stream, "SPDIF configuration failed" );
     return VLC_EGENERIC;
 }
 
 static int
-StartPCM( JNIEnv *env, audio_output_t *p_aout, unsigned i_max_channels )
+StartPCM( JNIEnv *env, aout_stream_t *stream, unsigned i_max_channels )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     unsigned i_nb_channels;
     int i_at_format, i_ret;
 
@@ -1370,19 +1310,19 @@ StartPCM( JNIEnv *env, audio_output_t *p_aout, unsigned i_max_channels )
          * format configuration. If AudioTrack_Create fails, try again with a
          * less advanced format (PCM S16N). If it fails again, try again with
          * Stereo channels. */
-        i_ret = AudioTrack_Create( env, p_aout, p_sys->fmt.i_rate, i_at_format,
+        i_ret = AudioTrack_Create( env, stream, p_sys->fmt.i_rate, i_at_format,
                                    p_sys->fmt.i_physical_channels );
         if( i_ret != 0 )
         {
             if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
             {
-                msg_Warn( p_aout, "FL32 configuration failed, "
+                msg_Warn( stream, "FL32 configuration failed, "
                                   "fallback to S16N PCM" );
                 p_sys->fmt.i_format = VLC_CODEC_S16N;
             }
             else if( p_sys->fmt.i_physical_channels & AOUT_CHANS_5_1 )
             {
-                msg_Warn( p_aout, "5.1 or 7.1 configuration failed, "
+                msg_Warn( stream, "5.1 or 7.1 configuration failed, "
                                   "fallback to Stereo" );
                 p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
             }
@@ -1406,31 +1346,43 @@ StartPCM( JNIEnv *env, audio_output_t *p_aout, unsigned i_max_channels )
 }
 
 static int
-Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
+Start( aout_stream_t *stream, audio_sample_format_t *restrict p_fmt,
+       enum android_audio_device_type adev )
 {
-    aout_sys_t *p_sys = p_aout->sys;
     JNIEnv *env;
     int i_ret;
     bool b_try_passthrough;
     unsigned i_max_channels;
 
-    if( p_sys->at_dev == AT_DEV_ENCODED )
+    if( adev == ANDROID_AUDIO_DEVICE_ENCODED )
     {
         b_try_passthrough = true;
-        i_max_channels = AT_DEV_MAX_CHANNELS;
+        i_max_channels = ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
     }
     else
     {
-        b_try_passthrough = var_InheritBool( p_aout, "spdif" );
-        i_max_channels = p_sys->at_dev == AT_DEV_STEREO ? 2 : AT_DEV_MAX_CHANNELS;
+        b_try_passthrough = false;
+        i_max_channels = adev == ANDROID_AUDIO_DEVICE_STEREO ? 2 : ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
     }
 
     if( !( env = GET_ENV() ) )
         return VLC_EGENERIC;
 
+    aout_sys_t *p_sys = stream->sys = calloc( 1, sizeof (aout_sys_t) );
+
+    if( unlikely( p_sys == NULL ) )
+        return VLC_ENOMEM;
+
+    vlc_mutex_init(&p_sys->lock);
+    vlc_cond_init(&p_sys->aout_cond);
+    vlc_cond_init(&p_sys->thread_cond);
+
+    p_sys->volume = 1.0f;
+    p_sys->mute = false;
+
     p_sys->fmt = *p_fmt;
 
-    aout_FormatPrint( p_aout, "VLC is looking for:", &p_sys->fmt );
+    aout_FormatPrint( stream, "VLC is looking for:", &p_sys->fmt );
 
     bool low_latency = false;
     if (p_sys->fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
@@ -1444,14 +1396,17 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
     }
 
     if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
-        i_ret = StartPCM( env, p_aout, i_max_channels );
+        i_ret = StartPCM( env, stream, i_max_channels );
     else if( b_try_passthrough )
-        i_ret = StartPassthrough( env, p_aout );
+        i_ret = StartPassthrough( env, stream );
     else
-        return VLC_EGENERIC;
+        i_ret = VLC_EGENERIC;
 
     if( i_ret != 0 )
+    {
+        free( p_sys );
         return VLC_EGENERIC;
+    }
 
     if( jfields.AudioTrack.getBufferSizeInFrames )
         p_sys->i_max_audiotrack_samples = JNI_AT_CALL_INT( getBufferSizeInFrames );
@@ -1474,32 +1429,32 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
     }
 #endif
 
-    AudioTrack_Reset( env, p_aout );
+    AudioTrack_Reset( env, stream );
 
     if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
     {
-        msg_Dbg( p_aout, "using WRITE_FLOATARRAY");
+        msg_Dbg( stream, "using WRITE_FLOATARRAY");
         p_sys->i_write_type = WRITE_FLOATARRAY;
     }
     else if( p_sys->fmt.i_format == VLC_CODEC_SPDIFL )
     {
         assert( jfields.AudioFormat.has_ENCODING_IEC61937 );
-        msg_Dbg( p_aout, "using WRITE_SHORTARRAYV23");
+        msg_Dbg( stream, "using WRITE_SHORTARRAYV23");
         p_sys->i_write_type = WRITE_SHORTARRAYV23;
     }
     else if( jfields.AudioTrack.writeV23 )
     {
-        msg_Dbg( p_aout, "using WRITE_BYTEARRAYV23");
+        msg_Dbg( stream, "using WRITE_BYTEARRAYV23");
         p_sys->i_write_type = WRITE_BYTEARRAYV23;
     }
     else if( jfields.AudioTrack.writeBufferV21 )
     {
-        msg_Dbg( p_aout, "using WRITE_BYTEBUFFER");
+        msg_Dbg( stream, "using WRITE_BYTEBUFFER");
         p_sys->i_write_type = WRITE_BYTEBUFFER;
     }
     else
     {
-        msg_Dbg( p_aout, "using WRITE_BYTEARRAY");
+        msg_Dbg( stream, "using WRITE_BYTEARRAY");
         p_sys->i_write_type = WRITE_BYTEARRAY;
     }
 
@@ -1535,7 +1490,7 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
 
             if( !p_sys->circular.u.p_bytearray )
             {
-                msg_Err(p_aout, "byte array allocation failed");
+                msg_Err(stream, "byte array allocation failed");
                 goto error;
             }
             break;
@@ -1553,7 +1508,7 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
             }
             if( !p_sys->circular.u.p_shortarray )
             {
-                msg_Err(p_aout, "short array allocation failed");
+                msg_Err(stream, "short array allocation failed");
                 goto error;
             }
             break;
@@ -1571,7 +1526,7 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
             }
             if( !p_sys->circular.u.p_floatarray )
             {
-                msg_Err(p_aout, "float array allocation failed");
+                msg_Err(stream, "float array allocation failed");
                 goto error;
             }
             break;
@@ -1580,7 +1535,7 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
             p_sys->circular.u.bytebuffer.p_data = malloc( p_sys->circular.i_size );
             if( !p_sys->circular.u.bytebuffer.p_data )
             {
-                msg_Err(p_aout, "bytebuffer allocation failed");
+                msg_Err(stream, "bytebuffer allocation failed");
                 goto error;
             }
             break;
@@ -1589,9 +1544,9 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
     /* Run AudioTrack_Thread */
     p_sys->b_thread_running = true;
     p_sys->b_thread_paused = false;
-    if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, p_aout ) )
+    if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, stream ) )
     {
-        msg_Err(p_aout, "vlc clone failed");
+        msg_Err(stream, "vlc clone failed");
         goto error;
     }
 
@@ -1600,22 +1555,28 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
 
     *p_fmt = p_sys->fmt;
 
-    p_aout->volume_set(p_aout, p_sys->volume);
-    if (p_sys->mute)
-        p_aout->mute_set(p_aout, true);
-    aout_FormatPrint( p_aout, "VLC will output:", &p_sys->fmt );
+    aout_FormatPrint( stream, "VLC will output:", &p_sys->fmt );
 
+    stream->stop = Stop;
+    stream->play = Play;
+    stream->pause = Pause;
+    stream->flush = Flush;
+    stream->time_get = TimeGet;
+    stream->volume_set = VolumeSet;
+    stream->mute_set = MuteSet;
+
+    msg_Dbg(stream, "using AudioTrack API");
     return VLC_SUCCESS;
 
 error:
-    Stop( p_aout );
+    Stop( stream );
     return VLC_EGENERIC;
 }
 
 static void
-Stop( audio_output_t *p_aout )
+Stop( aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env;
 
     if( !( env = GET_ENV() ) )
@@ -1643,21 +1604,14 @@ Stop( audio_output_t *p_aout )
                 JNI_AT_CALL_VOID( release );
         }
         (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
-        p_sys->p_audiotrack = NULL;
     }
 
-    if( p_sys->p_dp )
-    {
-        (*env)->DeleteGlobalRef( env, p_sys->p_dp );
-        p_sys->p_dp = NULL;
-    }
+    if( p_sys->p_dp != NULL )
+        DynamicsProcessing_Delete( stream, p_sys->p_dp );
 
     /* Release the timestamp object */
     if( p_sys->timestamp.p_obj )
-    {
         (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
-        p_sys->timestamp.p_obj = NULL;
-    }
 
     /* Release the Circular buffer data */
     switch( p_sys->i_write_type )
@@ -1665,34 +1619,22 @@ Stop( audio_output_t *p_aout )
     case WRITE_BYTEARRAY:
     case WRITE_BYTEARRAYV23:
         if( p_sys->circular.u.p_bytearray )
-        {
             (*env)->DeleteGlobalRef( env, p_sys->circular.u.p_bytearray );
-            p_sys->circular.u.p_bytearray = NULL;
-        }
         break;
     case WRITE_SHORTARRAYV23:
         if( p_sys->circular.u.p_shortarray )
-        {
             (*env)->DeleteGlobalRef( env, p_sys->circular.u.p_shortarray );
-            p_sys->circular.u.p_shortarray = NULL;
-        }
         break;
     case WRITE_FLOATARRAY:
         if( p_sys->circular.u.p_floatarray )
-        {
             (*env)->DeleteGlobalRef( env, p_sys->circular.u.p_floatarray );
-            p_sys->circular.u.p_floatarray = NULL;
-        }
         break;
     case WRITE_BYTEBUFFER:
         free( p_sys->circular.u.bytebuffer.p_data );
-        p_sys->circular.u.bytebuffer.p_data = NULL;
         break;
     }
 
-    p_sys->b_audiotrack_exception = false;
-    p_sys->b_error = false;
-    p_sys->b_passthrough = false;
+    free( p_sys );
 }
 
 /**
@@ -1701,21 +1643,21 @@ Stop( audio_output_t *p_aout )
  * that we won't wait in AudioTrack.write() method.
  */
 static int
-AudioTrack_WriteByteArray( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_WriteByteArray( JNIEnv *env, aout_stream_t *stream,
                            size_t i_data_size, size_t i_data_offset,
                            bool b_force )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     uint64_t i_samples;
     uint64_t i_audiotrack_pos;
     uint64_t i_samples_pending;
 
-    i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, p_aout );
+    i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, stream );
 
     assert( i_audiotrack_pos <= p_sys->i_samples_written );
     if( i_audiotrack_pos > p_sys->i_samples_written )
     {
-        msg_Err( p_aout, "audiotrack position is ahead. Should NOT happen" );
+        msg_Err( stream, "audiotrack position is ahead. Should NOT happen" );
         p_sys->i_samples_written = 0;
         p_sys->b_error = true;
         return 0;
@@ -1725,7 +1667,7 @@ AudioTrack_WriteByteArray( JNIEnv *env, audio_output_t *p_aout,
     /* check if audiotrack buffer is not full before writing on it. */
     if( b_force )
     {
-        msg_Warn( p_aout, "Force write. It may block..." );
+        msg_Warn( stream, "Force write. It may block..." );
         i_samples_pending = 0;
     } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
         return 0;
@@ -1745,10 +1687,10 @@ AudioTrack_WriteByteArray( JNIEnv *env, audio_output_t *p_aout,
  * flags.
  */
 static int
-AudioTrack_WriteByteArrayV23( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_WriteByteArrayV23( JNIEnv *env, aout_stream_t *stream,
                               size_t i_data_size, size_t i_data_offset )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     return JNI_AT_CALL_INT( writeV23, p_sys->circular.u.p_bytearray,
                             i_data_offset, i_data_size,
@@ -1761,10 +1703,10 @@ AudioTrack_WriteByteArrayV23( JNIEnv *env, audio_output_t *p_aout,
  * flags.
  */
 static int
-AudioTrack_WriteByteBuffer( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_WriteByteBuffer( JNIEnv *env, aout_stream_t *stream,
                             size_t i_data_size, size_t i_data_offset )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     /* The same DirectByteBuffer will be used until the data_offset reaches 0.
      * The internal position of this buffer is moved by the writeBufferV21
@@ -1798,10 +1740,10 @@ AudioTrack_WriteByteBuffer( JNIEnv *env, audio_output_t *p_aout,
  * flags.
  */
 static int
-AudioTrack_WriteShortArrayV23( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_WriteShortArrayV23( JNIEnv *env, aout_stream_t *stream,
                                size_t i_data_size, size_t i_data_offset )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     int i_ret;
 
     i_ret = JNI_AT_CALL_INT( writeShortV23, p_sys->circular.u.p_shortarray,
@@ -1819,10 +1761,10 @@ AudioTrack_WriteShortArrayV23( JNIEnv *env, audio_output_t *p_aout,
  * flags.
  */
 static int
-AudioTrack_WriteFloatArray( JNIEnv *env, audio_output_t *p_aout,
+AudioTrack_WriteFloatArray( JNIEnv *env, aout_stream_t *stream,
                             size_t i_data_size, size_t i_data_offset )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     int i_ret;
 
     i_ret = JNI_AT_CALL_INT( writeFloat, p_sys->circular.u.p_floatarray,
@@ -1835,32 +1777,32 @@ AudioTrack_WriteFloatArray( JNIEnv *env, audio_output_t *p_aout,
 }
 
 static int
-AudioTrack_Write( JNIEnv *env, audio_output_t *p_aout, size_t i_data_size,
+AudioTrack_Write( JNIEnv *env, aout_stream_t *stream, size_t i_data_size,
                   size_t i_data_offset, bool b_force )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     int i_ret;
 
     switch( p_sys->i_write_type )
     {
     case WRITE_BYTEARRAYV23:
-        i_ret = AudioTrack_WriteByteArrayV23( env, p_aout, i_data_size,
+        i_ret = AudioTrack_WriteByteArrayV23( env, stream, i_data_size,
                                               i_data_offset );
         break;
     case WRITE_BYTEBUFFER:
-        i_ret = AudioTrack_WriteByteBuffer( env, p_aout, i_data_size,
+        i_ret = AudioTrack_WriteByteBuffer( env, stream, i_data_size,
                                             i_data_offset );
         break;
     case WRITE_SHORTARRAYV23:
-        i_ret = AudioTrack_WriteShortArrayV23( env, p_aout, i_data_size,
+        i_ret = AudioTrack_WriteShortArrayV23( env, stream, i_data_size,
                                                i_data_offset );
         break;
     case WRITE_BYTEARRAY:
-        i_ret = AudioTrack_WriteByteArray( env, p_aout, i_data_size,
+        i_ret = AudioTrack_WriteByteArray( env, stream, i_data_size,
                                            i_data_offset, b_force );
         break;
     case WRITE_FLOATARRAY:
-        i_ret = AudioTrack_WriteFloatArray( env, p_aout, i_data_size,
+        i_ret = AudioTrack_WriteFloatArray( env, stream, i_data_size,
                                             i_data_offset );
         break;
     default:
@@ -1871,11 +1813,11 @@ AudioTrack_Write( JNIEnv *env, audio_output_t *p_aout, size_t i_data_size,
         if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
             && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
         {
-            msg_Warn( p_aout, "ERROR_DEAD_OBJECT: "
+            msg_Warn( stream, "ERROR_DEAD_OBJECT: "
                               "try recreating AudioTrack" );
-            if( ( i_ret = AudioTrack_Recreate( env, p_aout ) ) == 0 )
+            if( ( i_ret = AudioTrack_Recreate( env, stream ) ) == 0 )
             {
-                AudioTrack_Reset( env, p_aout );
+                AudioTrack_Reset( env, stream );
                 JNI_AT_CALL_VOID( play );
                 CHECK_AT_EXCEPTION( "play" );
             }
@@ -1888,7 +1830,7 @@ AudioTrack_Write( JNIEnv *env, audio_output_t *p_aout, size_t i_data_size,
                 str = "ERROR_BAD_VALUE";
             else
                 str = "ERROR";
-            msg_Err( p_aout, "Write failed: %s", str );
+            msg_Err( stream, "Write failed: %s", str );
             p_sys->b_error = true;
         }
     } else
@@ -1902,8 +1844,8 @@ AudioTrack_Write( JNIEnv *env, audio_output_t *p_aout, size_t i_data_size,
 static void *
 AudioTrack_Thread( void *p_data )
 {
-    audio_output_t *p_aout = p_data;
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_stream_t *stream = p_data;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env = GET_ENV();
     vlc_tick_t i_last_time_blocked = 0;
 
@@ -1955,7 +1897,7 @@ AudioTrack_Thread( void *p_data )
         i_data_size = __MIN( p_sys->circular.i_size - i_data_offset,
                              p_sys->circular.i_write - p_sys->circular.i_read );
 
-        i_ret = AudioTrack_Write( env, p_aout, i_data_size, i_data_offset,
+        i_ret = AudioTrack_Write( env, stream, i_data_size, i_data_offset,
                                   b_forced );
         if( i_ret >= 0 )
         {
@@ -1997,7 +1939,7 @@ AudioTrack_Thread( void *p_data )
 }
 
 static int
-ConvertFromIEC61937( audio_output_t *p_aout, block_t *p_buffer )
+ConvertFromIEC61937( aout_stream_t *stream, block_t *p_buffer )
 {
     /* This function is only used for Android API 23 when AudioTrack is
      * configured with ENCODING_ AC3/E_AC3/DTS. In that case, only the codec
@@ -2007,7 +1949,7 @@ ConvertFromIEC61937( audio_output_t *p_aout, block_t *p_buffer )
      * send us the codec data directly, but in that case, we wouldn't benefit
      * from the eac3 block merger of tospdif.c. */
 
-    VLC_UNUSED( p_aout );
+    VLC_UNUSED( stream );
     uint8_t i_length_mul;
 
     if( p_buffer->i_buffer < 6 )
@@ -2047,14 +1989,14 @@ ConvertFromIEC61937( audio_output_t *p_aout, block_t *p_buffer )
 }
 
 static void
-Play( audio_output_t *p_aout, block_t *p_buffer, vlc_tick_t i_date )
+Play( aout_stream_t *stream, block_t *p_buffer, vlc_tick_t i_date )
 {
     JNIEnv *env = NULL;
     size_t i_buffer_offset = 0;
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
-     && ConvertFromIEC61937( p_aout, p_buffer ) != 0 )
+     && ConvertFromIEC61937( stream, p_buffer ) != 0 )
     {
         block_Release(p_buffer);
         return;
@@ -2133,9 +2075,9 @@ bailout:
 }
 
 static void
-Pause( audio_output_t *p_aout, bool b_pause, vlc_tick_t i_date )
+Pause( aout_stream_t *stream, bool b_pause, vlc_tick_t i_date )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env;
     VLC_UNUSED( i_date );
 
@@ -2152,7 +2094,7 @@ Pause( audio_output_t *p_aout, bool b_pause, vlc_tick_t i_date )
     } else
     {
         p_sys->b_thread_paused = false;
-        AudioTrack_ResetPositions( env, p_aout );
+        AudioTrack_ResetPositions( env, stream );
         JNI_AT_CALL_VOID( play );
         CHECK_AT_EXCEPTION( "play" );
     }
@@ -2162,9 +2104,9 @@ bailout:
 }
 
 static void
-Flush( audio_output_t *p_aout )
+Flush( aout_stream_t *stream )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env;
 
     vlc_mutex_lock( &p_sys->lock );
@@ -2197,13 +2139,13 @@ Flush( audio_output_t *p_aout )
      * Version is 4.3 or before */
     if( !jfields.AudioTimestamp.clazz && p_sys->i_samples_written > 0 )
     {
-        if( AudioTrack_Recreate( env, p_aout ) != 0 )
+        if( AudioTrack_Recreate( env, stream ) != 0 )
         {
             p_sys->b_error = true;
             goto bailout;
         }
     }
-    AudioTrack_Reset( env, p_aout );
+    AudioTrack_Reset( env, stream );
     JNI_AT_CALL_VOID( play );
     CHECK_AT_EXCEPTION( "play" );
 
@@ -2212,9 +2154,9 @@ bailout:
 }
 
 static void
-AudioTrack_SetVolume( JNIEnv *env, audio_output_t *p_aout, float volume )
+AudioTrack_SetVolume( JNIEnv *env, aout_stream_t *stream, float volume )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
 
     if( jfields.AudioTrack.setVolume )
     {
@@ -2227,10 +2169,10 @@ AudioTrack_SetVolume( JNIEnv *env, audio_output_t *p_aout, float volume )
     }
 }
 
-static int
-VolumeSet( audio_output_t *p_aout, float volume )
+static void
+VolumeSet( aout_stream_t *stream, float volume )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env;
     float gain = 1.0f;
 
@@ -2243,7 +2185,7 @@ VolumeSet( audio_output_t *p_aout, float volume )
 
     if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
     {
-        AudioTrack_SetVolume( env, p_aout, volume );
+        AudioTrack_SetVolume( env, stream, volume );
 
         /* Apply gain > 1.0f via DynamicsProcessing if possible */
         if( p_sys->p_dp != NULL )
@@ -2252,140 +2194,40 @@ VolumeSet( audio_output_t *p_aout, float volume )
             {
                 /* DynamicsProcessing is not needed anymore (using AudioTrack
                  * volume) */
-                JNI_CALL_INT( p_sys->p_dp, jfields.DynamicsProcessing.setEnabled, false );
-                CHECK_EXCEPTION( "DynamicsProcessing", "setEnabled" );
+                DynamicsProcessing_Disable( stream, p_sys->p_dp );
             }
             else
             {
-                /* convert linear gain to dB */
-                float dB = 20.0f * log10f(gain);
-
-                JNI_CALL_VOID( p_sys->p_dp, jfields.DynamicsProcessing.setInputGainAllChannelsTo, dB );
-                int ret = JNI_CALL_INT( p_sys->p_dp, jfields.DynamicsProcessing.setEnabled, true );
+                int ret = DynamicsProcessing_SetVolume( stream, p_sys->p_dp, gain );
 
-                if( !CHECK_EXCEPTION( "DynamicsProcessing", "setEnabled" ) && ret == 0 )
+                if( ret == VLC_SUCCESS )
                     gain = 1.0; /* reset sw gain */
                 else
-                    msg_Warn( p_aout, "failed to set gain via DynamicsProcessing, fallback to sw gain");
+                    msg_Warn( stream, "failed to set gain via DynamicsProcessing, fallback to sw gain");
             }
         }
     }
 
-    aout_VolumeReport(p_aout, p_sys->volume);
-    aout_GainRequest(p_aout, gain);
-    return 0;
+    aout_stream_GainRequest(stream, gain);
 }
 
-static int
-MuteSet( audio_output_t *p_aout, bool mute )
+static void
+MuteSet( aout_stream_t *stream, bool mute )
 {
-    aout_sys_t *p_sys = p_aout->sys;
+    aout_sys_t *p_sys = stream->sys;
     JNIEnv *env;
     p_sys->mute = mute;
 
     if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
-        AudioTrack_SetVolume( env, p_aout, mute ? 0.0f : p_sys->volume );
-
-    aout_MuteReport(p_aout, mute);
-    return 0;
-}
-
-static int DeviceSelect(audio_output_t *p_aout, const char *p_id)
-{
-    aout_sys_t *p_sys = p_aout->sys;
-    enum at_dev at_dev = AT_DEV_DEFAULT;
-
-    if( p_id )
-    {
-        for( unsigned int i = 0; at_devs[i].id; ++i )
-        {
-            if( strncmp( p_id, at_devs[i].id, strlen( at_devs[i].id ) ) == 0 )
-            {
-                at_dev = at_devs[i].at_dev;
-                break;
-            }
-        }
-    }
-
-    long long i_encoding_flags = 0;
-    if( at_dev == AT_DEV_ENCODED )
-    {
-        const size_t i_prefix_size = strlen( "encoded:" );
-        if( strncmp( p_id, "encoded:", i_prefix_size ) == 0 )
-            i_encoding_flags = atoll( p_id + i_prefix_size );
-    }
-
-    if( at_dev != p_sys->at_dev || i_encoding_flags != p_sys->i_encoding_flags )
-    {
-        p_sys->at_dev = at_dev;
-        p_sys->i_encoding_flags = i_encoding_flags;
-        aout_RestartRequest( p_aout, AOUT_RESTART_OUTPUT );
-        msg_Dbg( p_aout, "selected device: %s", p_id );
-
-        if( at_dev == AT_DEV_ENCODED )
-        {
-            static const vlc_fourcc_t enc_fourccs[] = {
-                VLC_CODEC_DTS, VLC_CODEC_DTSHD, VLC_CODEC_A52, VLC_CODEC_EAC3,
-                VLC_CODEC_TRUEHD,
-            };
-            for( size_t i = 0;
-                 i < sizeof( enc_fourccs ) / sizeof( enc_fourccs[0] ); ++i )
-            {
-                if( AudioTrack_HasEncoding( p_aout, enc_fourccs[i] ) )
-                    msg_Dbg( p_aout, "device has %4.4s passthrough support",
-                             (const char *)&enc_fourccs[i] );
-            }
-        }
-    }
-    aout_DeviceReport( p_aout, p_id );
-    return VLC_SUCCESS;
+        AudioTrack_SetVolume( env, stream, mute ? 0.0f : p_sys->volume );
 }
 
-static int
-Open( vlc_object_t *obj )
-{
-    audio_output_t *p_aout = (audio_output_t *) obj;
-    aout_sys_t *p_sys;
-    JNIEnv *env = GET_ENV();
-
-    if( !env || !InitJNIFields( p_aout, env ) )
-        return VLC_EGENERIC;
-
-    p_sys = calloc( 1, sizeof (aout_sys_t) );
-
-    if( unlikely( p_sys == NULL ) )
-        return VLC_ENOMEM;
-
-    p_sys->at_dev = AT_DEV_DEFAULT;
-    vlc_mutex_init(&p_sys->lock);
-    vlc_cond_init(&p_sys->aout_cond);
-    vlc_cond_init(&p_sys->thread_cond);
-
-    p_aout->sys = p_sys;
-    p_aout->start = Start;
-    p_aout->stop = Stop;
-    p_aout->play = Play;
-    p_aout->pause = Pause;
-    p_aout->flush = Flush;
-    p_aout->time_get = TimeGet;
-    p_aout->device_select = DeviceSelect;
-
-    for( unsigned int i = 0; at_devs[i].id; ++i )
-        aout_HotplugReport(p_aout, at_devs[i].id, at_devs[i].name);
-
-    p_aout->volume_set = VolumeSet;
-    p_aout->mute_set = MuteSet;
-    p_sys->volume = 1.0f;
-    p_sys->mute = false;
-
-    return VLC_SUCCESS;
-}
+vlc_module_begin ()
+    set_shortname("AudioTrack")
+    set_description("Android AudioTrack audio output")
 
-static void
-Close( vlc_object_t *obj )
-{
-    audio_output_t *p_aout = (audio_output_t *) obj;
-    aout_sys_t *p_sys = p_aout->sys;
+    set_subcategory(SUBCAT_AUDIO_AOUT)
 
-    free( p_sys );
-}
+    set_capability("aout android stream", 180)
+    set_callback(Start)
+vlc_module_end ()


=====================================
modules/audio_output/android/device.c
=====================================
@@ -0,0 +1,405 @@
+/*****************************************************************************
+ * android/device.c: Android AudioTrack/AAudio device handler
+ *****************************************************************************
+ * Copyright © 2012-2022 VLC authors and VideoLAN, VideoLabs
+ *
+ * Authors: 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_modules.h>
+#include <vlc_aout.h>
+#include "device.h"
+#include "../video_output/android/utils.h"
+
+/* There is an undefined behavior when configuring AudioTrack with SPDIF or
+ * more than 2 channels when there is no HDMI out. It may succeed and the
+ * Android ressampler will be used to downmix to stereo. It may fails cleanly,
+ * and this module will be able to recover and fallback to stereo. Finally, in
+ * some rare cases, it may crash during init or while ressampling. Because of
+ * the last case we don't try up to 8 channels and we use
+ * ANDROID_AUDIO_DEVICE_STEREO device per default */
+#define ANDROID_AUDIO_DEVICE_DEFAULT ANDROID_AUDIO_DEVICE_STEREO
+
+static const struct {
+    const char *id;
+    const char *name;
+    enum android_audio_device_type adev;
+} adevs[] = {
+    { "stereo", "Up to 2 channels (compat mode).", ANDROID_AUDIO_DEVICE_STEREO },
+    { "pcm", "Up to 8 channels.", ANDROID_AUDIO_DEVICE_PCM },
+
+    /* With "encoded", the module will try to play every audio codecs via
+     * passthrough.
+     *
+     * With "encoded:ENCODING_FLAGS_MASK", the module will try to play only
+     * codecs specified by ENCODING_FLAGS_MASK. This extra value is a long long
+     * that contains binary-shifted AudioFormat.ENCODING_* values. */
+    { "encoded", "Up to 8 channels, passthrough if available.", ANDROID_AUDIO_DEVICE_ENCODED },
+    {  NULL, NULL, ANDROID_AUDIO_DEVICE_DEFAULT },
+};
+
+static struct DynamicsProcessing_fields dp_fields;
+
+struct sys {
+    aout_stream_t *stream;
+
+    enum android_audio_device_type adev;
+    long long encoding_flags;
+
+    bool mute;
+    float volume;
+};
+
+static void
+Drain(audio_output_t *aout)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+    assert(sys->stream->drain != NULL);
+
+    sys->stream->drain(sys->stream);
+}
+
+static int
+TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+
+    return sys->stream->time_get(sys->stream, delay);
+}
+
+static int
+Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
+{
+    struct sys *sys = aout->sys;
+
+    if (!AudioTrack_HasEncoding(sys->encoding_flags, fmt->i_format))
+        return VLC_EGENERIC;
+
+    aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
+    if (unlikely(s == NULL))
+        return VLC_EGENERIC;
+    s->aout = aout;
+
+    /* There is only one "aout" module for android, that take care of choosing
+     * the best API by default (AAudio, AudioTrack, OpenSLES). This is needed
+     * because AAudio, that is the best API to use, doesn't handle pass-through
+     * while AudioTrack can. Therefore, this intermediate "aout" module allow
+     * choosing AAudio for PCM and AudioTrack for pass-through. The user is
+     * still able to force an API via LibVLC because the "aout" choice is
+     * forwarded to the "aout android stream" module probe. */
+    char *modlist = var_InheritString(aout, "aout");
+    module_t **mods;
+    ssize_t total = vlc_module_match("aout android stream", modlist, false, &mods, NULL);
+    int ret = VLC_EGENERIC;
+    for (ssize_t i = 0; i < total; i++)
+    {
+        aout_stream_start start = vlc_module_map(vlc_object_logger(aout), mods[i]);
+        if (start == NULL)
+            continue;
+        ret = start(s, fmt, sys->adev);
+        if (ret == VLC_SUCCESS)
+        {
+            sys->stream = s;
+
+            assert(s->stop != NULL && s->play != NULL &&
+                   s->pause != NULL && s->flush != NULL);
+
+            aout->drain = s->drain != NULL ? Drain : NULL;
+            aout->time_get = s->time_get != NULL ? TimeGet : NULL;
+
+            if (s->volume_set != NULL)
+                s->volume_set(s, sys->volume);
+            if (s->mute_set != NULL && sys->mute)
+                s->mute_set(s, true);
+            break;
+        }
+    }
+
+    free(modlist);
+    free(mods);
+
+    return ret;
+}
+
+static void
+Stop(audio_output_t *aout)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+
+    sys->stream->stop(sys->stream);
+
+    vlc_object_delete(sys->stream);
+    sys->stream = NULL;
+}
+
+static void
+Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+
+    sys->stream->play(sys->stream, block, date);
+}
+
+static void
+Pause(audio_output_t *aout, bool paused, vlc_tick_t date)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+
+    sys->stream->pause(sys->stream, paused, date);
+}
+
+static void
+Flush(audio_output_t *aout)
+{
+    struct sys *sys = aout->sys;
+    assert(sys->stream != NULL);
+
+    sys->stream->flush(sys->stream);
+}
+
+static int
+VolumeSet(audio_output_t *aout, float vol)
+{
+    struct sys *sys = aout->sys;
+
+    sys->volume = vol;
+    if (sys->stream != NULL && sys->stream->volume_set != NULL)
+        sys->stream->volume_set(sys->stream, vol);
+
+    aout_VolumeReport(aout, vol);
+    return 0;
+}
+
+static int
+MuteSet(audio_output_t *aout, bool mute)
+{
+    struct sys *sys = aout->sys;
+
+    sys->mute = mute;
+    if (sys->stream != NULL && sys->stream->mute_set != NULL)
+        sys->stream->mute_set(sys->stream, mute);
+
+    aout_MuteReport(aout, mute);
+    return 0;
+}
+
+static int DeviceSelect(audio_output_t *aout, const char *id)
+{
+    struct sys *sys = aout->sys;
+    enum android_audio_device_type adev = ANDROID_AUDIO_DEVICE_DEFAULT;
+
+    if (id)
+    {
+        for (unsigned int i = 0; adevs[i].id; ++i)
+        {
+            if (strncmp(id, adevs[i].id, strlen(adevs[i].id))== 0)
+            {
+                adev = adevs[i].adev;
+                break;
+            }
+        }
+    }
+
+    long long encoding_flags = 0;
+    if (adev == ANDROID_AUDIO_DEVICE_ENCODED)
+    {
+        const size_t prefix_size = strlen("encoded:");
+        if (strncmp(id, "encoded:", prefix_size)== 0)
+            encoding_flags = atoll(id + prefix_size);
+    }
+
+    if (adev != sys->adev || encoding_flags != sys->encoding_flags)
+    {
+        sys->adev = adev;
+        sys->encoding_flags = encoding_flags;
+        aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
+        msg_Dbg(aout, "selected device: %s", id);
+
+        if (adev == ANDROID_AUDIO_DEVICE_ENCODED)
+        {
+            static const vlc_fourcc_t enc_fourccs[] = {
+                VLC_CODEC_DTS, VLC_CODEC_DTSHD, VLC_CODEC_A52, VLC_CODEC_EAC3,
+                VLC_CODEC_TRUEHD,
+            };
+            for (size_t i = 0;
+                 i < sizeof(enc_fourccs)/ sizeof(enc_fourccs[0]); ++i)
+            {
+                if (AudioTrack_HasEncoding(sys->encoding_flags, enc_fourccs[i]))
+                    msg_Dbg(aout, "device has %4.4s passthrough support",
+                             (const char *)&enc_fourccs[i]);
+            }
+        }
+    }
+    aout_DeviceReport(aout, id);
+    return VLC_SUCCESS;
+}
+
+static int
+Open(vlc_object_t *obj)
+{
+    audio_output_t *aout = (audio_output_t *)obj;
+
+    int ret = AudioTrack_InitJNI(aout, &dp_fields);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    struct sys *sys = aout->sys = vlc_obj_malloc(obj, sizeof(*sys));
+    if (sys == NULL)
+        return VLC_ENOMEM;
+
+    sys->adev = ANDROID_AUDIO_DEVICE_DEFAULT;
+    sys->encoding_flags = 0;
+    sys->volume = 1.f;
+    sys->mute = false;
+
+    aout->start = Start;
+    aout->stop = Stop;
+    aout->play = Play;
+    aout->pause = Pause;
+    aout->flush = Flush;
+    aout->drain = NULL;
+    aout->device_select = DeviceSelect;
+    aout->volume_set = VolumeSet;
+    aout->mute_set = MuteSet;
+
+    for (unsigned int i = 0; adevs[i].id; ++i)
+        aout_HotplugReport(aout, adevs[i].id, adevs[i].name);
+
+    if (var_InheritBool(aout, "spdif"))
+        DeviceSelect(aout, "encoded");
+
+    return VLC_SUCCESS;
+}
+
+#define THREAD_NAME "android_audio"
+#define GET_ENV() android_getEnv( VLC_OBJECT(stream), THREAD_NAME )
+#define JNI_CALL( what, obj, method, ... ) (*env)->what( env, obj, method, ##__VA_ARGS__ )
+#define JNI_CALL_INT( obj, method, ... ) JNI_CALL( CallIntMethod, obj, method, ##__VA_ARGS__ )
+#define JNI_CALL_VOID( obj, method, ... ) JNI_CALL( CallVoidMethod, obj, method, ##__VA_ARGS__ )
+static inline bool
+check_exception( JNIEnv *env, aout_stream_t *stream,
+                 const char *class, const char *method )
+{
+    if( (*env)->ExceptionCheck( env ) )
+    {
+        (*env)->ExceptionDescribe( env );
+        (*env)->ExceptionClear( env );
+        msg_Err( stream, "%s.%s triggered an exception !", class, method );
+        return true;
+    } else
+        return false;
+}
+#define CHECK_EXCEPTION( class, method ) check_exception( env, stream, class, method )
+
+jobject
+DynamicsProcessing_New( aout_stream_t *stream, int session_id )
+{
+    JNIEnv *env;
+    if( !( env = GET_ENV() ) )
+        return NULL;
+
+    if( !dp_fields.clazz )
+        return NULL;
+
+    jobject dp = JNI_CALL( NewObject, dp_fields.clazz,
+                           dp_fields.ctor, session_id );
+
+    if( CHECK_EXCEPTION( "DynamicsProcessing", "ctor" ) )
+        return NULL;
+
+    jobject global_dp = (*env)->NewGlobalRef( env, dp );
+    (*env)->DeleteLocalRef( env, dp );
+
+    return global_dp;
+}
+
+void
+DynamicsProcessing_Disable( aout_stream_t *stream, jobject dp )
+{
+    JNIEnv *env;
+    if( !( env = GET_ENV() ) )
+        return;
+
+    JNI_CALL_INT( dp, dp_fields.setEnabled, false );
+    CHECK_EXCEPTION( "DynamicsProcessing", "setEnabled" );
+}
+
+int
+DynamicsProcessing_SetVolume( aout_stream_t *stream, jobject dp, float volume )
+{
+    JNIEnv *env;
+    if( !( env = GET_ENV() ) )
+        return VLC_EGENERIC;
+
+    /* convert linear gain to dB */
+    float dB = volume == 0.0f ? -144 : 20.0f * log10f( volume );
+
+    JNI_CALL_VOID( dp, dp_fields.setInputGainAllChannelsTo, dB );
+    int ret = JNI_CALL_INT( dp, dp_fields.setEnabled, volume != 1.0f );
+
+    if( CHECK_EXCEPTION( "DynamicsProcessing", "setEnabled" ) || ret != 0 )
+        return VLC_EGENERIC;
+
+    return VLC_SUCCESS;
+}
+
+void
+DynamicsProcessing_Delete( aout_stream_t *stream, jobject dp )
+{
+    JNIEnv *env;
+    if( !( env = GET_ENV() ) )
+        return;
+
+    JNI_CALL_INT( dp, dp_fields.setEnabled, false );
+    CHECK_EXCEPTION( "DynamicsProcessing", "setEnabled" );
+
+    (*env)->DeleteGlobalRef( env, dp );
+}
+
+#define add_aout(shortcut, name, desc) \
+    add_submodule() \
+        add_shortcut(name) \
+        set_shortname(name) \
+        set_description(desc) \
+        set_capability("audio output", 0) \
+        set_callback(Open)
+
+#define AUDIOTRACK_SESSION_ID_TEXT " Id of audio session the AudioTrack must be attached to"
+
+vlc_module_begin ()
+    set_shortname("Android Audio")
+    set_description("Android automatic audio output")
+    set_capability("audio output", 200)
+    set_subcategory(SUBCAT_AUDIO_AOUT)
+    add_integer("audiotrack-session-id", 0,
+            AUDIOTRACK_SESSION_ID_TEXT, NULL )
+        change_private()
+    set_callback(Open)
+    add_aout("audiotrack", "AudioTrack", "Android AudioTrack audio output")
+    add_aout("aaudio", "AAudio", "Android AAudio output")
+vlc_module_end ()


=====================================
modules/audio_output/android/device.h
=====================================
@@ -0,0 +1,105 @@
+/*****************************************************************************
+ * android/device.h: Android AudioTrack/AAudio device handler
+ *****************************************************************************
+ * Copyright © 2012-2022 VLC authors and VideoLAN, VideoLabs
+ *
+ * Authors: 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.
+ *****************************************************************************/
+
+#include <jni.h>
+
+typedef struct aout_stream aout_stream_t;
+
+enum android_audio_device_type
+{
+    ANDROID_AUDIO_DEVICE_STEREO = 0,
+    ANDROID_AUDIO_DEVICE_PCM,
+    ANDROID_AUDIO_DEVICE_ENCODED,
+};
+#define ANDROID_AUDIO_DEVICE_MAX_CHANNELS 8
+
+struct DynamicsProcessing_fields
+{
+    jclass clazz;
+    jmethodID ctor;
+    jmethodID setInputGainAllChannelsTo;
+    jmethodID setEnabled;
+};
+
+int
+AudioTrack_InitJNI(audio_output_t *aout,
+                   struct DynamicsProcessing_fields *dp_fields);
+
+bool
+AudioTrack_HasEncoding(long long encoding_flags, vlc_fourcc_t format);
+
+jobject
+DynamicsProcessing_New(aout_stream_t *stream, int32_t session_id);
+
+int
+DynamicsProcessing_SetVolume(aout_stream_t *stream, jobject dp, float volume);
+
+void
+DynamicsProcessing_Disable(aout_stream_t *stream, jobject dp);
+
+void
+DynamicsProcessing_Delete(aout_stream_t *stream, jobject dp);
+
+struct aout_stream
+{
+    struct vlc_object_t obj;
+    void *sys;
+
+    void (*stop)(aout_stream_t *);
+    int (*time_get)(aout_stream_t *, vlc_tick_t *);
+    void (*play)(aout_stream_t *, block_t *, vlc_tick_t);
+    void (*pause)(aout_stream_t *, bool, vlc_tick_t);
+    void (*flush)(aout_stream_t *);
+    void (*drain)(aout_stream_t *);
+    void (*volume_set)(aout_stream_t *, float volume);
+    void (*mute_set)(aout_stream_t *, bool mute);
+
+    audio_output_t *aout;
+};
+
+static inline void
+aout_stream_GainRequest(aout_stream_t *s, float gain)
+{
+    aout_GainRequest(s->aout, gain);
+}
+
+static inline void
+aout_stream_RestartRequest(aout_stream_t *s, unsigned mode)
+{
+    aout_RestartRequest(s->aout, mode);
+}
+
+static inline
+void aout_stream_TimingReport(aout_stream_t *s, vlc_tick_t system_ts,
+                              vlc_tick_t audio_ts)
+{
+    aout_TimingReport(s->aout, system_ts, audio_ts);
+}
+
+static inline
+void aout_stream_DrainedReport(aout_stream_t *s)
+{
+    aout_DrainedReport(s->aout);
+}
+
+typedef int (*aout_stream_start)(aout_stream_t *s, audio_sample_format_t *fmt,
+                                 enum android_audio_device_type dev);


=====================================
modules/audio_output/opensles_android.c → modules/audio_output/android/opensles.c
=====================================
@@ -22,10 +22,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
-/*****************************************************************************
- * Preamble
- *****************************************************************************/
-
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
@@ -79,9 +75,9 @@ typedef SLresult (*slCreateEngine_t)(
 #define SetVolumeLevel(a, b) (*a)->SetVolumeLevel(a, b)
 #define SetMute(a, b) (*a)->SetMute(a, b)
 
-/*****************************************************************************
- *
- *****************************************************************************/
+static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt);
+static void Stop(audio_output_t *aout);
+
 typedef struct
 {
     /* OpenSL objects */
@@ -122,30 +118,6 @@ typedef struct
     size_t                          samples;
 } aout_sys_t;
 
-/*****************************************************************************
- * Local prototypes.
- *****************************************************************************/
-static int  Open  (vlc_object_t *);
-static void Close (vlc_object_t *);
-
-/*****************************************************************************
- * Module descriptor
- *****************************************************************************/
-
-vlc_module_begin ()
-    set_description("OpenSLES audio output")
-    set_shortname("OpenSLES")
-    set_subcategory(SUBCAT_AUDIO_AOUT)
-
-    set_capability("audio output", 170)
-    add_shortcut("opensles", "android")
-    set_callbacks(Open, Close)
-vlc_module_end ()
-
-/*****************************************************************************
- *
- *****************************************************************************/
-
 static inline int bytesPerSample(void)
 {
     return 2 /* S16 */ * 2 /* stereo */;
@@ -321,9 +293,6 @@ static int WriteBuffer(audio_output_t *aout)
     }
 }
 
-/*****************************************************************************
- * Play: play a sound
- *****************************************************************************/
 static void Play(audio_output_t *aout, block_t *p_buffer, vlc_tick_t date)
 {
     aout_sys_t *sys = aout->sys;
@@ -380,9 +349,122 @@ static int aout_get_native_sample_rate(audio_output_t *aout)
 }
 #endif
 
-/*****************************************************************************
- *
- *****************************************************************************/
+static int Open (vlc_object_t *obj)
+{
+    audio_output_t *aout = (audio_output_t *)obj;
+    aout_sys_t *sys;
+    SLresult result;
+
+    aout->sys = sys = calloc(1, sizeof(*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    sys->p_so_handle = dlopen("libOpenSLES.so", RTLD_NOW);
+    if (sys->p_so_handle == NULL)
+    {
+        msg_Err(aout, "Failed to load libOpenSLES");
+        goto error;
+    }
+
+    sys->slCreateEnginePtr = dlsym(sys->p_so_handle, "slCreateEngine");
+    if (unlikely(sys->slCreateEnginePtr == NULL))
+    {
+        msg_Err(aout, "Failed to load symbol slCreateEngine");
+        goto error;
+    }
+
+#define OPENSL_DLSYM(dest, name)                       \
+    do {                                                       \
+        const SLInterfaceID *sym = dlsym(sys->p_so_handle, "SL_IID_"name);        \
+        if (unlikely(sym == NULL))                             \
+        {                                                      \
+            msg_Err(aout, "Failed to load symbol SL_IID_"name); \
+            goto error;                                        \
+        }                                                      \
+        sys->dest = *sym;                                           \
+    } while(0)
+
+    OPENSL_DLSYM(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, "ANDROIDSIMPLEBUFFERQUEUE");
+    OPENSL_DLSYM(SL_IID_ENGINE, "ENGINE");
+    OPENSL_DLSYM(SL_IID_PLAY, "PLAY");
+    OPENSL_DLSYM(SL_IID_VOLUME, "VOLUME");
+#undef OPENSL_DLSYM
+
+    // create engine
+    result = sys->slCreateEnginePtr(&sys->engineObject, 0, NULL, 0, NULL, NULL);
+    CHECK_OPENSL_ERROR("Failed to create engine");
+
+    // realize the engine in synchronous mode
+    result = Realize(sys->engineObject, SL_BOOLEAN_FALSE);
+    CHECK_OPENSL_ERROR("Failed to realize engine");
+
+    // get the engine interface, needed to create other objects
+    result = GetInterface(sys->engineObject, sys->SL_IID_ENGINE, &sys->engineEngine);
+    CHECK_OPENSL_ERROR("Failed to get the engine interface");
+
+    // create output mix, with environmental reverb specified as a non-required interface
+    const SLInterfaceID ids1[] = { sys->SL_IID_VOLUME };
+    const SLboolean req1[] = { SL_BOOLEAN_FALSE };
+    result = CreateOutputMix(sys->engineEngine, &sys->outputMixObject, 1, ids1, req1);
+    CHECK_OPENSL_ERROR("Failed to create output mix");
+
+    // realize the output mix in synchronous mode
+    result = Realize(sys->outputMixObject, SL_BOOLEAN_FALSE);
+    CHECK_OPENSL_ERROR("Failed to realize output mix");
+
+    vlc_mutex_init(&sys->lock);
+
+    aout->start      = Start;
+    aout->stop       = Stop;
+    aout->time_get   = TimeGet;
+    aout->play       = Play;
+    aout->pause      = Pause;
+    aout->flush      = Flush;
+    aout->mute_set   = MuteSet;
+    aout->volume_set = VolumeSet;
+
+    return VLC_SUCCESS;
+
+error:
+    if (sys->outputMixObject)
+        Destroy(sys->outputMixObject);
+    if (sys->engineObject)
+        Destroy(sys->engineObject);
+    if (sys->p_so_handle)
+        dlclose(sys->p_so_handle);
+    free(sys);
+    return VLC_EGENERIC;
+}
+
+static void Close(vlc_object_t *obj)
+{
+    audio_output_t *aout = (audio_output_t *)obj;
+    aout_sys_t *sys = aout->sys;
+
+    Destroy(sys->outputMixObject);
+    Destroy(sys->engineObject);
+    dlclose(sys->p_so_handle);
+    free(sys);
+}
+
+static void Stop(audio_output_t *aout)
+{
+    aout_sys_t *sys = aout->sys;
+
+    SetPlayState(sys->playerPlay, SL_PLAYSTATE_STOPPED);
+    //Flush remaining buffers if any.
+    Clear(sys->playerBufferQueue);
+
+    free(sys->buf);
+    block_ChainRelease(sys->p_buffer_chain);
+
+    Destroy(sys->playerObject);
+    sys->playerObject = NULL;
+    sys->playerBufferQueue = NULL;
+    sys->volumeItf = NULL;
+    sys->playerPlay = NULL;
+}
+
 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
 {
     if (aout_FormatNbChannels(fmt) == 0 || !AOUT_FMT_LINEAR(fmt))
@@ -488,6 +570,8 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
 
     aout_FormatPrepare(fmt);
 
+    msg_Dbg(aout, "using OpenSL ES API");
+
     return VLC_SUCCESS;
 
 error:
@@ -502,121 +586,12 @@ error:
     return VLC_EGENERIC;
 }
 
-static void Stop(audio_output_t *aout)
-{
-    aout_sys_t *sys = aout->sys;
-
-    SetPlayState(sys->playerPlay, SL_PLAYSTATE_STOPPED);
-    //Flush remaining buffers if any.
-    Clear(sys->playerBufferQueue);
-
-    free(sys->buf);
-    block_ChainRelease(sys->p_buffer_chain);
-
-    Destroy(sys->playerObject);
-    sys->playerObject = NULL;
-    sys->playerBufferQueue = NULL;
-    sys->volumeItf = NULL;
-    sys->playerPlay = NULL;
-}
-
-/*****************************************************************************
- *
- *****************************************************************************/
-static void Close(vlc_object_t *obj)
-{
-    audio_output_t *aout = (audio_output_t *)obj;
-    aout_sys_t *sys = aout->sys;
-
-    Destroy(sys->outputMixObject);
-    Destroy(sys->engineObject);
-    dlclose(sys->p_so_handle);
-    free(sys);
-}
-
-static int Open (vlc_object_t *obj)
-{
-    audio_output_t *aout = (audio_output_t *)obj;
-    aout_sys_t *sys;
-    SLresult result;
-
-    aout->sys = sys = calloc(1, sizeof(*sys));
-    if (unlikely(sys == NULL))
-        return VLC_ENOMEM;
-
-    sys->p_so_handle = dlopen("libOpenSLES.so", RTLD_NOW);
-    if (sys->p_so_handle == NULL)
-    {
-        msg_Err(aout, "Failed to load libOpenSLES");
-        goto error;
-    }
-
-    sys->slCreateEnginePtr = dlsym(sys->p_so_handle, "slCreateEngine");
-    if (unlikely(sys->slCreateEnginePtr == NULL))
-    {
-        msg_Err(aout, "Failed to load symbol slCreateEngine");
-        goto error;
-    }
-
-#define OPENSL_DLSYM(dest, name)                       \
-    do {                                                       \
-        const SLInterfaceID *sym = dlsym(sys->p_so_handle, "SL_IID_"name);        \
-        if (unlikely(sym == NULL))                             \
-        {                                                      \
-            msg_Err(aout, "Failed to load symbol SL_IID_"name); \
-            goto error;                                        \
-        }                                                      \
-        sys->dest = *sym;                                           \
-    } while(0)
-
-    OPENSL_DLSYM(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, "ANDROIDSIMPLEBUFFERQUEUE");
-    OPENSL_DLSYM(SL_IID_ENGINE, "ENGINE");
-    OPENSL_DLSYM(SL_IID_PLAY, "PLAY");
-    OPENSL_DLSYM(SL_IID_VOLUME, "VOLUME");
-#undef OPENSL_DLSYM
-
-    // create engine
-    result = sys->slCreateEnginePtr(&sys->engineObject, 0, NULL, 0, NULL, NULL);
-    CHECK_OPENSL_ERROR("Failed to create engine");
-
-    // realize the engine in synchronous mode
-    result = Realize(sys->engineObject, SL_BOOLEAN_FALSE);
-    CHECK_OPENSL_ERROR("Failed to realize engine");
-
-    // get the engine interface, needed to create other objects
-    result = GetInterface(sys->engineObject, sys->SL_IID_ENGINE, &sys->engineEngine);
-    CHECK_OPENSL_ERROR("Failed to get the engine interface");
-
-    // create output mix, with environmental reverb specified as a non-required interface
-    const SLInterfaceID ids1[] = { sys->SL_IID_VOLUME };
-    const SLboolean req1[] = { SL_BOOLEAN_FALSE };
-    result = CreateOutputMix(sys->engineEngine, &sys->outputMixObject, 1, ids1, req1);
-    CHECK_OPENSL_ERROR("Failed to create output mix");
-
-    // realize the output mix in synchronous mode
-    result = Realize(sys->outputMixObject, SL_BOOLEAN_FALSE);
-    CHECK_OPENSL_ERROR("Failed to realize output mix");
-
-    vlc_mutex_init(&sys->lock);
-
-    aout->start      = Start;
-    aout->stop       = Stop;
-    aout->time_get   = TimeGet;
-    aout->play       = Play;
-    aout->pause      = Pause;
-    aout->flush      = Flush;
-    aout->mute_set   = MuteSet;
-    aout->volume_set = VolumeSet;
-
-    return VLC_SUCCESS;
+vlc_module_begin ()
+    set_description("OpenSLES audio output")
+    set_shortname("OpenSLES")
+    set_subcategory(SUBCAT_AUDIO_AOUT)
 
-error:
-    if (sys->outputMixObject)
-        Destroy(sys->outputMixObject);
-    if (sys->engineObject)
-        Destroy(sys->engineObject);
-    if (sys->p_so_handle)
-        dlclose(sys->p_so_handle);
-    free(sys);
-    return VLC_EGENERIC;
-}
+    set_capability("audio output", 170)
+    add_shortcut("opensles", "android")
+    set_callbacks(Open, Close)
+vlc_module_end ()


=====================================
po/POTFILES.in
=====================================
@@ -253,7 +253,8 @@ modules/audio_mixer/integer.c
 modules/audio_output/adummy.c
 modules/audio_output/alsa.c
 modules/audio_output/amem.c
-modules/audio_output/audiotrack.c
+modules/audio_output/android/device.c
+modules/audio_output/android/opensles.c
 modules/audio_output/audiounit_ios.m
 modules/audio_output/auhal.c
 modules/audio_output/coreaudio_common.c
@@ -261,7 +262,6 @@ modules/audio_output/file.c
 modules/audio_output/jack.c
 modules/audio_output/kai.c
 modules/audio_output/mmdevice.c
-modules/audio_output/opensles_android.c
 modules/audio_output/oss.c
 modules/audio_output/pulse.c
 modules/audio_output/sndio.c



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/f44d18f7d35c9e87b9631fc938bfc2d420ebfdca...d696b09984c122e062110d69241d89742b4f1cfb

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/f44d18f7d35c9e87b9631fc938bfc2d420ebfdca...d696b09984c122e062110d69241d89742b4f1cfb
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list