[vlc-commits] [Git][videolan/vlc][master] 10 commits: pulse: move drain in a new function

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Thu Nov 16 09:36:03 UTC 2023



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
d7eccba7 by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: move drain in a new function

No functional changes.

- - - - -
83967414 by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: move data_free up

No functional changes. For the next commit

- - - - -
4d868b52 by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: add stream_get_interpolated_latency()

This function interpolate the last timing. This will be used to rework
the draining and when switching to data callback.

An alternative is to use vlc_pa_get_latency() directly with the
PA_STREAM_INTERPOLATE_TIMING flag but timings can go in the past when
using this flag.

- - - - -
fb79d34d by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: simplify draining

No need to trigger the drain callback from the timing callback anymore
since we can now use stream_get_interpolated_latency().

- - - - -
ee4a520c by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: remove TriggerDrain()

Since it is now used only one time

- - - - -
25c17ac8 by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: earlier return in stream_drain

This specific case will be gone on the next commit.
No functional changes.

- - - - -
d08924ed by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: add VLC_TICK_0 to audio_ts timing

In the (very) unlikely case where rt is 0 (=> invalid for the  VLC clock).

- - - - -
7e9281cb by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: use pa_stream_set_write_callback()

Keep a fifo block in the module and send blocks to pulse via the
write_callback.

Pulse is now uncorked sooner (on the first play), silence is sent via
the data callback to reach for the start date.

This fixes overflow when sending more then 5 seconds of audio.

- - - - -
a720d19e by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: remove overflow handling

It is now less likely to happen.

It's better to do nothing if it happens since the writing is already
paced via the callback and pa_stream_writable_size().

- - - - -
eeef5936 by Thomas Guillem at 2023-11-16T09:10:49+00:00
pulse: earlier return in stream_latency_cb()

- - - - -


1 changed file:

- modules/audio_output/pulse.c


Changes:

=====================================
modules/audio_output/pulse.c
=====================================
@@ -65,11 +65,20 @@ typedef struct
     pa_stream *stream; /**< PulseAudio playback stream object */
     pa_context *context; /**< PulseAudio connection context */
     pa_threaded_mainloop *mainloop; /**< PulseAudio thread */
-    pa_time_event *trigger; /**< Deferred stream trigger */
     pa_time_event *drain_trigger; /**< Drain stream trigger */
     bool draining;
     pa_cvolume cvolume; /**< actual sink input volume */
-    vlc_tick_t last_date; /**< Play system timestamp of last buffer */
+
+    bool start_date_reached;
+    vlc_tick_t start_date;
+    size_t total_silence_bytes;
+
+    struct {
+        size_t size;
+        block_t **last;
+        block_t *first;
+    } fifo;
+
     pa_usec_t flush_rt;
 
     pa_volume_t volume_force; /**< Forced volume (stream must be NULL) */
@@ -77,8 +86,26 @@ typedef struct
     char *sink_force; /**< Forced sink name (stream must be NULL) */
 
     struct sink *sinks; /**< Locally-cached list of sinks */
+
+    vlc_tick_t timing_system_ts;
 } aout_sys_t;
 
