Browse Source

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.
pull/141/head
Thomas Guillem 6 years ago
committed by Jean-Baptiste Kempf
parent
commit
a53eb42eea
  1. 5
      modules/audio_output/Makefile.am
  2. 824
      modules/audio_output/android/aaudio.c

5
modules/audio_output/Makefile.am

@ -7,11 +7,14 @@ libopensles_android_plugin_la_LIBADD = libandroid_utils.la $(LIBDL) $(LIBM)
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_audiodevice_plugin.la libopensles_android_plugin.la \
libandroid_audiotrack_plugin.la
libandroid_audiotrack_plugin.la libandroid_aaudio_plugin.la
endif
libadummy_plugin_la_SOURCES = audio_output/adummy.c

824
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@videolabs.io>
* Thomas Guillem <thomas@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 ()
Loading…
Cancel
Save