[vlc-devel] [PATCH v2 3/7] aout: add gapless support

Rémi Denis-Courmont remi at remlab.net
Thu Mar 21 13:41:57 CET 2019


I already told you that this cannot work. If you think about all possible combos, it's kinda obvious that concurrent inputs are necessary, but not sufficient, to make it work.

You cannot know if PTS will be continuous or if the formats will match, or if there will be an active audio ES at all.

Le 21 mars 2019 01:09:10 GMT+08:00, Thomas Guillem <thomas at gllm.fr> a écrit :
>The player will call aout_OutputSetGaplessEnabled(true|false) when
>there is a
>next media to play or not. This call is protected with the same mutex
>used for
>start/stop and can be called from anywhere.
>
>Then, when the stream is draining (aout_DecDrain), it will postpone the
>actual
>drain if gapless is enabled, and do the following depending on the next
>aout
>call:
>
>- if play is called just after, the postponed drain is executed and the
>   gapless state is canceled.
>- if flush is called, the gapless state is canceled and everything is
>flushed
>- if stop is called, filters are cleaned but the stream is not stopped,
>it is
>   saved for a future usage: (via aout_OutputSaveStream()).
>
>On the next stream start, it will try to recover the previous stream
>(that
>could be stopped if the player disabled gapless in the meantime) and
>will do
>the following depending on the aout_DecNew parameters:
>
>- if format are different: cancel gapless state, drain, flush and stop
>the
>   previous stream.
>
>- if format are equals: use the previous stream (don't call
>aout->stop(),
>   aout->start()), and set-up new filters chain with the new clock.
>
>The final gapless transition will be done from aout_DecPlay():
>
>- if the previous stream delay (time_get) is compatible with the next
>clock,
>blocks will be added in a temporary buffer until the clock jitter delay
>is
>   reached.
>- if not, cancel the gapless state, drain and flush the
>(previous=current)
>   stream (but don't restart it), and play all blocks normally.
>
>When the jitter delay is reached, blocks added in the temporary buffers
>can be
>played and the playback can resume normally.
>
>Fixes #549
>---
> src/audio_output/aout_internal.h |  17 +++
> src/audio_output/dec.c           | 206 +++++++++++++++++++++++++++++--
> src/audio_output/output.c        |  74 +++++++++++
> 3 files changed, 289 insertions(+), 8 deletions(-)
>
>diff --git a/src/audio_output/aout_internal.h
>b/src/audio_output/aout_internal.h
>index 12fa4dfad9..bab59977eb 100644
>--- a/src/audio_output/aout_internal.h
>+++ b/src/audio_output/aout_internal.h
>@@ -47,6 +47,8 @@ typedef struct
>     aout_filters_t *filters;
>     aout_volume_t *volume;
> 
>+    bool stream_saved;
>+
>     struct
>     {
>         vlc_mutex_t lock;
>@@ -85,6 +87,17 @@ typedef struct
>     atomic_uint buffers_played;
>     atomic_uchar restart;
> 
>+    bool gapless_enabled;
>+    enum
>+    {
>+        GAPLESS_STATE_NONE,
>+        GAPLESS_STATE_DRAINED,
>+        GAPLESS_STATE_STOPPED,
>+        GAPLESS_STATE_STARTED,
>+    } gapless_state;
>+    block_t *gapless_block_chain;
>+    block_t **gapless_block_last;
>+
>     atomic_uintptr_t refs;
> } aout_owner_t;
> 
>@@ -120,6 +133,10 @@ void aout_Destroy (audio_output_t *);
> int aout_OutputNew(audio_output_t *, audio_sample_format_t *,
>                    aout_filters_cfg_t *filters_cfg);
> void aout_OutputDelete( audio_output_t * p_aout );
>+void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled);
>+bool aout_OutputIsGaplessEnabled(audio_output_t *aout);
>+int aout_OutputRecoverStream(audio_output_t *aout);
>+void aout_OutputSaveStream(audio_output_t *aout);
> 
> 
> /* From common.c : */
>diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c
>index 75ea4e6621..e4f1737776 100644
>--- a/src/audio_output/dec.c
>+++ b/src/audio_output/dec.c
>@@ -65,6 +65,44 @@ static bool aout_FormatEquals(const
>audio_sample_format_t *f1,
>         && f1->i_channels == f2->i_channels;
> }
> 
>+static int aout_GaplessReuseStream(audio_output_t *aout,
>+                                   const audio_sample_format_t
>*format,
>+                                   vlc_clock_t *clock,
>+                                   const audio_replay_gain_t
>*replay_gain)
>+{
>+    aout_owner_t *owner = aout_owner(aout);
>+
>+    if (aout_OutputRecoverStream(aout) != VLC_SUCCESS)
>+    {
>+        /* Unlikely case: The old stream was already stopped by the
>player */
>+        owner->gapless_state = GAPLESS_STATE_NONE;
>+        return -1;
>+    }
>+
>+    if (!aout_FormatEquals(format, &owner->input_format))
>+        goto fail;
>+
>+    /* Reuse the previous stream but set-up new clock and filters */
>+    owner->sync.clock = clock;
>+    owner->filters =
>+        aout_FiltersNewWithClock(VLC_OBJECT(aout), clock,
>+                                 format, &owner->mixer_format,
>+                                 &owner->filters_cfg);
>+    if (owner->filters == NULL)
>+        goto fail;
>+
>+    owner->volume = aout_volume_New (aout, replay_gain);
>+    owner->gapless_state = GAPLESS_STATE_STARTED;
>+    msg_Dbg(aout, "trying gapless transition");
>+    return 0;
>+fail:
>+    /* No gapless: drain and delete the old stream */
>+    aout_Drain(aout);
>+    aout_OutputDelete(aout);
>+    owner->gapless_state = GAPLESS_STATE_NONE;
>+    return -1;
>+}
>+
> /**
>  * Creates an audio output
>  */
>@@ -74,6 +112,16 @@ int aout_DecNew(audio_output_t *p_aout, const
>audio_sample_format_t *p_format,
>     assert(p_aout);
>     assert(p_format);
>     assert(clock);
>+
>+    aout_owner_t *owner = aout_owner(p_aout);
>+
>+    assert(owner->gapless_state == GAPLESS_STATE_NONE
>+        || owner->gapless_state == GAPLESS_STATE_STOPPED);
>+
>+    if (owner->gapless_state == GAPLESS_STATE_STOPPED
>+     && aout_GaplessReuseStream(p_aout, p_format, clock,
>p_replay_gain) == 0)
>+        return 0;
>+
>     if( p_format->i_bitspersample > 0 )
>     {
> /* Sanitize audio format, input need to have a valid physical channels
>@@ -100,8 +148,6 @@ int aout_DecNew(audio_output_t *p_aout, const
>audio_sample_format_t *p_format,
>         return -1;
>     }
> 
>-    aout_owner_t *owner = aout_owner(p_aout);
>-
>     /* Create the audio output stream */
>     owner->volume = aout_volume_New (p_aout, p_replay_gain);
> 
>@@ -147,11 +193,25 @@ void aout_DecDelete (audio_output_t *aout)
> {
>     aout_owner_t *owner = aout_owner (aout);
> 
>+    assert(owner->gapless_state == GAPLESS_STATE_NONE
>+        || owner->gapless_state == GAPLESS_STATE_DRAINED);
>+
>     if (owner->mixer_format.i_format)
>     {
>-        aout_DecFlush(aout);
>-        aout_FiltersDelete (aout, owner->filters);
>-        aout_OutputDelete (aout);
>+        if (owner->gapless_state == GAPLESS_STATE_DRAINED)
>+        {
>+            /* Gapless transition: save the stream for the next
>aout_DecNew
>+             * call */
>+            aout_OutputSaveStream(aout);
>+            owner->gapless_state = GAPLESS_STATE_STOPPED;
>+            aout_FiltersDelete (aout, owner->filters);
>+        }
>+        else
>+        {
>+            aout_DecFlush(aout);
>+            aout_FiltersDelete (aout, owner->filters);
>+            aout_OutputDelete (aout);
>+        }
>     }
>     aout_volume_Delete (owner->volume);
>     owner->volume = NULL;
>@@ -206,6 +266,7 @@ static int aout_CheckReady (audio_output_t *aout)
>             }
>          aout_FiltersSetClockDelay(owner->filters, owner->sync.delay);
>         }
>+        owner->gapless_state = GAPLESS_STATE_NONE;
>   /* 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); */
>@@ -426,6 +487,89 @@ static void aout_Play(audio_output_t *aout,
>block_t *block,
>atomic_fetch_add_explicit(&owner->buffers_played, 1,
>memory_order_relaxed);
> }
> 
>+static void aout_GaplessPlay(audio_output_t *aout, block_t *block,
>+                             vlc_tick_t system_now, vlc_tick_t
>original_pts)
>+{
>+    aout_owner_t *owner = aout_owner (aout);
>+    vlc_tick_t delay;
>+
>+    assert(owner->gapless_state == GAPLESS_STATE_STARTED);
>+
>+    /* Save blocks until we reach the new clock jitter delay or until
>we cancel
>+     * gapless due to timing issues */
>+    if (block != NULL)
>+    {
>+        block->i_dts = original_pts;
>+        block_ChainLastAppend(&owner->gapless_block_last, block);
>+    }
>+
>+    if (aout->time_get(aout, &delay) != 0)
>+    {
>+        msg_Warn(aout, "aout delay invalid: canceling gapless");
>+        goto cancel;
>+    }
>+
>+    const vlc_tick_t play_date =
>+        vlc_clock_ConvertToSystem(owner->sync.clock, system_now, 0,
>+                                  owner->sync.rate);
>+    const vlc_tick_t jitter = play_date - system_now;
>+
>+    if (delay <= jitter)
>+    {
>+        msg_Warn(aout, "clock jitter higher than aout delay: canceling
>gapless");
>+        goto cancel;
>+    }
>+
>+    if (jitter >= 0 && block != NULL)
>+        return; /* need more blocks */
>+
>+    if (delay <= AOUT_MAX_PTS_ADVANCE)
>+    {
>+        msg_Warn(aout, "delay too short: canceling gapless");
>+        goto cancel;
>+    }
>+
>+    if (owner->gapless_block_chain)
>+    {
>+        const vlc_tick_t block_original_pts =
>owner->gapless_block_chain->i_dts;
>+        const vlc_tick_t drift =
>+            vlc_clock_Update(owner->sync.clock, system_now + delay,
>+                             block_original_pts, owner->sync.rate);
>+        if (drift != 0)
>+        {
>+            msg_Warn(aout, "can't update clock: canceling gapless");
>+            goto cancel;
>+        }
>+    }
>+
>+    msg_Dbg(aout, "gapless transition successful");
>+    owner->gapless_state = GAPLESS_STATE_NONE;
>+    owner->sync.discontinuity = false;
>+
>+cancel:
>+    if (owner->gapless_state != GAPLESS_STATE_NONE)
>+    {
>+        /* Gapless canceled: drain and flush the previous stream */
>+        aout_Drain(aout);
>+        aout_DecFlush(aout);
>+        owner->gapless_state = GAPLESS_STATE_NONE;
>+    }
>+
>+    /* Play every saved blocks now */
>+    for (block_t *next = NULL, *it = owner->gapless_block_chain; it;
>it = next)
>+    {
>+        next = it->p_next;
>+        const vlc_tick_t block_original_pts = it->i_dts;
>+        it->i_dts = VLC_TICK_INVALID;
>+
>+        aout_Play(aout, it, system_now, block_original_pts);
>+        system_now = vlc_tick_now(); /* system_now might be too old */
>+    }
>+
>+    owner->gapless_block_chain = NULL;
>+    owner->gapless_block_last = &owner->gapless_block_chain;
>+}
>+
>/*****************************************************************************
>  * aout_DecPlay : filter & mix the decoded buffer
>*****************************************************************************/
>@@ -433,6 +577,17 @@ int aout_DecPlay(audio_output_t *aout, block_t
>*block)
> {
>     aout_owner_t *owner = aout_owner (aout);
> 
>+    if (unlikely(owner->gapless_state == GAPLESS_STATE_DRAINED))
>+    {
>+        /* Drained but not stopped: do the drain that was previously
>ignored
>+         * and cancel the gapless transition */
>+        aout_Drain(aout);
>+        owner->gapless_state = GAPLESS_STATE_NONE;
>+    }
>+
>+    assert(owner->gapless_state == GAPLESS_STATE_NONE
>+        || owner->gapless_state == GAPLESS_STATE_STARTED);
>+
>     assert (block->i_pts != VLC_TICK_INVALID);
> 
>     block->i_length = vlc_tick_from_samples( block->i_nb_samples,
>@@ -476,7 +631,8 @@ int aout_DecPlay(audio_output_t *aout, block_t
>*block)
>     aout_volume_Amplify (owner->volume, block);
> 
>     /* Update delay */
>-    if (owner->sync.request_delay != owner->sync.delay)
>+    if (owner->gapless_state == GAPLESS_STATE_NONE
>+     && owner->sync.request_delay != owner->sync.delay)
>     {
>         owner->sync.delay = owner->sync.request_delay;
>vlc_tick_t delta = vlc_clock_SetDelay(owner->sync.clock,
>owner->sync.delay);
>@@ -486,7 +642,12 @@ int aout_DecPlay(audio_output_t *aout, block_t
>*block)
>     }
> 
>     vlc_tick_t system_now = vlc_tick_now();
>-    aout_Play(aout, block, system_now, original_pts);
>+
>+    if (owner->gapless_state == GAPLESS_STATE_STARTED)
>+        aout_GaplessPlay(aout, block, system_now, original_pts);
>+    else
>+        aout_Play(aout, block, system_now, original_pts);
>+
>     return ret;
> drop:
>     owner->sync.discontinuity = true;
>@@ -540,6 +701,15 @@ void aout_DecFlush(audio_output_t *aout)
> 
>     if (owner->mixer_format.i_format)
>     {
>+        if (owner->gapless_state != GAPLESS_STATE_NONE)
>+        {
>+            /* Flush gapless buffers and reset state */
>+            block_ChainRelease(owner->gapless_block_chain);
>+            owner->gapless_block_chain = NULL;
>+            owner->gapless_block_last = &owner->gapless_block_chain;
>+            owner->gapless_state = GAPLESS_STATE_NONE;
>+        }
>+
>         aout_FiltersFlush (owner->filters);
> 
>         aout->flush(aout);
>@@ -571,11 +741,31 @@ void aout_DecDrain(audio_output_t *aout)
>     if (!owner->mixer_format.i_format)
>         return;
> 
>+    assert(owner->gapless_state == GAPLESS_STATE_NONE
>+        || owner->gapless_state == GAPLESS_STATE_STARTED);
>+
>+    if (owner->gapless_state == GAPLESS_STATE_STARTED)
>+    {
>+        /* Drain gapless block chain */
>+        aout_GaplessPlay(aout, NULL, vlc_tick_now(),
>VLC_TICK_INVALID);
>+    }
>+
>     block_t *block = aout_FiltersDrain (owner->filters);
>     if (block)
>         aout->play(aout, block, vlc_tick_now());
> 
>-    aout_Drain(aout);
>+    if (AOUT_FMT_LINEAR(&owner->mixer_format)
>+     && aout_OutputIsGaplessEnabled(aout))
>+    {
>+
>+        /* First gapless state, cancellable if stop/play/flush is not
>called
>+         * right after */
>+        owner->gapless_state = GAPLESS_STATE_DRAINED;
>+        owner->gapless_block_chain = NULL;
>+        owner->gapless_block_last = &owner->gapless_block_chain;
>+    }
>+    else
>+        aout_Drain(aout);
> 
>     vlc_clock_Reset(owner->sync.clock);
>     aout_FiltersResetClock(owner->filters);
>diff --git a/src/audio_output/output.c b/src/audio_output/output.c
>index 5c378dbb31..6f11628a52 100644
>--- a/src/audio_output/output.c
>+++ b/src/audio_output/output.c
>@@ -223,6 +223,9 @@ audio_output_t *aout_New (vlc_object_t *parent)
>     vlc_viewpoint_init (&owner->vp.value);
>     atomic_init (&owner->vp.update, false);
>     atomic_init(&owner->refs, 0);
>+    owner->stream_saved = false;
>+    owner->gapless_enabled = false;
>+    owner->gapless_state = GAPLESS_STATE_NONE;
> 
>     /* Audio output module callbacks */
>     var_Create (aout, "volume", VLC_VAR_FLOAT);
>@@ -565,6 +568,7 @@ int aout_OutputNew (audio_output_t *aout,
>audio_sample_format_t *restrict fmt,
>     aout->current_sink_info.headphones = false;
> 
>     vlc_mutex_lock(&owner->lock);
>+    assert(!owner->stream_saved);
>     int ret = aout->start(aout, fmt);
>    assert(aout->flush && aout->play && aout->time_get && aout->pause);
>     vlc_mutex_unlock(&owner->lock);
>@@ -592,10 +596,80 @@ void aout_OutputDelete (audio_output_t *aout)
> {
>     aout_owner_t *owner = aout_owner(aout);
>     vlc_mutex_lock(&owner->lock);
>+    assert(!owner->stream_saved);
>     aout->stop (aout);
>     vlc_mutex_unlock(&owner->lock);
> }
> 
>+/**
>+ * Recover the ownership of the stream previously saved
>+ */
>+int aout_OutputRecoverStream(audio_output_t *aout)
>+{
>+    aout_owner_t *owner = aout_owner(aout);
>+    vlc_mutex_lock(&owner->lock);
>+    if (unlikely(!owner->stream_saved))
>+    {
>+        /* The stream was already stopped by a previous
>+         * aout_OutputSetGaplessEnabled(false) from an other thread */
>+        vlc_mutex_unlock(&owner->lock);
>+        return VLC_EGENERIC;
>+    }
>+
>+    owner->stream_saved = false;
>+    vlc_mutex_unlock(&owner->lock);
>+
>+    return VLC_SUCCESS;
>+}
>+
>+/**
>+ * Save the stream for a future usage
>+ */
>+void aout_OutputSaveStream(audio_output_t *aout)
>+{
>+    aout_owner_t *owner = aout_owner(aout);
>+
>+    vlc_mutex_lock(&owner->lock);
>+
>+    assert(!owner->stream_saved);
>+
>+    /* The caller of aout_OutputSetGaplessEnabled (the player) is now
>the owner
>+     * of the stream during gapless transition. */
>+    owner->stream_saved = true;
>+    vlc_mutex_unlock(&owner->lock);
>+}
>+
>+/**
>+ * Enable or disable gapless
>+ *
>+ * Can be called by the player when the next media changes
>+ */
>+void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled)
>+{
>+    aout_owner_t *owner = aout_owner (aout);
>+
>+    vlc_mutex_lock(&owner->lock);
>+
>+    owner->gapless_enabled = enabled;
>+
>+    if (!enabled && unlikely(owner->stream_saved))
>+    {
>+        aout->stop (aout);
>+        owner->stream_saved = false;
>+    }
>+
>+    vlc_mutex_unlock(&owner->lock);
>+}
>+
>+bool aout_OutputIsGaplessEnabled(audio_output_t *aout)
>+{
>+    aout_owner_t *owner = aout_owner(aout);
>+    vlc_mutex_lock(&owner->lock);
>+    const bool enabled = owner->gapless_enabled;
>+    vlc_mutex_unlock(&owner->lock);
>+    return enabled;
>+}
>+
> /**
>  * Gets the volume of the audio output stream (independent of mute).
>  * \return Current audio volume (0. = silent, 1. = nominal),
>-- 
>2.20.1
>
>_______________________________________________
>vlc-devel mailing list
>To unsubscribe or modify your subscription options:
>https://mailman.videolan.org/listinfo/vlc-devel

-- 
Envoyé de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma brièveté.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20190321/c2f52c4b/attachment.html>


More information about the vlc-devel mailing list