+static vlc_tick_t stream_get_interpolated_latency(pa_stream *s,
+                                                  audio_output_t *aout,
+                                                  vlc_tick_t system_date)
+{
+    aout_sys_t *sys = aout->sys;
+
+    if (unlikely(sys->timing_system_ts == VLC_TICK_INVALID))
+        return 0;
+
+    vlc_tick_t latency = vlc_pa_get_latency(aout, sys->context, s);
+    if (unlikely(latency == VLC_TICK_INVALID))
+        return 0;
+
+    return latency + sys->timing_system_ts - system_date;
+}
+
 static void VolumeReport(audio_output_t *aout)
 {
     aout_sys_t *sys = aout->sys;
@@ -97,7 +124,6 @@ static void drain_trigger_cb(pa_mainloop_api *api, pa_time_event *e,
 
     vlc_pa_rttime_free(sys->mainloop, sys->drain_trigger);
     sys->drain_trigger = NULL;
-    sys->draining = false;
 
     aout_DrainedReport(aout);
     (void) api; (void) e; (void) tv;
@@ -109,21 +135,6 @@ static void stream_wait_cb(pa_stream *s, int success, void *userdata)
     pa_threaded_mainloop_signal(userdata, 0);
 }
 
-static int TriggerDrain(audio_output_t *aout)
-{
-    aout_sys_t *sys = aout->sys;
-    assert(sys->drain_trigger == NULL);
-
-    vlc_tick_t delay = vlc_pa_get_latency(aout, sys->context, sys->stream);
-    if (delay == VLC_TICK_INVALID)
-        return VLC_EGENERIC;
-
-    delay += pa_rtclock_now();
-    sys->drain_trigger = pa_context_rttime_new(sys->context, delay,
-                                               drain_trigger_cb, aout);
-    return sys->drain_trigger ? VLC_SUCCESS : VLC_ENOMEM;
-}
-
 /*** Sink ***/
 static void sink_add_cb(pa_context *ctx, const pa_sink_info *i, int eol,
                         void *userdata)
@@ -214,14 +225,14 @@ static void stream_start_now(pa_stream *s, audio_output_t *aout)
 {
     pa_operation *op;
 
-    assert ( ((aout_sys_t *)aout->sys)->trigger == NULL );
-
     op = pa_stream_cork(s, 0, NULL, NULL);
     if (op != NULL)
         pa_operation_unref(op);
     op = pa_stream_trigger(s, NULL, NULL);
     if (likely(op != NULL))
         pa_operation_unref(op);
+
+    (void) aout;
 }
 
 static void stream_stop(pa_stream *s, audio_output_t *aout)
@@ -229,11 +240,6 @@ static void stream_stop(pa_stream *s, audio_output_t *aout)
     aout_sys_t *sys = aout->sys;
     pa_operation *op;
 
-    if (sys->trigger != NULL) {
-        vlc_pa_rttime_free(sys->mainloop, sys->trigger);
-        sys->trigger = NULL;
-    }
-
     op = pa_stream_cork(s, 1, stream_wait_cb, sys->mainloop);
     if (op != NULL)
     {
@@ -243,73 +249,11 @@ static void stream_stop(pa_stream *s, audio_output_t *aout)
     }
 }
 
-static void stream_trigger_cb(pa_mainloop_api *api, pa_time_event *e,
-                              const struct timeval *tv, void *userdata)
-{
-    audio_output_t *aout = userdata;
-    aout_sys_t *sys = aout->sys;
-
-    assert (sys->trigger == e);
-
-    msg_Dbg(aout, "starting deferred");
-    vlc_pa_rttime_free(sys->mainloop, sys->trigger);
-    sys->trigger = NULL;
-    stream_start_now(sys->stream, aout);
-    (void) api; (void) e; (void) tv;
-}
-
-/**
- * Starts or resumes the playback stream.
- * Tries start playing back audio samples at the most accurate time
- * in order to minimize desync and resampling during early playback.
- * @note PulseAudio lock required.
- */
-static void stream_start(pa_stream *s, audio_output_t *aout, vlc_tick_t date)
-{
-    aout_sys_t *sys = aout->sys;
-    vlc_tick_t delta;
-
-    assert (sys->last_date != VLC_TICK_INVALID);
-
-    if (sys->trigger != NULL) {
-        vlc_pa_rttime_free(sys->mainloop, sys->trigger);
-        sys->trigger = NULL;
-    }
-
-    delta = vlc_pa_get_latency(aout, sys->context, s);
-    if (unlikely(delta == VLC_TICK_INVALID)) {
-        msg_Dbg(aout, "cannot synchronize start");
-        delta = 0; /* screwed */
-    }
-
-    delta = (date - vlc_tick_now()) - delta;
-    if (delta > 0) {
-        msg_Dbg(aout, "deferring start (%"PRId64" us)", delta);
-        delta += pa_rtclock_now();
-        sys->trigger = pa_context_rttime_new(sys->context, delta,
-                                             stream_trigger_cb, aout);
-    } else {
-        msg_Warn(aout, "starting late (%"PRId64" us)", delta);
-        stream_start_now(s, aout);
-    }
-}
-
 static void stream_latency_cb(pa_stream *s, void *userdata)
 {
     audio_output_t *aout = userdata;
     aout_sys_t *sys = aout->sys;
 
-    /* This callback is _never_ called while paused. */
-    if (sys->last_date == VLC_TICK_INVALID)
-    {
-        if (sys->draining && sys->drain_trigger == NULL)
-            TriggerDrain(aout);
-        return; /* nothing to do if buffers are (still) empty */
-    }
-
-    if (pa_stream_is_corked(s) > 0)
-        stream_start(s, aout, sys->last_date);
-
     const pa_timing_info *ti = pa_stream_get_timing_info(s);
     if (unlikely(ti == NULL) || !ti->playing)
         return;
@@ -320,36 +264,43 @@ static void stream_latency_cb(pa_stream *s, void *userdata)
         return;
     }
 
