<!doctype html><html><head><title></title><style type="text/css">p.MsoNormal,p.MsoNoSpacing{margin:0}</style></head><body>No. You are making unwarranted assumptions, and not just about no audio ES. I cannot agree.<br><br><div class="gmail_quote">Le 21 mars 2019 20:49:49 GMT+08:00, Thomas Guillem <thomas@gllm.fr> a écrit :<blockquote class="gmail_quote" style="margin: 0pt 0pt 0pt 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;">
<div><br></div><div>On Thu, Mar 21, 2019, at 13:42, Rémi Denis-Courmont wrote:<br></div><blockquote type="cite" id="fastmail-quoted"><div>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.<br></div></blockquote><div><br></div><div>Not obvious at all. It works for most common case: playing local or network music album. The buffer of the audio output is generally around 500ms before a drain, this leave a *lot* of time to open the next input. I can even play a music cut into several 200ms pieces without any problem.<br></div><div>And for the case where the audio buffer size is not enough ? It's handled: gapless will be canceled (drain/flush last stream and open the new one).<br></div><div><br></div><blockquote type="cite" id="fastmail-quoted"><div><br></div><div>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.<br></div></blockquote><div><br></div><div>My current branch handle the case when PTS are not continuous or if format won't match.<br></div><div><br></div><div>Only your last point is not handled. This last point need to have concurrent input: activate gapless only if the next media is sure to have an active audio ES.<br></div><div><br></div><blockquote type="cite" id="fastmail-quoted"><div><br></div><div class="fastmail-quoted-gmail_quote"><div>Le 21 mars 2019 01:09:10 GMT+08:00, Thomas Guillem <thomas@gllm.fr> a écrit :<br></div><blockquote style="margin-top:0pt;margin-right:0pt;margin-bottom:0pt;margin-left:0.8ex;border-left-color:rgb(204, 204, 204);border-left-style:solid;border-left-width:1px;padding-left:1ex;" class="fastmail-quoted-gmail_quote"><pre class="fastmail-quoted-k9mail"><div>The player will call aout_OutputSetGaplessEnabled(true|false) when there is a<br></div><div>next media to play or not. This call is protected with the same mutex used for<br></div><div>start/stop and can be called from anywhere.<br></div><div><br></div><div>Then, when the stream is draining (aout_DecDrain), it will postpone the actual<br></div><div>drain if gapless is enabled, and do the following depending on the next aout<br></div><div>call:<br></div><div><br></div><div> - if play is called just after, the postponed drain is executed and the<br></div><div>   gapless state is canceled.<br></div><div> - if flush is called, the gapless state is canceled and everything is flushed<br></div><div> - if stop is called, filters are cleaned but the stream is not stopped, it is<br></div><div>   saved for a future usage: (via aout_OutputSaveStream()).<br></div><div><br></div><div>On the next stream start, it will try to recover the previous stream (that<br></div><div>could be stopped if the player disabled gapless in the meantime) and will do<br></div><div>the following depending on the aout_DecNew parameters:<br></div><div><br></div><div> - if format are different: cancel gapless state, drain, flush and stop the<br></div><div>   previous stream.<br></div><div><br></div><div> - if format are equals: use the previous stream (don't call aout->stop(),<br></div><div>   aout->start()), and set-up new filters chain with the new clock.<br></div><div><br></div><div>The final gapless transition will be done from aout_DecPlay():<br></div><div><br></div><div> - if the previous stream delay (time_get) is compatible with the next clock,<br></div><div>   blocks will be added in a temporary buffer until the clock jitter delay is<br></div><div>   reached.<br></div><div> - if not, cancel the gapless state, drain and flush the (previous=current)<br></div><div>   stream (but don't restart it), and play all blocks normally.<br></div><div><br></div><div>When the jitter delay is reached, blocks added in the temporary buffers can be<br></div><div>played and the playback can resume normally.<br></div><div><br></div><div>Fixes #549<hr> src/audio_output/aout_internal.h |  17 +++<br></div><div> src/audio_output/dec.c           | 206 +++++++++++++++++++++++++++++--<br></div><div> src/audio_output/output.c        |  74 +++++++++++<br></div><div> 3 files changed, 289 insertions(+), 8 deletions(-)<br></div><div><br></div><div>diff --git a/src/audio_output/aout_internal.h b/src/audio_output/aout_internal.h<br></div><div>index 12fa4dfad9..bab59977eb 100644<br></div><div>--- a/src/audio_output/aout_internal.h<br></div><div>+++ b/src/audio_output/aout_internal.h<br></div><div>@@ -47,6 +47,8 @@ typedef struct<br></div><div>     aout_filters_t *filters;<br></div><div>     aout_volume_t *volume;<br></div><div> <br></div><div>+    bool stream_saved;<br></div><div>+<br></div><div>     struct<br></div><div>     {<br></div><div>         vlc_mutex_t lock;<br></div><div>@@ -85,6 +87,17 @@ typedef struct<br></div><div>     atomic_uint buffers_played;<br></div><div>     atomic_uchar restart;<br></div><div> <br></div><div>+    bool gapless_enabled;<br></div><div>+    enum<br></div><div>+    {<br></div><div>+        GAPLESS_STATE_NONE,<br></div><div>+        GAPLESS_STATE_DRAINED,<br></div><div>+        GAPLESS_STATE_STOPPED,<br></div><div>+        GAPLESS_STATE_STARTED,<br></div><div>+    } gapless_state;<br></div><div>+    block_t *gapless_block_chain;<br></div><div>+    block_t **gapless_block_last;<br></div><div>+<br></div><div>     atomic_uintptr_t refs;<br></div><div> } aout_owner_t;<br></div><div> <br></div><div>@@ -120,6 +133,10 @@ void aout_Destroy (audio_output_t *);<br></div><div> int aout_OutputNew(audio_output_t *, audio_sample_format_t *,<br></div><div>                    aout_filters_cfg_t *filters_cfg);<br></div><div> void aout_OutputDelete( audio_output_t * p_aout );<br></div><div>+void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled);<br></div><div>+bool aout_OutputIsGaplessEnabled(audio_output_t *aout);<br></div><div>+int aout_OutputRecoverStream(audio_output_t *aout);<br></div><div>+void aout_OutputSaveStream(audio_output_t *aout);<br></div><div> <br></div><div> <br></div><div> /* From common.c : */<br></div><div>diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c<br></div><div>index 75ea4e6621..e4f1737776 100644<br></div><div>--- a/src/audio_output/dec.c<br></div><div>+++ b/src/audio_output/dec.c<br></div><div>@@ -65,6 +65,44 @@ static bool aout_FormatEquals(const audio_sample_format_t *f1,<br></div><div>         && f1->i_channels == f2->i_channels;<br></div><div> }<br></div><div> <br></div><div>+static int aout_GaplessReuseStream(audio_output_t *aout,<br></div><div>+                                   const audio_sample_format_t *format,<br></div><div>+                                   vlc_clock_t *clock,<br></div><div>+                                   const audio_replay_gain_t *replay_gain)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner(aout);<br></div><div>+<br></div><div>+    if (aout_OutputRecoverStream(aout) != VLC_SUCCESS)<br></div><div>+    {<br></div><div>+        /* Unlikely case: The old stream was already stopped by the player */<br></div><div>+        owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+        return -1;<br></div><div>+    }<br></div><div>+<br></div><div>+    if (!aout_FormatEquals(format, &owner->input_format))<br></div><div>+        goto fail;<br></div><div>+<br></div><div>+    /* Reuse the previous stream but set-up new clock and filters */<br></div><div>+    owner->sync.clock = clock;<br></div><div>+    owner->filters =<br></div><div>+        aout_FiltersNewWithClock(VLC_OBJECT(aout), clock,<br></div><div>+                                 format, &owner->mixer_format,<br></div><div>+                                 &owner->filters_cfg);<br></div><div>+    if (owner->filters == NULL)<br></div><div>+        goto fail;<br></div><div>+<br></div><div>+    owner->volume = aout_volume_New (aout, replay_gain);<br></div><div>+    owner->gapless_state = GAPLESS_STATE_STARTED;<br></div><div>+    msg_Dbg(aout, "trying gapless transition");<br></div><div>+    return 0;<br></div><div>+fail:<br></div><div>+    /* No gapless: drain and delete the old stream */<br></div><div>+    aout_Drain(aout);<br></div><div>+    aout_OutputDelete(aout);<br></div><div>+    owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+    return -1;<br></div><div>+}<br></div><div>+<br></div><div> /**<br></div><div>  * Creates an audio output<br></div><div>  */<br></div><div>@@ -74,6 +112,16 @@ int aout_DecNew(audio_output_t *p_aout, const audio_sample_format_t *p_format,<br></div><div>     assert(p_aout);<br></div><div>     assert(p_format);<br></div><div>     assert(clock);<br></div><div>+<br></div><div>+    aout_owner_t *owner = aout_owner(p_aout);<br></div><div>+<br></div><div>+    assert(owner->gapless_state == GAPLESS_STATE_NONE<br></div><div>+        || owner->gapless_state == GAPLESS_STATE_STOPPED);<br></div><div>+<br></div><div>+    if (owner->gapless_state == GAPLESS_STATE_STOPPED<br></div><div>+     && aout_GaplessReuseStream(p_aout, p_format, clock, p_replay_gain) == 0)<br></div><div>+        return 0;<br></div><div>+<br></div><div>     if( p_format->i_bitspersample > 0 )<br></div><div>     {<br></div><div>         /* Sanitize audio format, input need to have a valid physical channels<br></div><div>@@ -100,8 +148,6 @@ int aout_DecNew(audio_output_t *p_aout, const audio_sample_format_t *p_format,<br></div><div>         return -1;<br></div><div>     }<br></div><div> <br></div><div>-    aout_owner_t *owner = aout_owner(p_aout);<br></div><div>-<br></div><div>     /* Create the audio output stream */<br></div><div>     owner->volume = aout_volume_New (p_aout, p_replay_gain);<br></div><div> <br></div><div>@@ -147,11 +193,25 @@ void aout_DecDelete (audio_output_t *aout)<br></div><div> {<br></div><div>     aout_owner_t *owner = aout_owner (aout);<br></div><div> <br></div><div>+    assert(owner->gapless_state == GAPLESS_STATE_NONE<br></div><div>+        || owner->gapless_state == GAPLESS_STATE_DRAINED);<br></div><div>+<br></div><div>     if (owner->mixer_format.i_format)<br></div><div>     {<br></div><div>-        aout_DecFlush(aout);<br></div><div>-        aout_FiltersDelete (aout, owner->filters);<br></div><div>-        aout_OutputDelete (aout);<br></div><div>+        if (owner->gapless_state == GAPLESS_STATE_DRAINED)<br></div><div>+        {<br></div><div>+            /* Gapless transition: save the stream for the next aout_DecNew<br></div><div>+             * call */<br></div><div>+            aout_OutputSaveStream(aout);<br></div><div>+            owner->gapless_state = GAPLESS_STATE_STOPPED;<br></div><div>+            aout_FiltersDelete (aout, owner->filters);<br></div><div>+        }<br></div><div>+        else<br></div><div>+        {<br></div><div>+            aout_DecFlush(aout);<br></div><div>+            aout_FiltersDelete (aout, owner->filters);<br></div><div>+            aout_OutputDelete (aout);<br></div><div>+        }<br></div><div>     }<br></div><div>     aout_volume_Delete (owner->volume);<br></div><div>     owner->volume = NULL;<br></div><div>@@ -206,6 +266,7 @@ static int aout_CheckReady (audio_output_t *aout)<br></div><div>             }<br></div><div>             aout_FiltersSetClockDelay(owner->filters, owner->sync.delay);<br></div><div>         }<br></div><div>+        owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>         /* TODO: This would be a good time to call clean up any video output<br></div><div>          * left over by an audio visualization:<br></div><div>         input_resource_TerminatVout(MAGIC HERE); */<br></div><div>@@ -426,6 +487,89 @@ static void aout_Play(audio_output_t *aout, block_t *block,<br></div><div>     atomic_fetch_add_explicit(&owner->buffers_played, 1, memory_order_relaxed);<br></div><div> }<br></div><div> <br></div><div>+static void aout_GaplessPlay(audio_output_t *aout, block_t *block,<br></div><div>+                             vlc_tick_t system_now, vlc_tick_t original_pts)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner (aout);<br></div><div>+    vlc_tick_t delay;<br></div><div>+<br></div><div>+    assert(owner->gapless_state == GAPLESS_STATE_STARTED);<br></div><div>+<br></div><div>+    /* Save blocks until we reach the new clock jitter delay or until we cancel<br></div><div>+     * gapless due to timing issues */<br></div><div>+    if (block != NULL)<br></div><div>+    {<br></div><div>+        block->i_dts = original_pts;<br></div><div>+        block_ChainLastAppend(&owner->gapless_block_last, block);<br></div><div>+    }<br></div><div>+<br></div><div>+    if (aout->time_get(aout, &delay) != 0)<br></div><div>+    {<br></div><div>+        msg_Warn(aout, "aout delay invalid: canceling gapless");<br></div><div>+        goto cancel;<br></div><div>+    }<br></div><div>+<br></div><div>+    const vlc_tick_t play_date =<br></div><div>+        vlc_clock_ConvertToSystem(owner->sync.clock, system_now, 0,<br></div><div>+                                  owner->sync.rate);<br></div><div>+    const vlc_tick_t jitter = play_date - system_now;<br></div><div>+<br></div><div>+    if (delay <= jitter)<br></div><div>+    {<br></div><div>+        msg_Warn(aout, "clock jitter higher than aout delay: canceling gapless");<br></div><div>+        goto cancel;<br></div><div>+    }<br></div><div>+<br></div><div>+    if (jitter >= 0 && block != NULL)<br></div><div>+        return; /* need more blocks */<br></div><div>+<br></div><div>+    if (delay <= AOUT_MAX_PTS_ADVANCE)<br></div><div>+    {<br></div><div>+        msg_Warn(aout, "delay too short: canceling gapless");<br></div><div>+        goto cancel;<br></div><div>+    }<br></div><div>+<br></div><div>+    if (owner->gapless_block_chain)<br></div><div>+    {<br></div><div>+        const vlc_tick_t block_original_pts = owner->gapless_block_chain->i_dts;<br></div><div>+        const vlc_tick_t drift =<br></div><div>+            vlc_clock_Update(owner->sync.clock, system_now + delay,<br></div><div>+                             block_original_pts, owner->sync.rate);<br></div><div>+        if (drift != 0)<br></div><div>+        {<br></div><div>+            msg_Warn(aout, "can't update clock: canceling gapless");<br></div><div>+            goto cancel;<br></div><div>+        }<br></div><div>+    }<br></div><div>+<br></div><div>+    msg_Dbg(aout, "gapless transition successful");<br></div><div>+    owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+    owner->sync.discontinuity = false;<br></div><div>+<br></div><div>+cancel:<br></div><div>+    if (owner->gapless_state != GAPLESS_STATE_NONE)<br></div><div>+    {<br></div><div>+        /* Gapless canceled: drain and flush the previous stream */<br></div><div>+        aout_Drain(aout);<br></div><div>+        aout_DecFlush(aout);<br></div><div>+        owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+    }<br></div><div>+<br></div><div>+    /* Play every saved blocks now */<br></div><div>+    for (block_t *next = NULL, *it = owner->gapless_block_chain; it; it = next)<br></div><div>+    {<br></div><div>+        next = it->p_next;<br></div><div>+        const vlc_tick_t block_original_pts = it->i_dts;<br></div><div>+        it->i_dts = VLC_TICK_INVALID;<br></div><div>+<br></div><div>+        aout_Play(aout, it, system_now, block_original_pts);<br></div><div>+        system_now = vlc_tick_now(); /* system_now might be too old */<br></div><div>+    }<br></div><div>+<br></div><div>+    owner->gapless_block_chain = NULL;<br></div><div>+    owner->gapless_block_last = &owner->gapless_block_chain;<br></div><div>+}<br></div><div>+<br></div><div> /*****************************************************************************<br></div><div>  * aout_DecPlay : filter & mix the decoded buffer<br></div><div>  *****************************************************************************/<br></div><div>@@ -433,6 +577,17 @@ int aout_DecPlay(audio_output_t *aout, block_t *block)<br></div><div> {<br></div><div>     aout_owner_t *owner = aout_owner (aout);<br></div><div> <br></div><div>+    if (unlikely(owner->gapless_state == GAPLESS_STATE_DRAINED))<br></div><div>+    {<br></div><div>+        /* Drained but not stopped: do the drain that was previously ignored<br></div><div>+         * and cancel the gapless transition */<br></div><div>+        aout_Drain(aout);<br></div><div>+        owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+    }<br></div><div>+<br></div><div>+    assert(owner->gapless_state == GAPLESS_STATE_NONE<br></div><div>+        || owner->gapless_state == GAPLESS_STATE_STARTED);<br></div><div>+<br></div><div>     assert (block->i_pts != VLC_TICK_INVALID);<br></div><div> <br></div><div>     block->i_length = vlc_tick_from_samples( block->i_nb_samples,<br></div><div>@@ -476,7 +631,8 @@ int aout_DecPlay(audio_output_t *aout, block_t *block)<br></div><div>     aout_volume_Amplify (owner->volume, block);<br></div><div> <br></div><div>     /* Update delay */<br></div><div>-    if (owner->sync.request_delay != owner->sync.delay)<br></div><div>+    if (owner->gapless_state == GAPLESS_STATE_NONE<br></div><div>+     && owner->sync.request_delay != owner->sync.delay)<br></div><div>     {<br></div><div>         owner->sync.delay = owner->sync.request_delay;<br></div><div>         vlc_tick_t delta = vlc_clock_SetDelay(owner->sync.clock, owner->sync.delay);<br></div><div>@@ -486,7 +642,12 @@ int aout_DecPlay(audio_output_t *aout, block_t *block)<br></div><div>     }<br></div><div> <br></div><div>     vlc_tick_t system_now = vlc_tick_now();<br></div><div>-    aout_Play(aout, block, system_now, original_pts);<br></div><div>+<br></div><div>+    if (owner->gapless_state == GAPLESS_STATE_STARTED)<br></div><div>+        aout_GaplessPlay(aout, block, system_now, original_pts);<br></div><div>+    else<br></div><div>+        aout_Play(aout, block, system_now, original_pts);<br></div><div>+<br></div><div>     return ret;<br></div><div> drop:<br></div><div>     owner->sync.discontinuity = true;<br></div><div>@@ -540,6 +701,15 @@ void aout_DecFlush(audio_output_t *aout)<br></div><div> <br></div><div>     if (owner->mixer_format.i_format)<br></div><div>     {<br></div><div>+        if (owner->gapless_state != GAPLESS_STATE_NONE)<br></div><div>+        {<br></div><div>+            /* Flush gapless buffers and reset state */<br></div><div>+            block_ChainRelease(owner->gapless_block_chain);<br></div><div>+            owner->gapless_block_chain = NULL;<br></div><div>+            owner->gapless_block_last = &owner->gapless_block_chain;<br></div><div>+            owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div>+        }<br></div><div>+<br></div><div>         aout_FiltersFlush (owner->filters);<br></div><div> <br></div><div>         aout->flush(aout);<br></div><div>@@ -571,11 +741,31 @@ void aout_DecDrain(audio_output_t *aout)<br></div><div>     if (!owner->mixer_format.i_format)<br></div><div>         return;<br></div><div> <br></div><div>+    assert(owner->gapless_state == GAPLESS_STATE_NONE<br></div><div>+        || owner->gapless_state == GAPLESS_STATE_STARTED);<br></div><div>+<br></div><div>+    if (owner->gapless_state == GAPLESS_STATE_STARTED)<br></div><div>+    {<br></div><div>+        /* Drain gapless block chain */<br></div><div>+        aout_GaplessPlay(aout, NULL, vlc_tick_now(), VLC_TICK_INVALID);<br></div><div>+    }<br></div><div>+<br></div><div>     block_t *block = aout_FiltersDrain (owner->filters);<br></div><div>     if (block)<br></div><div>         aout->play(aout, block, vlc_tick_now());<br></div><div> <br></div><div>-    aout_Drain(aout);<br></div><div>+    if (AOUT_FMT_LINEAR(&owner->mixer_format)<br></div><div>+     && aout_OutputIsGaplessEnabled(aout))<br></div><div>+    {<br></div><div>+<br></div><div>+        /* First gapless state, cancellable if stop/play/flush is not called<br></div><div>+         * right after */<br></div><div>+        owner->gapless_state = GAPLESS_STATE_DRAINED;<br></div><div>+        owner->gapless_block_chain = NULL;<br></div><div>+        owner->gapless_block_last = &owner->gapless_block_chain;<br></div><div>+    }<br></div><div>+    else<br></div><div>+        aout_Drain(aout);<br></div><div> <br></div><div>     vlc_clock_Reset(owner->sync.clock);<br></div><div>     aout_FiltersResetClock(owner->filters);<br></div><div>diff --git a/src/audio_output/output.c b/src/audio_output/output.c<br></div><div>index 5c378dbb31..6f11628a52 100644<br></div><div>--- a/src/audio_output/output.c<br></div><div>+++ b/src/audio_output/output.c<br></div><div>@@ -223,6 +223,9 @@ audio_output_t *aout_New (vlc_object_t *parent)<br></div><div>     vlc_viewpoint_init (&owner->vp.value);<br></div><div>     atomic_init (&owner->vp.update, false);<br></div><div>     atomic_init(&owner->refs, 0);<br></div><div>+    owner->stream_saved = false;<br></div><div>+    owner->gapless_enabled = false;<br></div><div>+    owner->gapless_state = GAPLESS_STATE_NONE;<br></div><div> <br></div><div>     /* Audio output module callbacks */<br></div><div>     var_Create (aout, "volume", VLC_VAR_FLOAT);<br></div><div>@@ -565,6 +568,7 @@ int aout_OutputNew (audio_output_t *aout, audio_sample_format_t *restrict fmt,<br></div><div>     aout->current_sink_info.headphones = false;<br></div><div> <br></div><div>     vlc_mutex_lock(&owner->lock);<br></div><div>+    assert(!owner->stream_saved);<br></div><div>     int ret = aout->start(aout, fmt);<br></div><div>     assert(aout->flush && aout->play && aout->time_get && aout->pause);<br></div><div>     vlc_mutex_unlock(&owner->lock);<br></div><div>@@ -592,10 +596,80 @@ void aout_OutputDelete (audio_output_t *aout)<br></div><div> {<br></div><div>     aout_owner_t *owner = aout_owner(aout);<br></div><div>     vlc_mutex_lock(&owner->lock);<br></div><div>+    assert(!owner->stream_saved);<br></div><div>     aout->stop (aout);<br></div><div>     vlc_mutex_unlock(&owner->lock);<br></div><div> }<br></div><div> <br></div><div>+/**<br></div><div>+ * Recover the ownership of the stream previously saved<br></div><div>+ */<br></div><div>+int aout_OutputRecoverStream(audio_output_t *aout)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner(aout);<br></div><div>+    vlc_mutex_lock(&owner->lock);<br></div><div>+    if (unlikely(!owner->stream_saved))<br></div><div>+    {<br></div><div>+        /* The stream was already stopped by a previous<br></div><div>+         * aout_OutputSetGaplessEnabled(false) from an other thread */<br></div><div>+        vlc_mutex_unlock(&owner->lock);<br></div><div>+        return VLC_EGENERIC;<br></div><div>+    }<br></div><div>+<br></div><div>+    owner->stream_saved = false;<br></div><div>+    vlc_mutex_unlock(&owner->lock);<br></div><div>+<br></div><div>+    return VLC_SUCCESS;<br></div><div>+}<br></div><div>+<br></div><div>+/**<br></div><div>+ * Save the stream for a future usage<br></div><div>+ */<br></div><div>+void aout_OutputSaveStream(audio_output_t *aout)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner(aout);<br></div><div>+<br></div><div>+    vlc_mutex_lock(&owner->lock);<br></div><div>+<br></div><div>+    assert(!owner->stream_saved);<br></div><div>+<br></div><div>+    /* The caller of aout_OutputSetGaplessEnabled (the player) is now the owner<br></div><div>+     * of the stream during gapless transition. */<br></div><div>+    owner->stream_saved = true;<br></div><div>+    vlc_mutex_unlock(&owner->lock);<br></div><div>+}<br></div><div>+<br></div><div>+/**<br></div><div>+ * Enable or disable gapless<br></div><div>+ *<br></div><div>+ * Can be called by the player when the next media changes<br></div><div>+ */<br></div><div>+void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner (aout);<br></div><div>+<br></div><div>+    vlc_mutex_lock(&owner->lock);<br></div><div>+<br></div><div>+    owner->gapless_enabled = enabled;<br></div><div>+<br></div><div>+    if (!enabled && unlikely(owner->stream_saved))<br></div><div>+    {<br></div><div>+        aout->stop (aout);<br></div><div>+        owner->stream_saved = false;<br></div><div>+    }<br></div><div>+<br></div><div>+    vlc_mutex_unlock(&owner->lock);<br></div><div>+}<br></div><div>+<br></div><div>+bool aout_OutputIsGaplessEnabled(audio_output_t *aout)<br></div><div>+{<br></div><div>+    aout_owner_t *owner = aout_owner(aout);<br></div><div>+    vlc_mutex_lock(&owner->lock);<br></div><div>+    const bool enabled = owner->gapless_enabled;<br></div><div>+    vlc_mutex_unlock(&owner->lock);<br></div><div>+    return enabled;<br></div><div>+}<br></div><div>+<br></div><div> /**<br></div><div>  * Gets the volume of the audio output stream (independent of mute).<br></div><div>  * \return Current audio volume (0. = silent, 1. = nominal),<br></div></pre></blockquote></div><div><br></div><div>-- <br></div><div>Envoyé de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma brièveté. <br></div><div>_______________________________________________<br></div><div>vlc-devel mailing list<br></div><div>To unsubscribe or modify your subscription options:<br></div><div>https://mailman.videolan.org/listinfo/vlc-devel<br></div></blockquote><div><br></div></blockquote></div><br>-- <br>Envoyé de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma brièveté.</body></html>