|
|
|
|
Quelle dec.c
Sprache: C
|
|
/*****************************************************************************
* dec.c : audio output API towards decoders
*****************************************************************************
* Copyright (C) 2002-2019 VLC authors, VideoLAN and Videolabs SAS
*
* Authors: Christophe Massiot <massiot@via.ecp.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.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <math.h>
#include <vlc_common.h>
#include <vlc_aout.h>
#include <vlc_tracer.h>
#include "aout_internal.h"
#include "clock/clock.h"
#include "../libvlc.h"
#define BLOCK_FLAG_CORE_PRIVATE_FILTERED (1 << BLOCK_FLAG_CORE_PRIVATE_SHIFT)
struct vlc_aout_stream
{
aout_instance_t *instance;
aout_volume_t *volume;
aout_filters_t *filters;
aout_filters_cfg_t filters_cfg;
atomic_bool drained;
_Atomic vlc_tick_t drain_deadline;
struct
{
bool draining;
block_t **fifo_last;
block_t *fifo_first;
} discontinuity;
struct
{
struct vlc_clock_t *clock;
uint32_t clock_id;
float rate; /**< Play-out speed rate */
vlc_tick_t resamp_start_drift; /**< Resampler drift absolute value */
int resamp_type; /**< Resampler mode (FIXME: redundant / resampling) */
bool played;
vlc_tick_t request_delay;
vlc_tick_t delay;
} sync;
struct
{
vlc_mutex_t lock; /* Guard first_pts, last_drift, rate_system_ts,
rate_audio_ts, system_ts, audio_ts */
vlc_tick_t last_drift;
vlc_tick_t first_pts;
int64_t played_samples; /* Used for stream_GetDelay() emulation */
vlc_tick_t rate_system_ts;
vlc_tick_t rate_audio_ts;
vlc_tick_t system_ts;
vlc_tick_t audio_ts;
vlc_tick_t pause_date;
float rate;
} timing;
const char *str_id;
/* Original input format and profile, won't change for the lifetime of a
* stream (between vlc_aout_stream_New() and vlc_aout_stream_Delete()). */
int input_profile;
audio_sample_format_t input_format;
/* Format used to configure the conversion filters. It is based on the
* input_format but its fourcc can be different when the module is handling
* codec passthrough. Indeed, in case of DTSHD->DTS or EAC3->AC3 fallback,
* the filter need to know which codec is handled by the output. */
audio_sample_format_t filter_format;
/* Output format used and modified by the module. */
audio_sample_format_t mixer_format;
atomic_uchar restart;
atomic_uint buffers_lost;
atomic_uint buffers_played;
};
static inline aout_owner_t *aout_stream_owner(vlc_aout_stream *stream)
{
return &stream->instance->owner;
}
static inline audio_output_t *aout_stream_aout(vlc_aout_stream *stream)
{
return &stream->instance->output;
}
static inline struct vlc_tracer *aout_stream_tracer(vlc_aout_stream *stream)
{
return stream->str_id == NULL ? NULL :
vlc_object_get_tracer(VLC_OBJECT(aout_stream_aout(stream)));
}
static int stream_GetDelay(vlc_aout_stream *stream, vlc_tick_t *delay)
{
vlc_mutex_assert(&stream->timing.lock);
vlc_tick_t system_ts = stream->timing.system_ts;
vlc_tick_t audio_ts = stream->timing.audio_ts;
if (system_ts == VLC_TICK_INVALID)
return -1;
/* Interpolate the last updated point. */
vlc_tick_t system_now = vlc_tick_now();
/* Use all samples sent to the aout via the play function (normal
* blocks, silence blocks, resampled correction) */
vlc_tick_t played_length =
vlc_tick_from_samples(stream->timing.played_samples,
stream->mixer_format.i_rate);
vlc_tick_t last_pts = stream->timing.first_pts + played_length;
/* Equivalent to vlc_clock_ConvertToSystem(), but assume the
* coefficient is 1.0 (audio at the same speed than the monotonic
* clock) since the calculation is done on a very small interval
* (generally less than 2 seconds). */
vlc_tick_t play_date = last_pts - audio_ts + system_ts;
*delay = play_date - system_now;
return 0;
}
static bool stream_IsDrained(vlc_aout_stream *stream)
{
audio_output_t *aout = aout_stream_aout(stream);
if (aout->drain == NULL)
{
vlc_tick_t drain_deadline =
atomic_load_explicit(&stream->drain_deadline, memory_order_relaxed);
return drain_deadline != VLC_TICK_INVALID
&& vlc_tick_now() >= drain_deadline;
}
else
return atomic_load_explicit(&stream->drained, memory_order_relaxed);
}
static int stream_StartDiscontinuity(vlc_aout_stream *stream, block_t *block)
{
audio_output_t *aout = aout_stream_aout(stream);
struct vlc_tracer *tracer = aout_stream_tracer(stream);
assert(!stream->discontinuity.draining);
/* Changing timings of the stream module while playing is an intricate
* task, therefore modules don't handle any PTS discontinuity.
*
* The PTS discontinuity is handled in the core:
*
* - Drain the current stream in case of discontinuity
* - Check from future Play() calls if the stream is drained
* - Keep blocks in a list while waiting for the drain
* - Flush when the stream is finally drained
* - Play back all blocks that were saved
*/
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id,
"discontinuity_start");
msg_Dbg(aout, "discontinuity: at %"PRId64" us, draining output",
block->i_pts);
vlc_aout_stream_Drain(stream);
stream->discontinuity.draining = true;
stream->discontinuity.fifo_first = NULL;
stream->discontinuity.fifo_last = &stream->discontinuity.fifo_first;
block->i_flags &= ~ BLOCK_FLAG_DISCONTINUITY;
block_ChainLastAppend(&stream->discontinuity.fifo_last, block);
return AOUT_DEC_SUCCESS;
}
static void stream_ResetDiscontinuity(vlc_aout_stream *stream)
{
block_ChainRelease(stream->discontinuity.fifo_first);
stream->discontinuity.draining = false;
}
static int stream_HandleDiscontinuity(vlc_aout_stream *stream, block_t *block)
{
audio_output_t *aout = aout_stream_aout(stream);
struct vlc_tracer *tracer = aout_stream_tracer(stream);
block_ChainLastAppend(&stream->discontinuity.fifo_last, block);
/* Will play back all blocks once the draining is finished */
if (!stream_IsDrained(stream))
return AOUT_DEC_SUCCESS;
/* Keep block list and stats before flushing */
int count;
vlc_tick_t length;
block_ChainProperties(stream->discontinuity.fifo_first, &count, NULL,
&length);
block = stream->discontinuity.fifo_first;
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id,
"discontinuity_flush");
msg_Dbg(aout, "discontinuity: flushing output");
/* Reset the discontinuity state, and flush */
stream->discontinuity.draining = false;
vlc_aout_stream_Flush(stream);
msg_Dbg(aout, "discontinuity: playing back %d blocks for a total length of "
"%"PRId64" us", count, length);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id,
"discontinuity_play");
/* Play back all blocks past the discontinuity */
for (block_t *next; block != NULL; block = next)
{
next = block->p_next;
block->p_next = NULL;
vlc_aout_stream_Play(stream, block);
}
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id,
"discontinuity_end");
return AOUT_DEC_SUCCESS;
}
static void stream_ResetTimings(vlc_aout_stream *stream)
{
stream->sync.played = false;
vlc_mutex_lock(&stream->timing.lock);
stream->timing.first_pts = VLC_TICK_INVALID;
stream->timing.last_drift = VLC_TICK_INVALID;
stream->timing.system_ts = VLC_TICK_INVALID;
stream->timing.audio_ts = VLC_TICK_INVALID;
vlc_mutex_unlock(&stream->timing.lock);
stream->timing.pause_date = VLC_TICK_INVALID;
stream->timing.played_samples = 0;
}
static void stream_Reset(vlc_aout_stream *stream)
{
aout_owner_t *owner = aout_stream_owner(stream);
if (stream->mixer_format.i_format)
{
vlc_audio_meter_Flush(&owner->meter);
if (stream->filters)
{
aout_FiltersFlush (stream->filters);
aout_FiltersAdjustResampling (stream->filters, 0);
}
stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
vlc_clock_Lock(stream->sync.clock);
vlc_clock_Reset(stream->sync.clock);
vlc_clock_Unlock(stream->sync.clock);
if (stream->filters)
aout_FiltersResetClock(stream->filters);
if (stream->sync.delay > 0)
{
/* Also reset the delay in case of a positive delay. This will
* trigger a silence playback before the next play. Consequently,
* the first play date won't be (delay + dejitter) but only
* dejitter. This will allow the aout to update the master clock
* sooner.
*/
vlc_clock_Lock(stream->sync.clock);
vlc_clock_SetDelay(stream->sync.clock, 0);
vlc_clock_Unlock(stream->sync.clock);
if (stream->filters)
aout_FiltersSetClockDelay(stream->filters, 0);
stream->sync.request_delay = stream->sync.delay;
stream->sync.delay = 0;
}
if (stream->discontinuity.draining)
stream_ResetDiscontinuity(stream);
}
stream->timing.rate_audio_ts = VLC_TICK_INVALID;
stream->timing.rate = 1.0;
atomic_store_explicit(&stream->drained, false, memory_order_relaxed);
atomic_store_explicit(&stream->drain_deadline, VLC_TICK_INVALID,
memory_order_relaxed);
stream_ResetTimings(stream);
}
/**
* Creates an audio output
*/
vlc_aout_stream * vlc_aout_stream_New(audio_output_t *p_aout,
const struct vlc_aout_stream_cfg *cfg)
{
assert(p_aout);
assert(cfg->fmt);
assert(cfg->clock);
const audio_sample_format_t *p_format = cfg->fmt;
if( p_format->i_bitspersample > 0 )
{
/* Sanitize audio format, input need to have a valid physical channels
* layout or a valid number of channels. */
int i_map_channels = aout_FormatNbChannels( p_format );
if( ( i_map_channels == 0 && p_format->i_channels == 0 )
|| i_map_channels > AOUT_CHAN_MAX || p_format->i_channels > INPUT_CHAN_MAX )
{
msg_Err( p_aout, "invalid audio channels count" );
return NULL;
}
}
if( p_format->i_rate > 768000 )
{
msg_Err( p_aout, "excessive audio sample frequency (%u)",
p_format->i_rate );
return NULL;
}
if( p_format->i_rate < 4000 )
{
msg_Err( p_aout, "too low audio sample frequency (%u)",
p_format->i_rate );
return NULL;
}
aout_owner_t *owner = aout_owner(p_aout);
vlc_aout_stream *stream = malloc(sizeof(*stream));
if (stream == NULL)
return NULL;
stream->instance = aout_instance(p_aout);
stream->volume = NULL;
if (!owner->bitexact)
stream->volume = aout_volume_New (p_aout, cfg->replay_gain);
atomic_init(&stream->restart, 0);
stream->input_profile = cfg->profile;
stream->filter_format = stream->mixer_format = stream->input_format = *p_format;
stream->sync.clock = cfg->clock;
stream->sync.clock_id = 0;
stream->str_id = cfg->str_id;
stream->timing.rate_audio_ts = VLC_TICK_INVALID;
stream->timing.rate = 1.f;
vlc_mutex_init(&stream->timing.lock);
stream->sync.rate = 1.f;
stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
stream->sync.delay = stream->sync.request_delay = 0;
stream->discontinuity.draining = false;
stream_ResetTimings(stream);
atomic_init (&stream->buffers_lost, 0);
atomic_init (&stream->buffers_played, 0);
atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
atomic_init(&stream->drained, false);
atomic_init(&stream->drain_deadline, VLC_TICK_INVALID);
stream->filters = NULL;
stream->filters_cfg = AOUT_FILTERS_CFG_INIT;
if (aout_OutputNew(p_aout, stream, &stream->mixer_format, stream->input_profile,
&stream->filter_format, &stream->filters_cfg))
goto error;
vlc_audio_meter_Reset(&owner->meter, &stream->mixer_format);
if (!owner->bitexact)
{
if (stream->volume != NULL)
aout_volume_SetFormat(stream->volume, stream->mixer_format.i_format);
/* Create the audio filtering "input" pipeline */
stream->filters = aout_FiltersNewWithClock(VLC_OBJECT(p_aout), cfg->clock,
&stream->filter_format,
&stream->mixer_format,
&stream->filters_cfg);
if (stream->filters == NULL)
{
aout_OutputDelete (p_aout);
vlc_audio_meter_Reset(&owner->meter, NULL);
error:
if (stream->volume != NULL)
aout_volume_Delete(stream->volume);
free(stream);
return NULL;
}
}
return stream;
}
/**
* Stops all plugins involved in the audio output.
*/
void vlc_aout_stream_Delete (vlc_aout_stream *stream)
{
audio_output_t *aout = aout_stream_aout(stream);
aout_owner_t *owner = aout_stream_owner(stream);
if (stream->mixer_format.i_format)
{
vlc_audio_meter_Reset(&owner->meter, NULL);
if (stream->filters)
aout_FiltersDelete (aout, stream->filters);
aout_OutputDelete (aout);
vlc_clock_Lock(stream->sync.clock);
vlc_clock_Reset(stream->sync.clock);
vlc_clock_Unlock(stream->sync.clock);
}
if (stream->volume != NULL)
aout_volume_Delete(stream->volume);
if (stream->discontinuity.draining)
stream_ResetDiscontinuity(stream);
free(stream);
}
static int stream_CheckReady (vlc_aout_stream *stream)
{
aout_owner_t *owner = aout_stream_owner(stream);
audio_output_t *aout = aout_stream_aout(stream);
int status = AOUT_DEC_SUCCESS;
int restart = atomic_exchange_explicit(&stream->restart, 0,
memory_order_acquire);
if (unlikely(restart != 0))
{
if (stream->filters)
{
aout_FiltersDelete (aout, stream->filters);
stream->filters = NULL;
}
struct vlc_tracer *tracer = aout_stream_tracer(stream);
if (restart & AOUT_RESTART_OUTPUT)
{ /* Reinitializes the output */
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "restart");
msg_Dbg (aout, "restarting output...");
if (stream->mixer_format.i_format)
aout_OutputDelete (aout);
stream->filter_format = stream->mixer_format = stream->input_format;
stream->filters_cfg = AOUT_FILTERS_CFG_INIT;
stream_Reset(stream);
if (aout_OutputNew(aout, stream, &stream->mixer_format, stream->input_profile,
&stream->filter_format, &stream->filters_cfg))
stream->mixer_format.i_format = 0;
if (stream->volume != NULL)
aout_volume_SetFormat(stream->volume,
stream->mixer_format.i_format);
/* Notify the decoder that the aout changed in order to try a new
* suitable codec (like an HDMI audio format). However, keep the
* same codec if the aout was restarted because of a stereo-mode
* change from the user. */
if ((restart & AOUT_RESTART_OUTPUT_DEC) == AOUT_RESTART_OUTPUT_DEC)
status = AOUT_DEC_CHANGED;
}
else if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "filters_restart");
msg_Dbg (aout, "restarting filters...");
stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
if (stream->mixer_format.i_format && !owner->bitexact)
{
stream->filters = aout_FiltersNewWithClock(VLC_OBJECT(aout),
stream->sync.clock,
&stream->filter_format,
&stream->mixer_format,
&stream->filters_cfg);
if (stream->filters == NULL)
{
aout_OutputDelete (aout);
stream->mixer_format.i_format = 0;
}
aout_FiltersSetClockDelay(stream->filters, stream->sync.delay);
}
vlc_audio_meter_Reset(&owner->meter,
stream->mixer_format.i_format ? &stream->mixer_format : NULL);
/* TODO: This would be a good time to call clean up any video output
* left over by an audio visualization:
input_resource_TerminatVout(MAGIC HERE); */
}
return (stream->mixer_format.i_format) ? status : AOUT_DEC_FAILED;
}
/**
* Marks the audio output for restart, to update any parameter of the output
* plug-in (e.g. output device or channel mapping).
*/
void vlc_aout_stream_RequestRestart(vlc_aout_stream *stream, unsigned mode)
{
audio_output_t *aout = aout_stream_aout(stream);
atomic_fetch_or_explicit(&stream->restart, mode, memory_order_release);
msg_Dbg (aout, "restart requested (%u)", mode);
}
/*
* Buffer management
*/
static void stream_StopResampling(vlc_aout_stream *stream)
{
assert(stream->filters);
stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
aout_FiltersAdjustResampling (stream->filters, 0);
}
static void stream_Silence (vlc_aout_stream *stream, vlc_tick_t length, vlc_tick_t pts)
{
audio_output_t *aout = aout_stream_aout(stream);
const audio_sample_format_t *fmt = &stream->mixer_format;
size_t frames = samples_from_vlc_tick(length, fmt->i_rate);
block_t *block = block_Alloc (frames * fmt->i_bytes_per_frame
/ fmt->i_frame_length);
if (unlikely(block == NULL))
return; /* uho! */
msg_Dbg (aout, "inserting %zu zeroes / %"PRId64"ms", frames, MS_FROM_VLC_TICK(length));
memset (block->p_buffer, 0, block->i_buffer);
block->i_nb_samples = frames;
block->i_pts = pts;
block->i_dts = pts;
block->i_length = length;
const vlc_tick_t system_now = vlc_tick_now();
vlc_clock_Lock(stream->sync.clock);
const vlc_tick_t system_pts =
vlc_clock_ConvertToSystem(stream->sync.clock, system_now, pts,
stream->sync.rate, NULL);
vlc_clock_Unlock(stream->sync.clock);
stream->timing.played_samples += block->i_nb_samples;
aout->play(aout, block, system_pts);
}
static void stream_HandleDrift(vlc_aout_stream *stream, vlc_tick_t drift,
vlc_tick_t audio_ts)
{
aout_owner_t *owner = aout_stream_owner(stream);
audio_output_t *aout = aout_stream_aout(stream);
assert(stream->sync.played);
float rate = stream->sync.rate;
if (owner->bitexact)
return;
struct vlc_tracer *tracer = aout_stream_tracer(stream);
if (tracer != NULL)
vlc_tracer_Trace(tracer, VLC_TRACE("type", "RENDER"),
VLC_TRACE("id", stream->str_id),
VLC_TRACE_TICK_NS("drift", drift),
VLC_TRACE_END);
/* Following calculations expect an opposite drift. Indeed,
* vlc_clock_Update() returns a positive relative time, corresponding to
* the time when audio_ts is expected to be played (in the future when not
* late). */
drift = -drift;
/* Late audio output.
* This can happen due to insufficient caching, scheduling jitter
* or bug in the decoder. Ideally, the output would seek backward. But that
* is not portable, not supported by some hardware and often unsafe/buggy
* where supported. The other alternative is to flush the buffers
* completely. */
if (drift > lroundf(+3 * AOUT_MAX_PTS_DELAY / rate))
{
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "late_flush");
msg_Warn (aout, "playback way too late (%"PRId64"): "
"flushing buffers", drift);
vlc_aout_stream_Flush(stream);
stream_StopResampling(stream);
return; /* nothing can be done if timing is unknown */
}
/* Early audio output.
* This is rare except at startup when the buffers are still empty. */
if (drift < lroundf(-3 * AOUT_MAX_PTS_ADVANCE / rate))
{
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "early_silence");
msg_Warn (aout, "playback way too early (%"PRId64"): "
"playing silence", drift);
stream_Silence(stream, -drift, audio_ts);
stream_StopResampling(stream);
drift = 0;
}
if (!aout_FiltersCanResample(stream->filters))
return;
/* Resampling */
if (drift > +AOUT_MAX_PTS_DELAY
&& stream->sync.resamp_type != AOUT_RESAMPLING_UP)
{
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "late_upsampling");
msg_Warn (aout, "playback too late (%"PRId64"): up-sampling",
drift);
stream->sync.resamp_type = AOUT_RESAMPLING_UP;
stream->sync.resamp_start_drift = +drift;
}
if (drift < -AOUT_MAX_PTS_ADVANCE
&& stream->sync.resamp_type != AOUT_RESAMPLING_DOWN)
{
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "early_downsampling");
msg_Warn (aout, "playback too early (%"PRId64"): down-sampling",
drift);
stream->sync.resamp_type = AOUT_RESAMPLING_DOWN;
stream->sync.resamp_start_drift = -drift;
}
if (stream->sync.resamp_type == AOUT_RESAMPLING_NONE)
return; /* Everything is fine. Nothing to do. */
if (llabs (drift) > 2 * stream->sync.resamp_start_drift)
{ /* If the drift is ever increasing, then something is seriously wrong.
* Cease resampling and hope for the best. */
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "timing_screwed");
msg_Warn (aout, "timing screwed (drift: %"PRId64" us): "
"stopping resampling", drift);
stream_StopResampling(stream);
return;
}
/* Resampling has been triggered earlier. This checks if it needs to be
* increased or decreased. Resampling rate changes must be kept slow for
* the comfort of listeners. */
int adj = (stream->sync.resamp_type == AOUT_RESAMPLING_UP) ? +2 : -2;
if (2 * llabs (drift) <= stream->sync.resamp_start_drift)
/* If the drift has been reduced from more than half its initial
* value, then it is time to switch back the resampling direction. */
adj *= -1;
if (!aout_FiltersAdjustResampling (stream->filters, adj))
{ /* Everything is back to normal: stop resampling. */
stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "stop_resampling");
msg_Dbg (aout, "resampling stopped (drift: %"PRId64" us)", drift);
}
}
static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now,
vlc_tick_t play_date, vlc_tick_t dec_pts)
{
/**
* Depending on the drift between the actual and intended playback times,
* the audio core may ignore the drift, trigger upsampling or downsampling,
* insert silence or even discard samples.
* Future VLC versions may instead adjust the input rate.
*
* The audio output plugin is responsible for estimating its actual
* playback time, or rather the estimated time when the next sample will
* be played. (The actual playback time is always the current time, that is
* to say vlc_tick_now(). It is not an useful statistic.)
*
* Most audio output plugins can estimate the delay until playback of
* the next sample to be written to the buffer, or equally the time until
* all samples in the buffer will have been played. Then:
* pts = vlc_tick_now() + delay
*/
vlc_tick_t delay;
vlc_tick_t drift;
assert(system_now != VLC_TICK_INVALID);
vlc_mutex_lock(&stream->timing.lock);
if (stream->sync.rate != stream->timing.rate)
{
/* Save the first timing point seeing a rate change */
stream->timing.rate_system_ts = play_date;
stream->timing.rate_audio_ts = dec_pts;
stream->timing.rate = stream->sync.rate;
}
if (stream->timing.first_pts == VLC_TICK_INVALID)
stream->timing.first_pts = dec_pts;
bool is_drifting = stream->timing.last_drift != VLC_TICK_INVALID;
if (!is_drifting)
{
/* stream is master: nothing to do */
vlc_mutex_unlock(&stream->timing.lock);
return;
}
int ret = stream_GetDelay(stream, &delay);
vlc_mutex_unlock(&stream->timing.lock);
if (ret != 0)
return; /* nothing can be done if timing is unknown */
drift = play_date - system_now - delay;
stream_HandleDrift(stream, drift, dec_pts);
}
void vlc_aout_stream_NotifyTiming(vlc_aout_stream *stream, vlc_tick_t system_ts,
vlc_tick_t audio_ts)
{
vlc_mutex_lock(&stream->timing.lock);
vlc_tick_t rate_audio_ts = stream->timing.rate_audio_ts;
vlc_tick_t rate_system_ts = stream->timing.rate_system_ts;
/* audio_ts is the output position: at rate != 1, it advances with the
* output samples, like played_samples, not with the stream timestamps
* of the clock. */
vlc_tick_t clock_audio_ts = audio_ts;
if (rate_audio_ts != VLC_TICK_INVALID)
{
/* Drop timing updates that comes before the rate change */
if (system_ts < rate_system_ts)
{
vlc_mutex_unlock(&stream->timing.lock);
return;
}
/* Fix the audio timestamp with the rate */
clock_audio_ts = rate_audio_ts + (system_ts - rate_system_ts)
* stream->timing.rate;
}
stream->timing.system_ts = system_ts;
stream->timing.audio_ts = audio_ts;
vlc_clock_Lock(stream->sync.clock);
stream->timing.last_drift =
vlc_clock_Update(stream->sync.clock, system_ts,
clock_audio_ts, stream->timing.rate);
vlc_clock_Unlock(stream->sync.clock);
vlc_mutex_unlock(&stream->timing.lock);
}
static vlc_tick_t stream_ClockConvert(vlc_aout_stream *stream,
vlc_tick_t system_now, vlc_tick_t pts)
{
uint32_t clock_id;
vlc_clock_Lock(stream->sync.clock);
vlc_tick_t play_date =
vlc_clock_ConvertToSystem(stream->sync.clock, system_now, pts,
stream->sync.rate, &clock_id);
vlc_clock_Unlock(stream->sync.clock);
if (clock_id != stream->sync.clock_id && stream->sync.played)
{
stream->sync.clock_id = clock_id;
return VLC_TICK_INVALID;
}
return play_date;
}
/*****************************************************************************
* vlc_aout_stream_Play : filter & mix the decoded buffer
*****************************************************************************/
int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
{
aout_owner_t *owner = aout_stream_owner(stream);
audio_output_t *aout = aout_stream_aout(stream);
assert (block->i_pts != VLC_TICK_INVALID);
assert (stream->timing.pause_date == VLC_TICK_INVALID);
block->i_length = vlc_tick_from_samples( block->i_nb_samples,
stream->input_format.i_rate );
if (stream->discontinuity.draining)
return stream_HandleDiscontinuity(stream, block);
if (block->i_flags & BLOCK_FLAG_DISCONTINUITY && stream->sync.played)
return stream_StartDiscontinuity(stream, block);
int ret = stream_CheckReady (stream);
if (unlikely(ret == AOUT_DEC_FAILED))
goto drop; /* Pipeline is unrecoverably broken :-( */
vlc_tick_t play_date = VLC_TICK_INVALID;
vlc_tick_t system_now;
if (stream->sync.request_delay != stream->sync.delay)
{
stream->sync.delay = stream->sync.request_delay;
vlc_clock_Lock(stream->sync.clock);
vlc_tick_t delta = vlc_clock_SetDelay(stream->sync.clock, stream->sync.delay);
vlc_clock_Unlock(stream->sync.clock);
if (stream->filters)
aout_FiltersSetClockDelay(stream->filters, stream->sync.delay);
if (delta > 0)
stream_Silence(stream, delta, block->i_pts);
}
if (stream->filters && (block->i_flags & BLOCK_FLAG_CORE_PRIVATE_FILTERED) == 0)
{
system_now = vlc_tick_now();
vlc_tick_t prefilter_pts = block->i_pts;
play_date = stream_ClockConvert(stream, system_now, block->i_pts);
if (play_date == VLC_TICK_INVALID)
return stream_StartDiscontinuity(stream, block);
if (atomic_load_explicit(&owner->vp.update, memory_order_relaxed))
{
vlc_mutex_lock (&owner->vp.lock);
aout_FiltersChangeViewpoint (stream->filters, &owner->vp.value);
atomic_store_explicit(&owner->vp.update, false, memory_order_relaxed);
vlc_mutex_unlock (&owner->vp.lock);
}
block = aout_FiltersPlay(stream->filters, block, stream->sync.rate);
if (block == NULL)
return ret;
assert (block->i_pts != VLC_TICK_INVALID);
/* Re-trigger a clock convert if the filtered ts is different */
if (prefilter_pts != block->i_pts)
play_date = VLC_TICK_INVALID;
}
/* Software volume */
if (stream->volume != NULL)
aout_volume_Amplify(stream->volume, block);
/* Convert the pts if not previously done by filters */
if (play_date == VLC_TICK_INVALID)
{
system_now = vlc_tick_now();
play_date = stream_ClockConvert(stream, system_now, block->i_pts);
if (play_date == VLC_TICK_INVALID)
{
block->i_flags |= BLOCK_FLAG_CORE_PRIVATE_FILTERED;
return stream_StartDiscontinuity(stream, block);
}
}
/* Drift correction */
stream_Synchronize(stream, system_now, play_date, block->i_pts);
vlc_audio_meter_Process(&owner->meter, block, play_date);
/* Output */
stream->sync.played = true;
stream->timing.played_samples += block->i_nb_samples;
aout->play(aout, block, play_date);
atomic_fetch_add_explicit(&stream->buffers_played, 1, memory_order_relaxed);
return ret;
drop:
stream_ResetTimings(stream);
block_Release (block);
atomic_fetch_add_explicit(&stream->buffers_lost, 1, memory_order_relaxed);
return ret;
}
void vlc_aout_stream_GetResetStats(vlc_aout_stream *stream, unsigned *restrict lost,
unsigned *restrict played)
{
*lost = atomic_exchange_explicit(&stream->buffers_lost, 0,
memory_order_relaxed);
*played = atomic_exchange_explicit(&stream->buffers_played, 0,
memory_order_relaxed);
}
void vlc_aout_stream_ChangePause(vlc_aout_stream *stream, bool paused, vlc_tick_t date)
{
audio_output_t *aout = aout_stream_aout(stream);
if (stream->mixer_format.i_format)
{
struct vlc_tracer *tracer = aout_stream_tracer(stream);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id,
paused ? "paused" : "resumed");
if (paused)
{
assert(stream->timing.pause_date == VLC_TICK_INVALID);
stream->timing.pause_date = date;
}
else if (stream->timing.pause_date != VLC_TICK_INVALID)
{
/* Delay the last timing with the pause duration. This will be used
* by stream_GetDelay() until the module updates its next point
* after being resumed. */
vlc_mutex_lock(&stream->timing.lock);
if (stream->timing.system_ts != VLC_TICK_INVALID)
stream->timing.system_ts += date - stream->timing.pause_date;
vlc_mutex_unlock(&stream->timing.lock);
stream->timing.pause_date = VLC_TICK_INVALID;
}
if (aout->pause != NULL)
aout->pause(aout, paused, date);
else if (paused)
vlc_aout_stream_Flush(stream);
/* Update the rate point after the pause */
if (!paused && stream->timing.rate_audio_ts != VLC_TICK_INVALID)
{
vlc_clock_Lock(stream->sync.clock);
vlc_tick_t play_date =
vlc_clock_ConvertToSystem(stream->sync.clock, date,
stream->timing.rate_audio_ts,
stream->sync.rate, NULL);
vlc_clock_Unlock(stream->sync.clock);
stream->timing.rate_system_ts = play_date;
}
}
}
void vlc_aout_stream_ChangeRate(vlc_aout_stream *stream, float rate)
{
stream->sync.rate = rate;
}
void vlc_aout_stream_ChangeDelay(vlc_aout_stream *stream, vlc_tick_t delay)
{
stream->sync.request_delay = delay;
}
void vlc_aout_stream_Flush(vlc_aout_stream *stream)
{
audio_output_t *aout = aout_stream_aout(stream);
struct vlc_tracer *tracer = aout_stream_tracer(stream);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "flushed");
if (stream->mixer_format.i_format)
aout->flush(aout);
stream_Reset(stream);
}
void vlc_aout_stream_NotifyGain(vlc_aout_stream *stream, float gain)
{
if (stream->volume != NULL)
aout_volume_SetVolume(stream->volume, gain);
}
void vlc_aout_stream_NotifyDrained(vlc_aout_stream *stream)
{
atomic_store_explicit(&stream->drained, true, memory_order_relaxed);
}
bool vlc_aout_stream_IsDrained(vlc_aout_stream *stream)
{
/* The internal draining state should not mess with the public one */
if (stream->discontinuity.draining)
return false;
return stream_IsDrained(stream);
}
void vlc_aout_stream_Drain(vlc_aout_stream *stream)
{
audio_output_t *aout = aout_stream_aout(stream);
if (!stream->mixer_format.i_format)
return;
if (unlikely(stream->discontinuity.draining))
{
if (stream->discontinuity.fifo_first != NULL)
{
vlc_tick_t length;
block_ChainProperties(stream->discontinuity.fifo_first, NULL, NULL,
&length);
msg_Err(aout, "draining while handling a discontinuity not handled"
", losing %"PRId64 " us of audio", length); /* FIXME */
}
stream_ResetDiscontinuity(stream);
return;
}
struct vlc_tracer *tracer = aout_stream_tracer(stream);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "drain");
if (stream->filters)
{
block_t *block = aout_FiltersDrain (stream->filters);
if (block)
{
assert (block->i_pts != VLC_TICK_INVALID);
stream->timing.played_samples += block->i_nb_samples;
aout->play(aout, block, vlc_tick_now());
}
}
if (aout->drain)
{
assert(!atomic_load_explicit(&stream->drained, memory_order_relaxed));
aout->drain(aout);
}
else
{
assert(atomic_load_explicit(&stream->drain_deadline,
memory_order_relaxed) == VLC_TICK_INVALID);
vlc_tick_t drain_deadline = vlc_tick_now();
vlc_tick_t delay;
vlc_mutex_lock(&stream->timing.lock);
if (stream_GetDelay(stream, &delay) == 0)
drain_deadline += delay;
vlc_mutex_unlock(&stream->timing.lock);
/* else the deadline is now, and vlc_aout_stream_IsDrained() will
* return true on the first call. */
atomic_store_explicit(&stream->drain_deadline, drain_deadline,
memory_order_relaxed);
}
}
¤ Dauer der Verarbeitung: 0.9 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=1104848 |
| #Domains=416962 |
|
|