-    if (pa_stream_is_corked(s) == 0)
-    {
-        pa_usec_t rt;
-        if (pa_stream_get_time(s, &rt) == 0 && rt > 0)
-        {
-            if (likely(rt >= sys->flush_rt))
-            {
-                /* Subtract the timestamp of the timing_info from the monotonic
-                 * time */
-                pa_usec_t ti_age_us = pa_timeval_age(&ti->timestamp);
-                vlc_tick_t system_ts = vlc_tick_now()
-                                     - VLC_TICK_FROM_US(ti_age_us);
+    if (sys->draining)
+        return;
 
-                rt -= sys->flush_rt;
+    if (pa_stream_is_corked(s) != 0)
+        return;
 
-                aout_TimingReport(aout, system_ts, VLC_TICK_FROM_US(rt));
-            }
+    pa_usec_t rt;
+    if (pa_stream_get_time(s, &rt) != 0 || rt == 0)
+        return;
+
+    /* Subtract the timestamp of the timing_info from the monotonic time */
+    pa_usec_t ti_age_us = pa_timeval_age(&ti->timestamp);
+    sys->timing_system_ts = vlc_tick_now()
+                          - VLC_TICK_FROM_US(ti_age_us);
+
+    const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
+    pa_usec_t silence_us =
+        pa_bytes_to_usec(sys->total_silence_bytes, ss);
+
+    if (sys->start_date_reached
+     && likely(rt >= sys->flush_rt + silence_us))
+    {
+        vlc_tick_t audio_ts = VLC_TICK_0 +
+            VLC_TICK_FROM_US(rt - sys->flush_rt - silence_us);
+
+        aout_TimingReport(aout, sys->timing_system_ts, audio_ts);
+    }
 #ifndef NDEBUG
-            else
-            {
-                /* The time returned by pa_stream_get_time() might be smaller
-                 * than flush_rt just after a flush (depending on
-                 * transport_usec, sink_usec), but the current read index
-                 * should always be superior or equal. */
-                const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
-                assert(pa_bytes_to_usec(ti->read_index, ss) >= sys->flush_rt);
-            }
-#endif
-        }
+    else
+    {
+        /* The time returned by pa_stream_get_time() might be smaller than
+         * flush_rt just after a flush (depending on transport_usec,
+         * sink_usec), but the current read index should always be superior or
+         * equal. */
+        assert(pa_bytes_to_usec(ti->read_index, ss) >= sys->flush_rt);
     }
+#endif
 }
 
 
