# 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,
(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;
*/
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))
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)
{
/*
* Persistent audio output variables
*/
- vlc_value_t val, text;
module_config_t *cfg;
char *str;
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)
{
&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;
}
{
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);
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");
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;
}
}
- 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;
}
*/
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);
}
/**
*/
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;
}
/**
*/
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);
}
/**
*/
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;
}