|
|
|
|
Quelle aaudio.c
Sprache: C
|
|
/*****************************************************************************
* 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 "dynamicsprocessing_jni.h"
#include <aaudio/AAudio.h>
#define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)
struct sys
{
struct
{
aaudio_format_t format;
int32_t session_id;
int32_t device_id;
bool low_latency;
} cfg;
AAudioStream *as;
jobject dp;
float volume;
bool muted;
audio_sample_format_t fmt;
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, used for warning */
size_t underrun_warn_bytes;
/* Bytes of silence written after it started */
size_t underrun_total_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;
vlc_tick_t first_pts;
/* Channel reordering */
uint8_t chans_to_reorder;
uint8_t chan_table[AOUT_CHAN_MAX];
};
/* dlopen/dlsym symbols */
static struct {
void *handle;
aaudio_result_t (*AAudio_createStreamBuilder)(AAudioStreamBuilder **);
const char *(*AAudio_convertResultToText)(aaudio_result_t);
void (*AAudioStreamBuilder_setDeviceId)(AAudioStreamBuilder *, int32_t);
void (*AAudioStreamBuilder_setFormat)(AAudioStreamBuilder *, aaudio_format_t);
void (*AAudioStreamBuilder_setChannelCount)(AAudioStreamBuilder *, int32_t);
void (*AAudioStreamBuilder_setChannelMask)(AAudioStreamBuilder *, aaudio_channel_mask_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_getDeviceId)(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, critical) \
do { \
void *sym = dlsym(vt.handle, #name); \
if (unlikely(!sym) && critical) { \
msg_Err(stream, "Failed to load symbol "#name); \
init_state = 0; \
goto end; \
} \
*(void **) &vt.name = sym; \
} while(0)
AAUDIO_DLSYM(AAudio_createStreamBuilder, true);
AAUDIO_DLSYM(AAudio_convertResultToText, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setChannelCount, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setChannelMask, false);
AAUDIO_DLSYM(AAudioStreamBuilder_setDeviceId, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setFormat, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setDataCallback, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setErrorCallback, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setPerformanceMode, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setSessionId, true);
AAUDIO_DLSYM(AAudioStreamBuilder_setUsage, true);
AAUDIO_DLSYM(AAudioStreamBuilder_openStream, true);
AAUDIO_DLSYM(AAudioStreamBuilder_delete, true);
AAUDIO_DLSYM(AAudioStream_requestStart, true);
AAUDIO_DLSYM(AAudioStream_requestStop, true);
AAUDIO_DLSYM(AAudioStream_requestPause, true);
AAUDIO_DLSYM(AAudioStream_requestFlush, true);
AAUDIO_DLSYM(AAudioStream_getDeviceId, true);
AAUDIO_DLSYM(AAudioStream_getSampleRate, true);
AAUDIO_DLSYM(AAudioStream_getTimestamp, true);
AAUDIO_DLSYM(AAudioStream_write, true);
AAUDIO_DLSYM(AAudioStream_close, true);
AAUDIO_DLSYM(AAudioStream_getState, true);
AAUDIO_DLSYM(AAudioStream_waitForStateChange, true);
AAUDIO_DLSYM(AAudioStream_getSessionId, true);
#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;
assert(sys->as);
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;
if (sys->error)
return AAUDIO_STREAM_STATE_UNINITIALIZED;
assert(sys->as);
return 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)
{
LogAAudioError(stream, "AAudioStream_getTimestamp failed", result);
return VLC_EGENERIC;
}
if (*pos_frames <= 0)
return VLC_EGENERIC;
*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);
aaudio_stream_state_t state = GetState(stream);
if (!sys->started)
{
size_t tocopy;
if (sys->first_play_date == VLC_TICK_INVALID)
tocopy = bytes;
else
{
vlc_tick_t now = vlc_tick_now();
/* Write silence to reach the first play date */
vlc_tick_t silence_ticks = sys->first_play_date - now;
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_warn_bytes += bytes;
sys->underrun_total_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;
int64_t pos_frames;
vlc_tick_t system_ts;
if (state == AAUDIO_STREAM_STATE_STARTED
&& sys->timing_report_last_written_bytes >= sys->timing_report_delay_bytes
&& GetFrameTimestampLocked(stream, &pos_frames, &system_ts) == VLC_SUCCESS)
{
vlc_tick_t pos_ticks = FramesToTicks(sys, pos_frames);
/* underrun 0s don't count in timing reports */
vlc_tick_t underrun_ticks = BytesToTicks(sys, sys->underrun_total_bytes);
/* pos_frame might not have taken the last underrun update */
if (likely(pos_ticks > underrun_ticks))
{
pos_ticks -= underrun_ticks;
/* From now on, fetch the timestamp every 1 seconds */
sys->timing_report_last_written_bytes = 0;
sys->timing_report_delay_bytes =
TicksToBytes(sys, TIMING_REPORT_DELAY_TICKS);
/* 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 + sys->first_pts);
}
}
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, false);
}
static void
CloseAAudioStream(aout_stream_t *stream)
{
struct sys *sys = stream->sys;
if (!sys->error)
{
RequestStop(stream);
if (WaitState(stream, AAUDIO_STREAM_STATE_STOPPED) != VLC_SUCCESS)
msg_Warn(stream, "Error waiting for stopped state");
}
vt.AAudioStream_close(sys->as);
sys->as = NULL;
sys->error = false;
}
static aaudio_channel_mask_t
VLCMaskToAaudio(uint16_t vlc_mask)
{
aaudio_channel_mask_t mask = 0;
if (vlc_mask & AOUT_CHAN_CENTER)
mask |= AAUDIO_CHANNEL_FRONT_CENTER;
if (vlc_mask & AOUT_CHAN_LEFT)
mask |= AAUDIO_CHANNEL_FRONT_LEFT;
if (vlc_mask & AOUT_CHAN_RIGHT)
mask |= AAUDIO_CHANNEL_FRONT_RIGHT;
if (vlc_mask & AOUT_CHAN_REARCENTER)
mask |= AAUDIO_CHANNEL_BACK_CENTER;
if (vlc_mask & AOUT_CHAN_REARLEFT)
mask |= AAUDIO_CHANNEL_BACK_LEFT;
if (vlc_mask & AOUT_CHAN_REARRIGHT)
mask |= AAUDIO_CHANNEL_BACK_RIGHT;
if (vlc_mask & AOUT_CHAN_MIDDLELEFT)
mask |= AAUDIO_CHANNEL_SIDE_LEFT;
if (vlc_mask & AOUT_CHAN_MIDDLERIGHT)
mask |= AAUDIO_CHANNEL_SIDE_RIGHT;
if (vlc_mask & AOUT_CHAN_LFE)
mask |= AAUDIO_CHANNEL_LOW_FREQUENCY;
return mask;
}
static int
OpenAAudioStream(aout_stream_t *stream)
{
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;
}
vt.AAudioStreamBuilder_setFormat(builder, sys->cfg.format);
uint32_t chans_out[AOUT_CHAN_MAX];
AndroidDevice_GetChanOrder(sys->fmt.i_physical_channels, chans_out,
AOUT_CHAN_MAX );
sys->chans_to_reorder =
aout_CheckChannelReorder(NULL, chans_out,
sys->fmt.i_physical_channels,
sys->chan_table );
if (vt.AAudioStreamBuilder_setChannelMask != NULL)
{
aaudio_channel_mask_t mask =
VLCMaskToAaudio(sys->fmt.i_physical_channels);
vt.AAudioStreamBuilder_setChannelMask(builder, mask);
}
else
vt.AAudioStreamBuilder_setChannelCount(builder, sys->fmt.i_channels);
/* Setup the session-id */
vt.AAudioStreamBuilder_setSessionId(builder, sys->cfg.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 */
vt.AAudioStreamBuilder_setDataCallback(builder, DataCallback, stream);
vt.AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, stream);
if (sys->cfg.low_latency)
vt.AAudioStreamBuilder_setPerformanceMode(builder,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
if (sys->cfg.device_id != AAUDIO_UNSPECIFIED)
vt.AAudioStreamBuilder_setDeviceId(builder, sys->cfg.device_id);
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);
sys->as = as;
return VLC_SUCCESS;
}
static void
PrepareAudioFormat(aout_stream_t *stream, audio_sample_format_t *fmt)
{
struct sys *sys = stream->sys;
sys->cfg.device_id = AAUDIO_UNSPECIFIED;
sys->cfg.session_id = var_InheritInteger(stream, "audiotrack-session-id");
if (sys->cfg.session_id == 0)
sys->cfg.session_id = AAUDIO_SESSION_ID_ALLOCATE;
if (vt.AAudioStreamBuilder_setChannelMask == NULL)
{
/* Without the proper setChannelMask API, support only mono, stereo,
* 5.1 and 7.1 */
unsigned channels = aout_FormatNbChannels( &sys->fmt );
if (channels > 7)
sys->fmt.i_physical_channels = AOUT_CHANS_7_1;
else if (channels > 5)
sys->fmt.i_physical_channels = AOUT_CHANS_5_1;
else if (channels == 1)
sys->fmt.i_physical_channels = AOUT_CHAN_LEFT;
else
sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
aout_FormatPrepare(fmt);
}
if (fmt->i_format == VLC_CODEC_S16N)
sys->cfg.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;
}
sys->cfg.format = AAUDIO_FORMAT_PCM_FLOAT;
}
if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
{
fmt->channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
sys->cfg.low_latency = true;
}
else
sys->cfg.low_latency = var_InheritBool(stream, "low-delay");
sys->fmt = *fmt;
}
static void
Play(aout_stream_t *stream, vlc_frame_t *frame, vlc_tick_t date)
{
struct sys *sys = stream->sys;
aaudio_stream_state_t state = GetState(stream);
if (state == AAUDIO_STREAM_STATE_OPEN)
{
if (RequestStart(stream) != VLC_SUCCESS)
goto bailout;
}
else if (state != AAUDIO_STREAM_STATE_STARTING
&& state != AAUDIO_STREAM_STATE_STARTED)
goto bailout;
assert(sys->as);
if (sys->chans_to_reorder > 0)
aout_ChannelReorder(frame->p_buffer, frame->i_buffer,
sys->chans_to_reorder, sys->chan_table,
sys->fmt.i_format );
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_pts == VLC_TICK_INVALID)
sys->first_pts = frame->i_pts;
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_warn_bytes;
sys->underrun_warn_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)
goto error;
if (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)
goto error;
state = GetState(stream);
if (state == AAUDIO_STREAM_STATE_PAUSING
&& WaitState(stream, AAUDIO_STREAM_STATE_PAUSED) != VLC_SUCCESS)
goto error;
if (RequestFlush(stream) != VLC_SUCCESS)
goto error;
if (WaitState(stream, AAUDIO_STREAM_STATE_FLUSHED) != VLC_SUCCESS)
goto error;
error:
CloseAAudioStream(stream);
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_pts = sys->first_play_date = VLC_TICK_INVALID;
sys->start_silence_bytes = 0;
sys->timing_report_last_written_bytes = 0;
sys->timing_report_delay_bytes = 0;
sys->underrun_warn_bytes = 0;
sys->underrun_total_bytes = 0;
int ret = OpenAAudioStream(stream);
if (ret != VLC_SUCCESS)
{
sys->error = true;
return;
}
int32_t new_rate = vt.AAudioStream_getSampleRate(sys->as);
assert(new_rate > 0);
if ((unsigned) new_rate != sys->fmt.i_rate)
{
msg_Err(stream, "rate changed after flush, from %u to %d",
sys->fmt.i_rate, new_rate);
aout_stream_RestartRequest(stream, false);
}
}
static void
Drain(aout_stream_t *stream)
{
struct sys *sys = stream->sys;
vlc_mutex_lock(&sys->lock);
sys->draining = true;
vlc_mutex_unlock(&sys->lock);
if (unlikely(sys->first_play_date == VLC_TICK_INVALID))
aout_stream_DrainedReport(stream);
}
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 void
Stop(aout_stream_t *stream)
{
struct sys *sys = stream->sys;
if (sys->as != NULL)
CloseAAudioStream(stream);
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)
{
if (!AOUT_FMT_LINEAR(fmt))
return VLC_EGENERIC;
if (LoadSymbols(stream) != VLC_SUCCESS)
return VLC_EGENERIC;
if (adev == ANDROID_AUDIO_DEVICE_STEREO)
{
fmt->i_physical_channels = AOUT_CHANS_STEREO;
aout_FormatPrepare(fmt);
}
struct sys *sys = stream->sys = malloc(sizeof(*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
sys->volume = 1.0f;
sys->muted = false;
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_warn_bytes = 0;
sys->underrun_total_bytes = 0;
sys->started = false;
sys->draining = false;
sys->first_pts = sys->first_play_date = VLC_TICK_INVALID;
sys->timing_report_last_written_bytes = 0;
sys->timing_report_delay_bytes = 0;
PrepareAudioFormat(stream, fmt);
int ret = OpenAAudioStream(stream);
if (ret != VLC_SUCCESS)
{
free(sys);
return ret;
}
/* Use the native sample rate of the device */
sys->fmt.i_rate = fmt->i_rate = vt.AAudioStream_getSampleRate(sys->as);
assert(fmt->i_rate > 0);
sys->cfg.session_id = vt.AAudioStream_getSessionId(sys->as);
if (sys->cfg.session_id != AAUDIO_SESSION_ID_NONE)
sys->dp = DynamicsProcessing_New(stream, sys->cfg.session_id);
else
sys->dp = NULL;
if (sys->dp == NULL)
msg_Warn(stream, "failed to attach DynamicsProcessing to the stream)");
sys->cfg.device_id = vt.AAudioStream_getDeviceId(sys->as);
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 ()
¤ Dauer der Verarbeitung: 0.5 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|
2026-11-01
|
|
|
|
|
Neuigkeiten |
| Aktuelles |
| Motto des Tages |
|
Open Source Software |
|
|
|
Jenseits des Üblichen ....
|
|
Besucherstatistik |
|
|
| Statistik |
| #Sources=1104851 |
| #Domains=417124 |
|
|