@@ -411,15 +362,168 @@ static void stream_moved_cb(pa_stream *s, void *userdata)
 static void stream_overflow_cb(pa_stream *s, void *userdata)
 {
     audio_output_t *aout = userdata;
+
+    msg_Err(aout, "overflow");
+    (void) s;
+}
+
+static void stream_drain(pa_stream *s, audio_output_t *aout)
+{
     aout_sys_t *sys = aout->sys;
-    pa_operation *op;
+    assert(sys->draining);
 
-    msg_Err(aout, "overflow, flushing");
-    op = pa_stream_flush(s, NULL, NULL);
-    if (unlikely(op == NULL))
+    if (sys->drain_trigger != NULL)
         return;
-    pa_operation_unref(op);
-    sys->last_date = VLC_TICK_INVALID;
+
+    pa_operation *op = pa_stream_drain(s, NULL, NULL);
+    if (op != NULL)
+        pa_operation_unref(op);
+
+    sys->flush_rt = 0;
+
+    /* XXX: Loosy drain emulation.
+     * See #18141: drain callback is never received */
+    vlc_tick_t delay =
+        stream_get_interpolated_latency(s, aout, vlc_tick_now());
+
+    delay += pa_rtclock_now();
+    sys->drain_trigger = pa_context_rttime_new(sys->context, delay,
+                                               drain_trigger_cb, aout);
+}
+
+static void data_free(void *data)
+{
+    block_Release(data);
+}
+
+static void noop_free(void *data)
+{
+    (void) data;
+}
+
+static size_t stream_write(pa_stream *s, audio_output_t *aout, size_t nbytes)
+{
+    aout_sys_t *sys = aout->sys;
+
+    size_t written = 0;
+    while (nbytes > 0)
+    {
+        block_t *first = sys->fifo.first;
+        if (unlikely(first == NULL))
+            return written;
+
+        const void *data;
+        size_t tocopy;
+        pa_free_cb_t free_cb;
+
+        if (nbytes >= first->i_buffer)
+        {
+            tocopy = first->i_buffer;
+            data = first->p_buffer;
+            free_cb = data_free;
+
+            sys->fifo.first = sys->fifo.first->p_next;
+            if (sys->fifo.first == NULL)
+                sys->fifo.last = &sys->fifo.first;
+        }
+        else
+        {
+            tocopy = nbytes;
+            data = first->p_buffer;
+            /* The block is not fully processed, free it only when finished */
+            free_cb = noop_free;
+
+            first->p_buffer += tocopy;
+            first->i_buffer -= tocopy;
+        }
+
+        if (pa_stream_write_ext_free(s, data, tocopy,
+                                     free_cb, first, 0, PA_SEEK_RELATIVE) < 0) {
+            vlc_pa_error(aout, "cannot write", sys->context);
+            free_cb(first);
+        }
+
+        nbytes -= tocopy;
+        written += tocopy;
+        sys->fifo.size -= tocopy;
+    }
+
+    return written;
+}
+
+static size_t stream_silence(pa_stream *s, audio_output_t *aout, size_t len)
+{
+    aout_sys_t *sys = aout->sys;
+
+    void *ptr;
+    if (pa_stream_begin_write(s, &ptr, &len))
+    {
+        vlc_pa_error(aout, "cannot begin write", sys->context);
+        return 0;
+    }
+
+    memset(ptr, 0, len);
+
+    if (pa_stream_write(s, ptr, len, NULL, 0, PA_SEEK_RELATIVE) < 0)
+    {
+        vlc_pa_error(aout, "cannot write", sys->context);
+        return 0;
+    }
+
+    return len;
+}
+
+static void stream_write_cb(pa_stream *s, size_t nbytes, void *userdata)
+{
+    audio_output_t *aout = userdata;
+    aout_sys_t *sys = aout->sys;
+
+    /* Strangely, the write callback can be called while corked, and it messes
+     * up the timings if we write silence in that state. */
+    if (unlikely(pa_stream_is_corked(s) != 0))
+        return;
+
+    if (!sys->start_date_reached)
+    {
+        /* Write 0s until we reach the start_date */
+        size_t silence_bytes;
+
+        if (likely(sys->start_date != VLC_TICK_INVALID))
+        {
+            const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
+
+            vlc_tick_t now = vlc_tick_now();
+            vlc_tick_t latency = stream_get_interpolated_latency(s, aout, now);
+            vlc_tick_t silence = sys->start_date - now - latency;
+            if (silence <= 0)
+                silence_bytes = 0;
+            else
+            {
+                silence_bytes = pa_usec_to_bytes(silence, ss);
+                if (silence_bytes > nbytes)
+                    silence_bytes = nbytes;
+            }
+        }
+        else
+            silence_bytes = nbytes;
+
+        if (silence_bytes != 0)
+        {
+            silence_bytes = stream_silence(s, aout, silence_bytes);
+            nbytes -= silence_bytes;
+            sys->total_silence_bytes += silence_bytes;
+        }
+
+        if (nbytes == 0)
+            return;
+
+        sys->start_date_reached = true;
+    }
+
+    stream_write(s, aout, nbytes);
+
+    if (sys->fifo.first == NULL && sys->draining)
+        stream_drain(s, aout);
 }
 
 static void stream_started_cb(pa_stream *s, void *userdata)
