]> git.sesse.net Git - vlc/blobdiff - modules/access/pulse.c
avcodec: fix end-of-stream handling (fixes #8792) and factor code
[vlc] / modules / access / pulse.c
index d75747bd76aadf0f48d71ba78235b9ff81d4f307..7018330a4f8928d499f79f0f5048332e1bd914a6 100644 (file)
@@ -1,22 +1,23 @@
+/**
+ * \file pulse.c
+ * \brief PulseAudio input plugin for vlc
+ */
 /*****************************************************************************
- * pulse.c : PulseAudio input plugin for vlc
- *****************************************************************************
- * Copyright (C) 2008 the VideoLAN team
- * Copyright (C) 2009-2011 Rémi Denis-Courmont
+ * Copyright (C) 2011 Rémi Denis-Courmont
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * 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 General Public License for more details.
+ * 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 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.
+ * 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.
  *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
 #include <vlc_demux.h>
 #include <vlc_plugin.h>
 #include <pulse/pulseaudio.h>
-#include <vlc_pulse.h>
+#include "../audio_output/vlcpulse.h"
+
+#define HELP_TEXT N_( \
+    "Pass pulse:// to open the default PulseAudio source, " \
+    "or pulse://SOURCE to open a specific source named SOURCE.")
 
 static int Open(vlc_object_t *);
 static void Close(vlc_object_t *);
@@ -39,6 +44,8 @@ vlc_module_begin ()
     set_capability ("access_demux", 0)
     set_category (CAT_INPUT)
     set_subcategory (SUBCAT_INPUT_ACCESS)
+    set_help (HELP_TEXT)
+
     add_shortcut ("pulse", "pulseaudio", "pa")
     set_callbacks (Open, Close)
 vlc_module_end ()
@@ -47,6 +54,7 @@ struct demux_sys_t
 {
     pa_stream *stream; /**< PulseAudio playback stream object */
     pa_context *context; /**< PulseAudio connection context */
+    pa_threaded_mainloop *mainloop; /**< PulseAudio thread */
 
     es_out_id_t *es;
     bool discontinuity; /**< The next block will not follow the last one */
@@ -57,15 +65,34 @@ struct demux_sys_t
 /* Stream helpers */
 static void stream_state_cb(pa_stream *s, void *userdata)
 {
+    pa_threaded_mainloop *mainloop = userdata;
+
     switch (pa_stream_get_state(s)) {
         case PA_STREAM_READY:
         case PA_STREAM_FAILED:
         case PA_STREAM_TERMINATED:
-            vlc_pa_signal(0);
+            pa_threaded_mainloop_signal(mainloop, 0);
         default:
             break;
     }
-    (void) userdata;
+}
+
+static void stream_success_cb (pa_stream *s, int success, void *userdata)
+{
+    pa_threaded_mainloop *mainloop = userdata;
+
+    pa_threaded_mainloop_signal(mainloop, 0);
+    (void) s;
+    (void) success;
+}
+
+static void stream_buffer_attr_cb(pa_stream *s, void *userdata)
+{
+    demux_t *demux = userdata;
+    const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(s);
+
+    msg_Dbg(demux, "using buffer metrics: maxlength=%"PRIu32", "
+            "fragsize=%"PRIu32, pba->maxlength, pba->fragsize);
 }
 
 static void stream_moved_cb(pa_stream *s, void *userdata)
@@ -75,6 +102,7 @@ static void stream_moved_cb(pa_stream *s, void *userdata)
 
     msg_Dbg(demux, "connected to source %"PRIu32": %s", idx,
                   pa_stream_get_device_name(s));
+    stream_buffer_attr_cb(s, userdata);
 }
 
 static void stream_overflow_cb(pa_stream *s, void *userdata)
@@ -109,14 +137,14 @@ static void stream_underflow_cb(pa_stream *s, void *userdata)
     (void) s;
 }
 
-static int stream_wait(pa_stream *stream)
+static int stream_wait(pa_stream *stream, pa_threaded_mainloop *mainloop)
 {
     pa_stream_state_t state;
 
     while ((state = pa_stream_get_state(stream)) != PA_STREAM_READY) {
         if (state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED)
             return -1;
-        vlc_pa_wait();
+        pa_threaded_mainloop_wait(mainloop);
     }
     return 0;
 }
