Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/src/audio_output/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 36 kB image not shown  

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);
    }
}

Messung
CHZG
85989994

¤ Dauer der Verarbeitung: 0.18 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.