@@ -527,11 +631,6 @@ static void context_cb(pa_context *ctx, pa_subscription_event_type_t type,
 
 /*** VLC audio output callbacks ***/
 
-static void data_free(void *data)
-{
-    block_Release(data);
-}
-
 /**
  * Queue one audio frame to the playback stream
  */
@@ -547,17 +646,28 @@ static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
      * will take place, and sooner or later a deadlock. */
     pa_threaded_mainloop_lock(sys->mainloop);
 
-    sys->last_date = date;
+    const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
 
-    if (pa_stream_is_corked(s) > 0)
+    if (!sys->start_date_reached)
     {
-        /* Trigger a latency update, that will update the stream_start timer
-         * using the last date. */
-        pa_operation *op = pa_stream_update_timing_info(s, NULL, NULL);
-        if (op != NULL)
-            pa_operation_unref(op);
+        vlc_tick_t now = vlc_tick_now();
+        sys->start_date = date
+                        - pa_bytes_to_usec(sys->fifo.size, ss);
+
+        if (sys->start_date > now)
+            msg_Dbg(aout, "deferring start (%"PRId64" us)",
+                    sys->start_date - now);
+        else
+            msg_Dbg(aout, "starting late (%"PRId64" us)",
+                    sys->start_date - now);
+
+        if (pa_stream_is_corked(s) > 0)
+            stream_start_now(s, aout);
     }
 
+    block_ChainLastAppend(&sys->fifo.last, block);
+    sys->fifo.size += block->i_buffer;
+
 #if 0 /* Fault injector to test underrun recovery */
     static volatile unsigned u = 0;
     if ((++u % 1000) == 0) {
@@ -565,13 +675,6 @@ static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
         pa_operation_unref(pa_stream_flush(s, NULL, NULL));
     }
 #endif
-
-    if (pa_stream_write_ext_free(s, block->p_buffer, block->i_buffer,
-                                 data_free, block, 0, PA_SEEK_RELATIVE) < 0) {
-        vlc_pa_error(aout, "cannot write", sys->context);
-        block_Release(block);
-    }
-
     pa_threaded_mainloop_unlock(sys->mainloop);
 }
 
@@ -590,8 +693,7 @@ static void Pause(audio_output_t *aout, bool paused, vlc_tick_t date)
         stream_stop(s, aout);
     } else {
         pa_stream_set_latency_update_callback(s, stream_latency_cb, aout);
-        if (likely(sys->last_date != VLC_TICK_INVALID))
-            stream_start_now(s, aout);
+        stream_start_now(s, aout);
     }
 
     pa_threaded_mainloop_unlock(sys->mainloop);
@@ -612,16 +714,25 @@ static void Flush(audio_output_t *aout)
     {
         vlc_pa_rttime_free(sys->mainloop, sys->drain_trigger);
         sys->drain_trigger = NULL;
-        sys->draining = false;
     }
+    sys->draining = false;
 
     pa_operation *op = pa_stream_flush(s, NULL, NULL);
     if (op != NULL)
         pa_operation_unref(op);
-    sys->last_date = VLC_TICK_INVALID;
 
     stream_stop(s, aout);
 
+    block_ChainRelease(sys->fifo.first);
+    sys->fifo.size = 0;
+    sys->fifo.first = NULL;
+    sys->fifo.last = &sys->fifo.first;
+
+    sys->start_date_reached = false;
+    sys->start_date = VLC_TICK_INVALID;
+    sys->total_silence_bytes = 0;
+    sys->timing_system_ts = VLC_TICK_INVALID;
+
     const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
     const pa_timing_info *ti = pa_stream_get_timing_info(s);
     if (ti != NULL && !ti->read_index_corrupt)
@@ -637,46 +748,9 @@ static void Drain(audio_output_t *aout)
 
     pa_threaded_mainloop_lock(sys->mainloop);
 
