]> git.sesse.net Git - vlc/blobdiff - src/audio_output/output.c
differentiate mp1v from mp2v and default mpgv to mp2v
[vlc] / src / audio_output / output.c
index 10db22ed454a6c0c256a6b568254a66689c969dd..18fee051641406e65e4e5aac0aacb41e7d31e1b0 100644 (file)
 # include "config.h"
 #endif
 
+#include <stdlib.h>
+#include <assert.h>
+
 #include <vlc_common.h>
 #include <vlc_aout.h>
 #include <vlc_modules.h>
-#include <vlc_cpu.h>
 
 #include "libvlc.h"
 #include "aout_internal.h"
 
+static const char unset_str[1] = ""; /* Non-NULL constant string pointer */
+
+struct aout_dev
+{
+    aout_dev_t *next;
+    char *name;
+    char id[1];
+};
+
+
 /* Local functions */
+static void aout_OutputAssertLocked (audio_output_t *aout)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    vlc_assert_locked (&owner->lock);
+}
+
 static void aout_Destructor( vlc_object_t * p_this );
 
 static int var_Copy (vlc_object_t *src, const char *name, vlc_value_t prev,
@@ -70,11 +89,65 @@ static void aout_PolicyNotify (audio_output_t *aout, bool cork)
     (cork ? var_IncInteger : var_DecInteger) (aout->p_parent, "corks");
 }
 