@@ -152,7 +180,7 @@ static void stream_read_cb(pa_stream *s, size_t length, void *userdata)
 
     block_t *block = block_Alloc(length);
     if (likely(block != NULL)) {
-        vlc_memcpy(block->p_buffer, ptr, length);
+        memcpy(block->p_buffer, ptr, length);
         block->i_nb_samples = samples;
         block->i_dts = block->i_pts = pts;
         if (sys->discontinuity) {
@@ -206,21 +234,38 @@ static int Control(demux_t *demux, int query, va_list ap)
     return VLC_SUCCESS;
 }
 
+/** PulseAudio sample (PCM) format to VLC codec */
+static const vlc_fourcc_t fourccs[] = {
+    [PA_SAMPLE_U8] =        VLC_CODEC_U8,
+    [PA_SAMPLE_ALAW] =      VLC_CODEC_ALAW,
+    [PA_SAMPLE_ULAW] =      VLC_CODEC_MULAW,
+    [PA_SAMPLE_S16LE] =     VLC_CODEC_S16L,
+    [PA_SAMPLE_S16BE] =     VLC_CODEC_S16B,
+    [PA_SAMPLE_FLOAT32LE] = VLC_CODEC_F32L,
+    [PA_SAMPLE_FLOAT32BE] = VLC_CODEC_F32B,
+    [PA_SAMPLE_S32LE] =     VLC_CODEC_S32L,
+    [PA_SAMPLE_S32BE] =     VLC_CODEC_S32B,
+    [PA_SAMPLE_S24LE] =     VLC_CODEC_S24L,
+    [PA_SAMPLE_S24BE] =     VLC_CODEC_S24B,
+    [PA_SAMPLE_S24_32LE] =  VLC_CODEC_S24L32,
+    [PA_SAMPLE_S24_32BE] =  VLC_CODEC_S24B32,
+};
+
 static int Open(vlc_object_t *obj)
 {
     demux_t *demux = (demux_t *)obj;
 
-    pa_context *ctx = vlc_pa_connect(obj);
-    if (ctx == NULL)
-        return VLC_EGENERIC;
-
     demux_sys_t *sys = malloc(sizeof (*sys));
-    if (unlikely(sys == NULL)) {
-        vlc_pa_disconnect (obj, ctx);
+    if (unlikely(sys == NULL))
         return VLC_ENOMEM;
+
+    sys->context = vlc_pa_connect(obj, &sys->mainloop);
+    if (sys->context == NULL) {
+        free(sys);
+        return VLC_EGENERIC;
     }
+
     sys->stream = NULL;
-    sys->context = ctx;
     sys->es = NULL;
     sys->discontinuity = false;
     sys->caching = INT64_C(1000) * var_InheritInteger(obj, "live-caching");
@@ -241,10 +286,16 @@ static int Open(vlc_object_t *obj)
 
     const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING
                                   | PA_STREAM_AUTO_TIMING_UPDATE
-                                  /*| PA_STREAM_FIX_FORMAT
+                                  | PA_STREAM_ADJUST_LATENCY
+                                  | PA_STREAM_FIX_FORMAT
                                   | PA_STREAM_FIX_RATE
-                                  | PA_STREAM_FIX_CHANNELS*/;
-    const struct pa_buffer_attr attr = {
+                                  /*| PA_STREAM_FIX_CHANNELS*/;
+
+    const char *dev = NULL;
+    if (demux->psz_location != NULL && demux->psz_location[0] != '\0')
+        dev = demux->psz_location;
+
+    struct pa_buffer_attr attr = {
         .maxlength = -1,
         .fragsize = pa_usec_to_bytes(sys->caching, &ss) / 2,
     };
@@ -253,50 +304,72 @@ static int Open(vlc_object_t *obj)
 
     /* Create record stream */
     pa_stream *s;
+    pa_operation *op;
 
-    vlc_pa_lock();
-    s = pa_stream_new(ctx, "audio stream", &ss, &map);
+    pa_threaded_mainloop_lock(sys->mainloop);
+    s = pa_stream_new(sys->context, "audio stream", &ss, &map);
     if (s == NULL)
         goto error;
 
     sys->stream = s;
-    pa_stream_set_state_callback(s, stream_state_cb, NULL);
+    pa_stream_set_state_callback(s, stream_state_cb, sys->mainloop);
     pa_stream_set_read_callback(s, stream_read_cb, demux);
+    pa_stream_set_buffer_attr_callback(s, stream_buffer_attr_cb, demux);
     pa_stream_set_moved_callback(s, stream_moved_cb, demux);
     pa_stream_set_overflow_callback(s, stream_overflow_cb, demux);
     pa_stream_set_started_callback(s, stream_started_cb, demux);
     pa_stream_set_suspended_callback(s, stream_suspended_cb, demux);
     pa_stream_set_underflow_callback(s, stream_underflow_cb, demux);
 
-    if (pa_stream_connect_record(s, NULL, &attr, flags) < 0
-     || stream_wait(s)) {
-        vlc_pa_error(obj, "cannot connect record stream", ctx);
+    if (pa_stream_connect_record(s, dev, &attr, flags) < 0
+     || stream_wait(s, sys->mainloop)) {
+        vlc_pa_error(obj, "cannot connect record stream", sys->context);
         goto error;
     }
 
     /* The ES should be initialized before stream_read_cb(), but how? */
-    es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_S16N);
+    const struct pa_sample_spec *pss = pa_stream_get_sample_spec(s);
+    if ((unsigned)pss->format >= sizeof (fourccs) / sizeof (fourccs[0])) {
+        msg_Err(obj, "unknown PulseAudio sample format %u",
+                (unsigned)pss->format);
+        goto error;
+    }
+
+    vlc_fourcc_t format = fourccs[pss->format];
+    if (format == 0) { /* FIXME: should renegotiate something else */
+        msg_Err(obj, "unsupported PulseAudio sample format %u",
+                (unsigned)pss->format);
+        goto error;
+    }
+
+    es_format_Init(&fmt, AUDIO_ES, format);
     fmt.audio.i_physical_channels = fmt.audio.i_original_channels =
         AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
     fmt.audio.i_channels = ss.channels;
-    fmt.audio.i_rate = ss.rate;
-    fmt.audio.i_bitspersample = 16;
-    fmt.audio.i_blockalign = 2 * ss.channels;
-    fmt.i_bitrate = ss.channels * ss.rate * fmt.audio.i_bitspersample;
+    fmt.audio.i_rate = pss->rate;
+    fmt.audio.i_bitspersample = aout_BitsPerSample(format);
+    fmt.audio.i_blockalign = fmt.audio.i_bitspersample * ss.channels / 8;
+    fmt.i_bitrate = fmt.audio.i_bitspersample * ss.channels * pss->rate;
     sys->framesize = fmt.audio.i_blockalign;
     sys->es = es_out_Add (demux->out, &fmt);
 
-    const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(s);
-    msg_Dbg(obj, "using buffer metrics: maxlength=%"PRIu32", fragsize=%"PRIu32,
-            pba->maxlength, pba->fragsize);
-    vlc_pa_unlock();
+    /* Update the buffer attributes according to actual format */
+    attr.fragsize = pa_usec_to_bytes(sys->caching, pss) / 2;
+    op = pa_stream_set_buffer_attr(s, &attr, stream_success_cb, sys->mainloop);
+    if (likely(op != NULL)) {
+        while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+            pa_threaded_mainloop_wait(sys->mainloop);
+        pa_operation_unref(op);
+    }
+    stream_buffer_attr_cb(s, demux);
+    pa_threaded_mainloop_unlock(sys->mainloop);
 
     demux->pf_demux = NULL;
     demux->pf_control = Control;
     return VLC_SUCCESS;
 
 error:
-    vlc_pa_unlock();
+    pa_threaded_mainloop_unlock(sys->mainloop);
     Close(obj);
     return VLC_EGENERIC;
 }
@@ -305,23 +378,23 @@ static void Close (vlc_object_t *obj)
 {
     demux_t *demux = (demux_t *)obj;
     demux_sys_t *sys = demux->p_sys;
-    pa_context *ctx = sys->context;
     pa_stream *s = sys->stream;
 
     if (likely(s != NULL)) {
-        vlc_pa_lock();
+        pa_threaded_mainloop_lock(sys->mainloop);
         pa_stream_disconnect(s);
         pa_stream_set_state_callback(s, NULL, NULL);
         pa_stream_set_read_callback(s, NULL, NULL);
+        pa_stream_set_buffer_attr_callback(s, NULL, NULL);
         pa_stream_set_moved_callback(s, NULL, NULL);
         pa_stream_set_overflow_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);
         pa_stream_unref(s);
-        vlc_pa_unlock();
+        pa_threaded_mainloop_unlock(sys->mainloop);
     }
 
-    vlc_pa_disconnect(obj, ctx);
+    vlc_pa_disconnect(obj, sys->context, sys->mainloop);
     free(sys);
 }