-    if (unlikely(pa_stream_is_corked(s) > 0))
-    {
-        /* Drain while the stream is corked. It happens with very small input
-         * when the stream is drained while the start is still being deferred.
-         * In that case, we need start the stream before we actually drain it.
-         * */
-        if (sys->trigger != NULL)
-        {
-            vlc_pa_rttime_free(sys->mainloop, sys->trigger);
-            sys->trigger = NULL;
-        }
-        stream_start_now(s, aout);
-    }
-
-    pa_operation *op = pa_stream_drain(s, NULL, NULL);
-    if (op != NULL)
-        pa_operation_unref(op);
-
-    if (sys->last_date == VLC_TICK_INVALID)
-        aout_DrainedReport(aout);
-    else
-    {
-        sys->last_date = VLC_TICK_INVALID;
-        sys->flush_rt = 0;
-
-        /* XXX: Loosy drain emulation.
-         * See #18141: drain callback is never received */
-        sys->draining = true;
-
-        /* Trigger a latency update, that will update the drain timer */
-        op = pa_stream_update_timing_info(s, NULL, NULL);
-        if (op != NULL)
-            pa_operation_unref(op);
-        else
-        {
-            /* Timing failed, update the drain timer using the last known
-             * latency */
-            TriggerDrain(aout);
-        }
-    }
+    sys->draining = true;
+    if (sys->fifo.first == NULL)
+        stream_drain(s, aout);
 
     pa_threaded_mainloop_unlock(sys->mainloop);
 }
@@ -903,12 +977,20 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
         pa_cvolume_set(cvolume, ss.channels, sys->volume_force);
     }
 
-    sys->trigger = sys->drain_trigger = NULL;
+    sys->drain_trigger = NULL;
     sys->draining = false;
     pa_cvolume_init(&sys->cvolume);
-    sys->last_date = VLC_TICK_INVALID;
     sys->flush_rt = 0;
 
+    sys->start_date_reached = false;
+    sys->start_date = VLC_TICK_INVALID;
+    sys->total_silence_bytes = 0;
+    sys->timing_system_ts = VLC_TICK_INVALID;
+
+    sys->fifo.size = 0;
+    sys->fifo.first = NULL;
+    sys->fifo.last = &sys->fifo.first;
+
     pa_format_info *formatv = pa_format_info_new();
     formatv->encoding = encoding;
     pa_format_info_set_rate(formatv, ss.rate);
@@ -1031,6 +1113,7 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     pa_stream_set_latency_update_callback(s, stream_latency_cb, aout);
     pa_stream_set_moved_callback(s, stream_moved_cb, aout);
     pa_stream_set_overflow_callback(s, stream_overflow_cb, aout);
+    pa_stream_set_write_callback(s, stream_write_cb, aout);
     pa_stream_set_started_callback(s, stream_started_cb, aout);
     pa_stream_set_suspended_callback(s, stream_suspended_cb, aout);
     pa_stream_set_underflow_callback(s, stream_underflow_cb, aout);
@@ -1076,8 +1159,6 @@ static void Stop(audio_output_t *aout)
     pa_stream *s = sys->stream;
 
     pa_threaded_mainloop_lock(sys->mainloop);
-    if (unlikely(sys->trigger != NULL))
-        vlc_pa_rttime_free(sys->mainloop, sys->trigger);
     if (sys->drain_trigger != NULL)
         vlc_pa_rttime_free(sys->mainloop, sys->drain_trigger);
     pa_stream_disconnect(s);
@@ -1089,6 +1170,7 @@ static void Stop(audio_output_t *aout)
     pa_stream_set_latency_update_callback(s, NULL, NULL);
     pa_stream_set_moved_callback(s, NULL, NULL);
     pa_stream_set_overflow_callback(s, NULL, NULL);
+    pa_stream_set_write_callback(s, NULL, NULL);
     pa_stream_set_started_callback(s, NULL, NULL);
     pa_stream_set_suspended_callback(s, NULL, NULL);
     pa_stream_set_underflow_callback(s, NULL, NULL);



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/54897d1ebfd22ccae2faa78a13aa706ab693e731...eeef59369010c984c2905bf2227592155a46d0db

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/54897d1ebfd22ccae2faa78a13aa706ab693e731...eeef59369010c984c2905bf2227592155a46d0db
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list