+static void aout_DeviceNotify (audio_output_t *aout, const char *id)
+{
+    var_SetString (aout, "device", (id != NULL) ? id : "");
+}
+
+static void aout_HotplugNotify (audio_output_t *aout,
+                                const char *id, const char *name)
+{
+    aout_owner_t *owner = aout_owner (aout);
+    aout_dev_t *dev, **pp = &owner->dev.list;
+
+    vlc_mutex_lock (&owner->dev.lock);
+    while ((dev = *pp) != NULL)
+    {
+        if (!strcmp (id, dev->id))
+            break;
+        pp = &dev->next;
+    }
+
+    if (name != NULL)
+    {
+        if (dev == NULL) /* Added device */
+        {
+            dev = malloc (sizeof (*dev) + strlen (id));
+            if (unlikely(dev == NULL))
+                goto out;
+            dev->next = NULL;
+            strcpy (dev->id, id);
+            *pp = dev;
+            owner->dev.count++;
+        }
+        else /* Modified device */
+            free (dev->name);
+        dev->name = strdup (name);
+    }
+    else
+    {
+        if (dev != NULL) /* Removed device */
+        {
+            owner->dev.count--;
+            *pp = dev->next;
+            free (dev->name);
+            free (dev);
+        }
+    }
+out:
+    vlc_mutex_unlock (&owner->dev.lock);
+}
+
+static void aout_RestartNotify (audio_output_t *aout, unsigned mode)
+{
+    aout_RequestRestart (aout, mode);
+}
+
 static int aout_GainNotify (audio_output_t *aout, float gain)
 {
     aout_owner_t *owner = aout_owner (aout);
 
-    aout_assert_locked (aout);
+    aout_OutputAssertLocked (aout);
     aout_volume_SetVolume (owner->volume, gain);
     /* XXX: ideally, return -1 if format cannot be amplified */
     return 0;
@@ -86,6 +159,8 @@ static int aout_GainNotify (audio_output_t *aout, float gain)
  */
 audio_output_t *aout_New (vlc_object_t *parent)
 {
+    vlc_value_t val, text;
+
     audio_output_t *aout = vlc_custom_create (parent, sizeof (aout_instance_t),
                                               "audio output");
     if (unlikely(aout == NULL))
@@ -94,26 +169,35 @@ audio_output_t *aout_New (vlc_object_t *parent)
     aout_owner_t *owner = aout_owner (aout);
 
     vlc_mutex_init (&owner->lock);
-    vlc_object_set_destructor (aout, aout_Destructor);
+    vlc_mutex_init (&owner->req.lock);
+    vlc_mutex_init (&owner->dev.lock);
+    owner->req.device = (char *)unset_str;
+    owner->req.volume = -1.f;
+    owner->req.mute = -1;
 
-    owner->input = NULL;
+    vlc_object_set_destructor (aout, aout_Destructor);
 
     /* Audio output module callbacks */
     var_Create (aout, "volume", VLC_VAR_FLOAT);
     var_AddCallback (aout, "volume", var_Copy, parent);
     var_Create (aout, "mute", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
     var_AddCallback (aout, "mute", var_Copy, parent);
+    var_Create (aout, "device", VLC_VAR_STRING);
 
     aout->event.volume_report = aout_VolumeNotify;
     aout->event.mute_report = aout_MuteNotify;
     aout->event.policy_report = aout_PolicyNotify;
+    aout->event.device_report = aout_DeviceNotify;
+    aout->event.hotplug_report = aout_HotplugNotify;
     aout->event.gain_request = aout_GainNotify;
+    aout->event.restart_request = aout_RestartNotify;
 
     /* Audio output module initialization */
     aout->start = NULL;
     aout->stop = NULL;
     aout->volume_set = NULL;
     aout->mute_set = NULL;
+    aout->device_select = NULL;
     owner->module = module_need (aout, "audio output", "$aout", false);
     if (owner->module == NULL)
     {
@@ -125,7 +209,6 @@ audio_output_t *aout_New (vlc_object_t *parent)
     /*
      * Persistent audio output variables
      */
-    vlc_value_t val, text;
     module_config_t *cfg;
     char *str;
 
@@ -169,6 +252,13 @@ audio_output_t *aout_New (vlc_object_t *parent)
         text.psz_string = (char*)"Vovoid VSXu";
         var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
     }
+    /* Look for glspectrum plugin */
+    if (module_exists ("glspectrum"))
+    {
+        val.psz_string = (char *)"glspectrum";
+        text.psz_string = (char*)"3D spectrum";
+        var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
+    }
     str = var_GetNonEmptyString (aout, "effect-list");
     if (str != NULL)
     {
@@ -216,6 +306,9 @@ audio_output_t *aout_New (vlc_object_t *parent)
                             &val, &text);
         }
 
+    var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
+    var_Create (aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
+
     return aout;
 }
 
@@ -226,12 +319,12 @@ void aout_Destroy (audio_output_t *aout)
 {
     aout_owner_t *owner = aout_owner (aout);
 
-    aout_lock (aout);
+    aout_OutputLock (aout);
     module_unneed (aout, owner->module);
     /* Protect against late call from intf.c */
     aout->volume_set = NULL;
     aout->mute_set = NULL;
-    aout_unlock (aout);
+    aout_OutputUnlock (aout);
 
     var_DelCallback (aout, "mute", var_Copy, aout->p_parent);
     var_SetFloat (aout, "volume", -1.f);
@@ -247,114 +340,83 @@ static void aout_Destructor (vlc_object_t *obj)
     audio_output_t *aout = (audio_output_t *)obj;
     aout_owner_t *owner = aout_owner (aout);
 
-    vlc_mutex_destroy (&owner->lock);
-}
-
-/**
- * Gets the volume of the audio output stream (independent of mute).
- * \return Current audio volume (0. = silent, 1. = nominal),
- * or a strictly negative value if undefined.
- */
-float aout_VolumeGet (audio_output_t *aout)
-{
-    return var_GetFloat (aout, "volume");
-}
-
-/**
- * Sets the volume of the audio output stream.
- * \note The mute status is not changed.
- * \return 0 on success, -1 on failure.
- */
-int aout_VolumeSet (audio_output_t *aout, float vol)
-{
-    int ret = -1;
-
-    aout_lock (aout);
-    if (aout->volume_set != NULL)
-        ret = aout->volume_set (aout, vol);
-    aout_unlock (aout);
-    return ret;
-}
-
-/**
- * Gets the audio output stream mute flag.
- * \return 0 if not muted, 1 if muted, -1 if undefined.
- */
-int aout_MuteGet (audio_output_t *aout)
-{
-    return var_InheritBool (aout, "mute");
-}
-
-/**
- * Sets the audio output stream mute flag.
- * \return 0 on success, -1 on failure.
- */
-int aout_MuteSet (audio_output_t *aout, bool mute)
-{
-    int ret = -1;
+    vlc_mutex_destroy (&owner->dev.lock);
+    for (aout_dev_t *dev = owner->dev.list, *next; dev != NULL; dev = next)
+    {
+        next = dev->next;
+        free (dev->name);
+        free (dev);
+    }
 
-    aout_lock (aout);
-    if (aout->mute_set != NULL)
-        ret = aout->mute_set (aout, mute);
-    aout_unlock (aout);
-    return ret;
+    assert (owner->req.device == unset_str);
+    vlc_mutex_destroy (&owner->req.lock);
+    vlc_mutex_destroy (&owner->lock);
 }
 
 /**
  * Starts an audio output stream.
- * \param fmtp audio output stream format [IN/OUT]
+ * \param fmt audio output stream format [IN/OUT]
  * \warning The caller must hold the audio output lock.
  */
-int aout_OutputNew (audio_output_t *aout, const audio_sample_format_t *fmtp)
+int aout_OutputNew (audio_output_t *aout, audio_sample_format_t *restrict fmt)
 {
-    aout_owner_t *owner = aout_owner (aout);
-
-    audio_sample_format_t fmt = *fmtp;
-    aout_FormatPrepare (&fmt);
-
-    aout_assert_locked (aout);
+    aout_OutputAssertLocked (aout);
+
+    /* Ideally, the audio filters would be created before the audio output,
+     * and the ideal audio format would be the output of the filters chain.
+     * But that scheme would not really play well with digital pass-through. */
+    if (AOUT_FMT_LINEAR(fmt))
+    {   /* Try to stay in integer domain if possible for no/slow FPU. */
+        fmt->i_format = (fmt->i_bitspersample > 16) ? VLC_CODEC_FL32
+                                                    : VLC_CODEC_S16N;
+        aout_FormatPrepare (fmt);
+    }
 
-    if (aout->start (aout, &fmt))
+    if (aout->start (aout, fmt))
     {
         msg_Err (aout, "module not functional");
         return -1;
     }
 
     if (!var_Type (aout, "stereo-mode"))
+    {
         var_Create (aout, "stereo-mode",
                     VLC_VAR_INTEGER | VLC_VAR_HASCHOICE | VLC_VAR_DOINHERIT);
 
+        vlc_value_t txt;
+        txt.psz_string = _("Stereo audio mode");
+        var_Change (aout, "stereo-mode", VLC_VAR_SETTEXT, &txt, NULL);
+    }
+
     /* The user may have selected a different channels configuration. */
     var_AddCallback (aout, "stereo-mode", aout_ChannelsRestart, NULL);
     switch (var_GetInteger (aout, "stereo-mode"))
     {
         case AOUT_VAR_CHAN_RSTEREO:
-            fmt.i_original_channels |= AOUT_CHAN_REVERSESTEREO;
-             break;
+            fmt->i_original_channels |= AOUT_CHAN_REVERSESTEREO;
+            break;
         case AOUT_VAR_CHAN_STEREO:
-            fmt.i_original_channels = AOUT_CHANS_STEREO;
+            fmt->i_original_channels = AOUT_CHANS_STEREO;
             break;
         case AOUT_VAR_CHAN_LEFT:
-            fmt.i_original_channels = AOUT_CHAN_LEFT;
+            fmt->i_original_channels = AOUT_CHAN_LEFT;
             break;
         case AOUT_VAR_CHAN_RIGHT:
-            fmt.i_original_channels = AOUT_CHAN_RIGHT;
+            fmt->i_original_channels = AOUT_CHAN_RIGHT;
             break;
         case AOUT_VAR_CHAN_DOLBYS:
-            fmt.i_original_channels = AOUT_CHANS_STEREO|AOUT_CHAN_DOLBYSTEREO;
+            fmt->i_original_channels = AOUT_CHANS_STEREO|AOUT_CHAN_DOLBYSTEREO;
             break;
         default:
         {
-            if ((fmt.i_original_channels & AOUT_CHAN_PHYSMASK)
-                                                         != AOUT_CHANS_STEREO)
+            if ((fmt->i_original_channels & AOUT_CHAN_PHYSMASK)
+                                                          != AOUT_CHANS_STEREO)
                  break;
 
             vlc_value_t val, txt;
             val.i_int = 0;
             var_Change (aout, "stereo-mode", VLC_VAR_DELCHOICE, &val, NULL);
-            txt.psz_string = _("Stereo audio mode");
-            var_Change (aout, "stereo-mode", VLC_VAR_SETTEXT, &txt, NULL);
-            if (fmt.i_original_channels & AOUT_CHAN_DOLBYSTEREO)
+            if (fmt->i_original_channels & AOUT_CHAN_DOLBYSTEREO)
             {
                 val.i_int = AOUT_VAR_CHAN_DOLBYS;
                 txt.psz_string = _("Dolby Surround");
@@ -369,9 +431,9 @@ int aout_OutputNew (audio_output_t *aout, const audio_sample_format_t *fmtp)
             val.i_int = AOUT_VAR_CHAN_LEFT;
             txt.psz_string = _("Left");
             var_Change (aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, &txt);
-            if (fmt.i_original_channels & AOUT_CHAN_DUALMONO)
+            if (fmt->i_original_channels & AOUT_CHAN_DUALMONO)
             {   /* Go directly to the left channel. */
-                fmt.i_original_channels = AOUT_CHAN_LEFT;
+                fmt->i_original_channels = AOUT_CHAN_LEFT;
                 var_Change (aout, "stereo-mode", VLC_VAR_SETVALUE, &val, NULL);
             }
             val.i_int = AOUT_VAR_CHAN_RIGHT;
@@ -383,37 +445,8 @@ int aout_OutputNew (audio_output_t *aout, const audio_sample_format_t *fmtp)
         }
     }
 
-    aout_FormatPrepare (&fmt);
-    aout_FormatPrint (aout, "output", &fmt );
-
-    /* Choose the mixer format. */
-    owner->mixer_format = fmt;
-    if (!AOUT_FMT_LINEAR(&fmt))
-        owner->mixer_format.i_format = fmtp->i_format;
-    else
-    /* Most audio filters can only deal with single-precision,
-     * so lets always use that when hardware supports floating point. */
-    if( HAVE_FPU )
-        owner->mixer_format.i_format = VLC_CODEC_FL32;
-    else
-    /* Fallback to 16-bits. This avoids pointless conversion to and from
-     * 32-bits samples for the sole purpose of software mixing. */
-        owner->mixer_format.i_format = VLC_CODEC_S16N;
-
-    aout_FormatPrepare (&owner->mixer_format);
-    aout_FormatPrint (aout, "mixer", &owner->mixer_format);
-
-    /* Create filters. */
-    owner->nb_filters = 0;
-    if (aout_FiltersCreatePipeline (aout, owner->filters, &owner->nb_filters,
-                                    sizeof (owner->filters)
-                                                  / sizeof (owner->filters[0]),
-                                    &owner->mixer_format, &fmt) < 0)
-    {
-        msg_Err (aout, "couldn't create audio output pipeline");
-        aout_OutputDelete (aout);
-        return -1;
-    }
+    aout_FormatPrepare (fmt);
+    aout_FormatPrint (aout, "output", fmt);
     return 0;
 }
 
@@ -424,14 +457,20 @@ int aout_OutputNew (audio_output_t *aout, const audio_sample_format_t *fmtp)
  */
 void aout_OutputDelete (audio_output_t *aout)
 {
-    aout_owner_t *owner = aout_owner (aout);
-
-    aout_assert_locked (aout);
+    aout_OutputAssertLocked (aout);
 
     var_DelCallback (aout, "stereo-mode", aout_ChannelsRestart, NULL);
     if (aout->stop != NULL)
         aout->stop (aout);
-    aout_FiltersDestroyPipeline (owner->filters, owner->nb_filters);
+}
+
+int aout_OutputTimeGet (audio_output_t *aout, mtime_t *delay)
+{
+    aout_OutputAssertLocked (aout);
+
+    if (aout->time_get == NULL)
+        return -1;
+    return aout->time_get (aout, delay);
 }
 
 /**
@@ -441,45 +480,15 @@ void aout_OutputDelete (audio_output_t *aout)
  */
 void aout_OutputPlay (audio_output_t *aout, block_t *block)
 {
-    aout_owner_t *owner = aout_owner (aout);
-    mtime_t drift = 0;
-
-    aout_assert_locked (aout);
-
-    aout_FiltersPlay (owner->filters, owner->nb_filters, &block);
-    if (block == NULL)
-        return;
-    if (block->i_buffer == 0)
-    {
-        block_Release (block);
-        return;
-    }
+    aout_OutputAssertLocked (aout);
+    aout->play (aout, block);
+}
 
-    aout->play (aout, block, &drift);
-/**
- * Notifies the audio input of the drift from the requested audio
- * playback timestamp (@ref block_t.i_pts) to the anticipated playback time
- * as reported by the audio output hardware.
- * Depending on the drift amplitude, the input core may ignore the drift
- * trigger upsampling or downsampling, or even discard samples.
- * Future VLC versions may instead adjust the input decoding speed.
- *
- * The audio output plugin is responsible for estimating the drift. A negative
- * value means playback is ahead of the intended time and a positive value
- * means playback is late from the intended time. In most cases, the audio
- * output can estimate the delay until playback of the next sample to be
- * queued. Then, before the block is queued:
- *    drift = mdate() + delay - block->i_pts
- * where mdate() + delay is the estimated time when the sample will be rendered
- * and block->i_pts is the intended time.
- */
-    if (drift < -AOUT_MAX_PTS_ADVANCE || +AOUT_MAX_PTS_DELAY < drift)
-    {
-        msg_Warn (aout, "not synchronized (%"PRId64" us), resampling",
-                  drift);
-        if (date_Get (&owner->sync.date) != VLC_TS_INVALID)
-            date_Move (&owner->sync.date, drift);
-    }
+static void PauseDefault (audio_output_t *aout, bool pause, mtime_t date)
+{
+    if (pause)
+        aout_OutputFlush (aout, false);
+    (void) date;
 }
 
 /**
@@ -491,9 +500,8 @@ void aout_OutputPlay (audio_output_t *aout, block_t *block)
  */
 void aout_OutputPause( audio_output_t *aout, bool pause, mtime_t date )
 {
-    aout_assert_locked( aout );
-    if( aout->pause != NULL )
-        aout->pause( aout, pause, date );
+    aout_OutputAssertLocked (aout);
+    ((aout->pause != NULL) ? aout->pause : PauseDefault) (aout, pause, date);
 }
 
 /**
@@ -506,8 +514,199 @@ void aout_OutputPause( audio_output_t *aout, bool pause, mtime_t date )
  */
 void aout_OutputFlush( audio_output_t *aout, bool wait )
 {
-    aout_assert_locked( aout );
+    aout_OutputAssertLocked( aout );
+    aout->flush (aout, wait);
+}
+
+static int aout_OutputVolumeSet (audio_output_t *aout, float vol)
+{
+    aout_OutputAssertLocked (aout);
+    return (aout->volume_set != NULL) ? aout->volume_set (aout, vol) : -1;
+}
+
+static int aout_OutputMuteSet (audio_output_t *aout, bool mute)
+{
+    aout_OutputAssertLocked (aout);
+    return (aout->mute_set != NULL) ? aout->mute_set (aout, mute) : -1;
+}
+
+static int aout_OutputDeviceSet (audio_output_t *aout, const char *id)
+{
+    aout_OutputAssertLocked (aout);
+    return (aout->device_select != NULL) ? aout->device_select (aout, id) : -1;
+}
+
+void aout_OutputLock (audio_output_t *aout)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    vlc_mutex_lock (&owner->lock);
+}
+
+static int aout_OutputTryLock (audio_output_t *aout)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    return vlc_mutex_trylock (&owner->lock);
+}
+
+void aout_OutputUnlock (audio_output_t *aout)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    vlc_assert_locked (&owner->lock);
+    vlc_mutex_lock (&owner->req.lock);
+
+    if (owner->req.device != unset_str)
+    {
+        aout_OutputDeviceSet (aout, owner->req.device);
+        free (owner->req.device);
+        owner->req.device = (char *)unset_str;
+    }
+
+    if (owner->req.volume >= 0.f)
+    {
+        aout_OutputVolumeSet (aout, owner->req.volume);
+        owner->req.volume = -1.f;
+    }
+
+    if (owner->req.mute >= 0)
+    {
+        aout_OutputMuteSet (aout, owner->req.mute);
+        owner->req.mute = -1;
+    }
+
+    vlc_mutex_unlock (&owner->lock);
+    /* If another thread is blocked waiting for owner->req.lock at this point,
+     * this aout_OutputUnlock() call will not see and apply its change request.
+     * The other thread will need to apply the change request itself, which
+     * implies it is able to (try-)lock owner->lock. Therefore this thread must
+     * release owner->lock _before_ owner->req.lock. Do not reorder!!! */
+    vlc_mutex_unlock (&owner->req.lock);
+}
+
+/**
+ * Gets the volume of the audio output stream (independent of mute).
+ * \return Current audio volume (0. = silent, 1. = nominal),
+ * or a strictly negative value if undefined.
+ */
+float aout_VolumeGet (audio_output_t *aout)
+{
+    return var_GetFloat (aout, "volume");
+}
+
+/**
+ * Sets the volume of the audio output stream.
+ * \note The mute status is not changed.
+ * \return 0 on success, -1 on failure (TODO).
+ */
+int aout_VolumeSet (audio_output_t *aout, float vol)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    assert (vol >= 0.f);
+    vlc_mutex_lock (&owner->req.lock);
+    owner->req.volume = vol;
+    vlc_mutex_unlock (&owner->req.lock);
+
+    if (aout_OutputTryLock (aout) == 0)
+        aout_OutputUnlock (aout);
+    return 0;
+}
+
+/**
+ * Gets the audio output stream mute flag.
+ * \return 0 if not muted, 1 if muted, -1 if undefined.
+ */
+int aout_MuteGet (audio_output_t *aout)
+{
+    return var_InheritBool (aout, "mute");
+}
+
+/**
+ * Sets the audio output stream mute flag.
+ * \return 0 on success, -1 on failure (TODO).
+ */
+int aout_MuteSet (audio_output_t *aout, bool mute)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    vlc_mutex_lock (&owner->req.lock);
+    owner->req.mute = mute;
+    vlc_mutex_unlock (&owner->req.lock);
+
+    if (aout_OutputTryLock (aout) == 0)
+        aout_OutputUnlock (aout);
+    return 0;
+}
+
+/**
+ * Gets the currently selected device.
+ * \return the selected device ID (caller must free() it)
+ *         NULL if no device is selected or in case of error.
+ */
+char *aout_DeviceGet (audio_output_t *aout)
+{
+    return var_GetNonEmptyString (aout, "device");
+}
+
+/**
+ * Selects an audio output device.
+ * \param id device ID to select, or NULL for the default device
+ * \return zero on success, non-zero on error (TODO).
+ */
+int aout_DeviceSet (audio_output_t *aout, const char *id)
+{
+    aout_owner_t *owner = aout_owner (aout);
+
+    char *dev = NULL;
+    if (id != NULL)
+    {
+        dev = strdup (id);
+        if (unlikely(dev == NULL))
+            return -1;
+    }
+
+    vlc_mutex_lock (&owner->req.lock);
+    if (owner->req.device != unset_str)
+        free (owner->req.device);
+    owner->req.device = dev;
+    vlc_mutex_unlock (&owner->req.lock);
+
+    if (aout_OutputTryLock (aout) == 0)
+        aout_OutputUnlock (aout);
+    return 0;
+}
+
+/**
+ * Enumerates possible audio output devices.
+ *
+ * The function will heap-allocate two tables of heap-allocated strings;
+ * the caller is responsible for freeing all strings and both tables.
+ *
+ * \param ids pointer to a table of device identifiers [OUT]
+ * \param names pointer to a table of device human-readable descriptions [OUT]
+ * \return the number of devices, or negative on error.
+ * \note In case of error, *ids and *names are undefined.
+ */
+int aout_DevicesList (audio_output_t *aout, char ***ids, char ***names)
+{
+    aout_owner_t *owner = aout_owner (aout);
+    char **tabid, **tabname;
+    unsigned count;
+
+    vlc_mutex_lock (&owner->dev.lock);
+    count = owner->dev.count;
+    tabid = xmalloc (sizeof (*tabid) * count);
+    tabname = xmalloc (sizeof (*tabname) * count);
+    *ids = tabid;
+    *names = tabname;
+    for (aout_dev_t *dev = owner->dev.list; dev != NULL; dev = dev->next)
+    {
+        *(tabid++) = xstrdup (dev->id);
+        *(tabname++) = xstrdup (dev->name);
+    }
+    vlc_mutex_unlock (&owner->dev.lock);
 
-    if( aout->flush != NULL )
-        aout->flush( aout, wait );
+    return count;
 }