mirror of https://code.videolan.org/videolan/vlc
967 lines
29 KiB
C
967 lines
29 KiB
C
/*****************************************************************************
|
|
* output.c : internal management of output streams for the audio output
|
|
*****************************************************************************
|
|
* Copyright (C) 2002-2004 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Christophe Massiot <massiot@via.ecp.fr>
|
|
*
|
|
* 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 Lesser General Public License for more details.
|
|
*
|
|
* 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 "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_configuration.h>
|
|
#include <vlc_aout.h>
|
|
#include <vlc_modules.h>
|
|
#include <vlc_atomic.h>
|
|
|
|
#include "libvlc.h"
|
|
#include "aout_internal.h"
|
|
|
|
typedef struct aout_dev
|
|
{
|
|
struct vlc_list node;
|
|
char *name;
|
|
char id[1];
|
|
} aout_dev_t;
|
|
|
|
|
|
/* Local functions */
|
|
|
|
static inline float clampf(const float value, const float min, const float max)
|
|
{
|
|
if (value < min)
|
|
return min;
|
|
else if (value > max)
|
|
return max;
|
|
else
|
|
return value;
|
|
}
|
|
|
|
static int var_Copy (vlc_object_t *src, const char *name, vlc_value_t prev,
|
|
vlc_value_t value, void *data)
|
|
{
|
|
vlc_object_t *dst = data;
|
|
|
|
(void) src; (void) prev;
|
|
return var_Set (dst, name, value);
|
|
}
|
|
|
|
static int var_CopyDevice (vlc_object_t *src, const char *name,
|
|
vlc_value_t prev, vlc_value_t value, void *data)
|
|
{
|
|
vlc_object_t *dst = data;
|
|
|
|
(void) src; (void) name; (void) prev;
|
|
return var_Set (dst, "audio-device", value);
|
|
}
|
|
|
|
static void aout_TimingNotify(audio_output_t *aout, vlc_tick_t system_ts,
|
|
vlc_tick_t audio_ts)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
assert(owner->main_stream);
|
|
vlc_aout_stream_NotifyTiming(owner->main_stream, system_ts, audio_ts);
|
|
}
|
|
|
|
static void aout_DrainedNotify(audio_output_t *aout)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
assert(owner->main_stream);
|
|
vlc_aout_stream_NotifyDrained(owner->main_stream);
|
|
}
|
|
|
|
/**
|
|
* Supply or update the current custom ("hardware") volume.
|
|
*
|
|
* @param aout the audio output notifying the new volume
|
|
* @param volume current custom volume
|
|
*
|
|
* @warning The caller (i.e. the audio output plug-in) is responsible for
|
|
* interlocking and synchronizing call to this function and to the
|
|
* audio_output_t.volume_set callback. This ensures that VLC gets correct
|
|
* volume information (possibly with a latency).
|
|
*/
|
|
static void aout_VolumeNotify (audio_output_t *aout, float volume)
|
|
{
|
|
var_SetFloat (aout, "volume", volume);
|
|
}
|
|
|
|
static void aout_MuteNotify (audio_output_t *aout, bool mute)
|
|
{
|
|
var_SetBool (aout, "mute", mute);
|
|
}
|
|
|
|
static void aout_PolicyNotify (audio_output_t *aout, bool cork)
|
|
{
|
|
(cork ? var_IncInteger : var_DecInteger)(vlc_object_parent(aout), "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 = NULL, *p;
|
|
|
|
vlc_mutex_lock (&owner->dev.lock);
|
|
vlc_list_foreach(p, &owner->dev.list, node)
|
|
{
|
|
if (!strcmp (id, p->id))
|
|
{
|
|
dev = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (name != NULL)
|
|
{
|
|
if (dev == NULL) /* Added device */
|
|
{
|
|
dev = malloc (sizeof (*dev) + strlen (id));
|
|
if (unlikely(dev == NULL))
|
|
goto out;
|
|
strcpy (dev->id, id);
|
|
vlc_list_append(&dev->node, &owner->dev.list);
|
|
owner->dev.count++;
|
|
}
|
|
else /* Modified device */
|
|
free (dev->name);
|
|
dev->name = strdup (name);
|
|
}
|
|
else
|
|
{
|
|
if (dev != NULL) /* Removed device */
|
|
{
|
|
owner->dev.count--;
|
|
vlc_list_remove(&dev->node);
|
|
free (dev->name);
|
|
free (dev);
|
|
}
|
|
}
|
|
out:
|
|
vlc_mutex_unlock (&owner->dev.lock);
|
|
}
|
|
|
|
static void aout_RestartNotify (audio_output_t *aout, unsigned mode)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
if (owner->main_stream)
|
|
vlc_aout_stream_RequestRestart(owner->main_stream, mode);
|
|
}
|
|
|
|
void aout_InputRequestRestart(audio_output_t *aout)
|
|
{
|
|
aout_RestartNotify(aout, AOUT_RESTART_FILTERS);
|
|
}
|
|
|
|
static int aout_GainNotify (audio_output_t *aout, float gain)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
|
|
vlc_mutex_assert(&owner->lock);
|
|
/* XXX: ideally, return -1 if format cannot be amplified */
|
|
if (owner->main_stream != NULL)
|
|
vlc_aout_stream_NotifyGain(owner->main_stream, gain);
|
|
return 0;
|
|
}
|
|
|
|
static const struct vlc_audio_output_events aout_events = {
|
|
aout_TimingNotify,
|
|
aout_DrainedNotify,
|
|
aout_VolumeNotify,
|
|
aout_MuteNotify,
|
|
aout_PolicyNotify,
|
|
aout_DeviceNotify,
|
|
aout_HotplugNotify,
|
|
aout_RestartNotify,
|
|
aout_GainNotify,
|
|
};
|
|
|
|
static int FilterCallback (vlc_object_t *obj, const char *var,
|
|
vlc_value_t prev, vlc_value_t cur, void *data)
|
|
{
|
|
if (strcmp(prev.psz_string, cur.psz_string))
|
|
aout_InputRequestRestart ((audio_output_t *)obj);
|
|
(void) var; (void) data;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int StereoModeCallback (vlc_object_t *obj, const char *varname,
|
|
vlc_value_t oldval, vlc_value_t newval, void *data)
|
|
{
|
|
audio_output_t *aout = (audio_output_t *)obj;
|
|
(void)varname; (void)oldval; (void)newval; (void)data;
|
|
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
vlc_mutex_lock (&owner->lock);
|
|
owner->requested_stereo_mode = newval.i_int;
|
|
vlc_mutex_unlock (&owner->lock);
|
|
|
|
aout_RestartRequest (aout, AOUT_RESTART_STEREOMODE);
|
|
return 0;
|
|
}
|
|
|
|
static int MixModeCallback (vlc_object_t *obj, const char *varname,
|
|
vlc_value_t oldval, vlc_value_t newval, void *data)
|
|
{
|
|
audio_output_t *aout = (audio_output_t *)obj;
|
|
(void)varname; (void)oldval; (void)newval; (void)data;
|
|
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
vlc_mutex_lock (&owner->lock);
|
|
owner->requested_mix_mode = newval.i_int;
|
|
vlc_mutex_unlock (&owner->lock);
|
|
|
|
aout_RestartRequest (aout, AOUT_RESTART_STEREOMODE);
|
|
return 0;
|
|
}
|
|
|
|
static void aout_ChangeViewpoint(audio_output_t *, const vlc_viewpoint_t *);
|
|
|
|
static int ViewpointCallback (vlc_object_t *obj, const char *var,
|
|
vlc_value_t prev, vlc_value_t cur, void *data)
|
|
{
|
|
if( cur.p_address != NULL )
|
|
aout_ChangeViewpoint((audio_output_t *)obj, cur.p_address );
|
|
(void) var; (void) data; (void) prev;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
#undef aout_New
|
|
/**
|
|
* Creates an audio output object and initializes an output module.
|
|
*/
|
|
audio_output_t *aout_New (vlc_object_t *parent)
|
|
{
|
|
vlc_value_t val;
|
|
|
|
audio_output_t *aout = vlc_custom_create (parent, sizeof (aout_instance_t),
|
|
"audio output");
|
|
if (unlikely(aout == NULL))
|
|
return NULL;
|
|
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
|
|
vlc_mutex_init (&owner->lock);
|
|
vlc_mutex_init (&owner->dev.lock);
|
|
vlc_mutex_init (&owner->vp.lock);
|
|
vlc_viewpoint_init (&owner->vp.value);
|
|
vlc_list_init(&owner->dev.list);
|
|
atomic_init (&owner->vp.update, false);
|
|
vlc_atomic_rc_init(&owner->rc);
|
|
vlc_audio_meter_Init(&owner->meter, aout);
|
|
|
|
owner->main_stream = NULL;
|
|
|
|
/* 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);
|
|
var_AddCallback (aout, "mute", var_Copy, parent);
|
|
var_Create (aout, "device", VLC_VAR_STRING);
|
|
var_AddCallback (aout, "device", var_CopyDevice, parent);
|
|
|
|
aout->events = &aout_events;
|
|
|
|
/* 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_var(aout, "audio output", "aout");
|
|
if (owner->module == NULL)
|
|
{
|
|
msg_Err (aout, "no suitable audio output module");
|
|
vlc_object_delete(aout);
|
|
return NULL;
|
|
}
|
|
assert(aout->start && aout->stop);
|
|
|
|
/*
|
|
* Persistent audio output variables
|
|
*/
|
|
module_config_t *cfg;
|
|
char *str;
|
|
|
|
/* Visualizations */
|
|
var_Create (aout, "visual", VLC_VAR_STRING);
|
|
var_Change(aout, "visual", VLC_VAR_SETTEXT, _("Visualizations"));
|
|
val.psz_string = (char *)"";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Disable"));
|
|
val.psz_string = (char *)"spectrometer";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrometer"));
|
|
val.psz_string = (char *)"scope";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Scope"));
|
|
val.psz_string = (char *)"spectrum";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrum"));
|
|
val.psz_string = (char *)"vuMeter";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("VU meter"));
|
|
/* Look for goom plugin */
|
|
if (module_exists ("goom"))
|
|
{
|
|
val.psz_string = (char *)"goom";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Goom");
|
|
}
|
|
/* Look for libprojectM plugin */
|
|
if (module_exists ("projectm"))
|
|
{
|
|
val.psz_string = (char *)"projectm";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "projectM");
|
|
}
|
|
/* Look for VSXu plugin */
|
|
if (module_exists ("vsxu"))
|
|
{
|
|
val.psz_string = (char *)"vsxu";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Vovoid VSXU");
|
|
}
|
|
/* Look for glspectrum plugin */
|
|
if (module_exists ("glspectrum"))
|
|
{
|
|
val.psz_string = (char *)"glspectrum";
|
|
var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "3D spectrum");
|
|
}
|
|
str = var_GetNonEmptyString (aout, "effect-list");
|
|
if (str != NULL)
|
|
{
|
|
var_SetString (aout, "visual", str);
|
|
free (str);
|
|
}
|
|
|
|
var_Create (aout, "audio-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
|
|
var_AddCallback (aout, "audio-filter", FilterCallback, NULL);
|
|
var_Change(aout, "audio-filter", VLC_VAR_SETTEXT, _("Audio filters"));
|
|
|
|
var_Create (aout, "viewpoint", VLC_VAR_ADDRESS );
|
|
var_AddCallback (aout, "viewpoint", ViewpointCallback, NULL);
|
|
|
|
var_Create (aout, "audio-visual", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
|
|
var_Change(aout, "audio-visual", VLC_VAR_SETTEXT,
|
|
_("Audio visualizations"));
|
|
|
|
/* Replay gain */
|
|
var_Create (aout, "audio-replay-gain-mode",
|
|
VLC_VAR_STRING | VLC_VAR_DOINHERIT );
|
|
var_Change(aout, "audio-replay-gain-mode", VLC_VAR_SETTEXT,
|
|
_("Replay gain"));
|
|
cfg = config_FindConfig("audio-replay-gain-mode");
|
|
if (likely(cfg != NULL))
|
|
for (unsigned i = 0; i < cfg->list_count; i++)
|
|
{
|
|
val.psz_string = (char *)cfg->list.psz[i];
|
|
var_Change(aout, "audio-replay-gain-mode", VLC_VAR_ADDCHOICE,
|
|
val, vlc_gettext(cfg->list_text[i]));
|
|
}
|
|
|
|
/* Stereo mode */
|
|
var_Create (aout, "stereo-mode", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
|
|
owner->requested_stereo_mode = var_GetInteger (aout, "stereo-mode");
|
|
|
|
var_AddCallback (aout, "stereo-mode", StereoModeCallback, NULL);
|
|
var_Change(aout, "stereo-mode", VLC_VAR_SETTEXT, _("Stereo audio mode"));
|
|
|
|
/* Mix mode */
|
|
var_Create (aout, "mix-mode", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
|
|
owner->requested_mix_mode = var_GetInteger (aout, "mix-mode");
|
|
var_AddCallback (aout, "mix-mode", MixModeCallback, NULL);
|
|
var_Change(aout, "mix-mode", VLC_VAR_SETTEXT, _("Audio mix mode"));
|
|
|
|
/* Equalizer */
|
|
var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
|
|
var_Create (aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
|
|
var_Create (aout, "equalizer-preset", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
|
|
|
|
owner->bitexact = var_InheritBool (aout, "audio-bitexact");
|
|
|
|
return aout;
|
|
}
|
|
|
|
audio_output_t *aout_Hold(audio_output_t *aout)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
|
|
vlc_atomic_rc_inc(&owner->rc);
|
|
return aout;
|
|
}
|
|
|
|
/**
|
|
* Deinitializes an audio output module and destroys an audio output object.
|
|
*/
|
|
static void aout_Destroy (audio_output_t *aout)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
|
|
vlc_mutex_lock(&owner->lock);
|
|
module_unneed (aout, owner->module);
|
|
/* Protect against late call from intf.c */
|
|
aout->volume_set = NULL;
|
|
aout->mute_set = NULL;
|
|
aout->device_select = NULL;
|
|
vlc_audio_meter_Destroy(&owner->meter);
|
|
vlc_mutex_unlock(&owner->lock);
|
|
|
|
var_DelCallback (aout, "viewpoint", ViewpointCallback, NULL);
|
|
var_DelCallback (aout, "audio-filter", FilterCallback, NULL);
|
|
var_DelCallback(aout, "device", var_CopyDevice, vlc_object_parent(aout));
|
|
var_DelCallback(aout, "mute", var_Copy, vlc_object_parent(aout));
|
|
var_SetFloat (aout, "volume", -1.f);
|
|
var_DelCallback(aout, "volume", var_Copy, vlc_object_parent(aout));
|
|
var_DelCallback (aout, "stereo-mode", StereoModeCallback, NULL);
|
|
var_DelCallback (aout, "mix-mode", MixModeCallback, NULL);
|
|
|
|
aout_dev_t *dev;
|
|
vlc_list_foreach(dev, &owner->dev.list, node)
|
|
{
|
|
vlc_list_remove(&dev->node);
|
|
free (dev->name);
|
|
free (dev);
|
|
}
|
|
|
|
vlc_object_delete(VLC_OBJECT(aout));
|
|
}
|
|
|
|
void aout_Release(audio_output_t *aout)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
|
|
if (!vlc_atomic_rc_dec(&owner->rc))
|
|
return;
|
|
|
|
aout_Destroy(aout);
|
|
}
|
|
|
|
static int aout_PrepareStereoMode(audio_output_t *aout,
|
|
const audio_sample_format_t *restrict fmt)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
|
|
/* Fill Stereo mode choices */
|
|
vlc_value_t val;
|
|
const char *txt;
|
|
val.i_int = 0;
|
|
|
|
if (!AOUT_FMT_LINEAR(fmt) || fmt->i_channels != 2)
|
|
return AOUT_VAR_CHAN_UNSET;
|
|
|
|
int i_default_mode = owner->requested_stereo_mode;
|
|
|
|
val.i_int = AOUT_VAR_CHAN_MONO;
|
|
var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Mono"));
|
|
|
|
if (fmt->i_chan_mode & AOUT_CHANMODE_DOLBYSTEREO)
|
|
{
|
|
val.i_int = AOUT_VAR_CHAN_DOLBYS;
|
|
txt = _("Dolby Surround");
|
|
}
|
|
else
|
|
{
|
|
val.i_int = AOUT_VAR_CHAN_STEREO;
|
|
txt = _("Stereo");
|
|
}
|
|
var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, txt);
|
|
|
|
if (fmt->i_chan_mode & AOUT_CHANMODE_DUALMONO)
|
|
i_default_mode = AOUT_VAR_CHAN_LEFT;
|
|
else
|
|
i_default_mode = val.i_int; /* Stereo or Dolby Surround */
|
|
|
|
val.i_int = AOUT_VAR_CHAN_LEFT;
|
|
var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Left"));
|
|
val.i_int = AOUT_VAR_CHAN_RIGHT;
|
|
var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Right"));
|
|
|
|
val.i_int = AOUT_VAR_CHAN_RSTEREO;
|
|
var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val,
|
|
_("Reverse stereo"));
|
|
|
|
return i_default_mode;
|
|
}
|
|
|
|
static void aout_UpdateStereoMode(audio_output_t *aout, int mode,
|
|
audio_sample_format_t *restrict fmt,
|
|
aout_filters_cfg_t *filters_cfg)
|
|
{
|
|
/* The user may have selected a different channels configuration. */
|
|
switch (mode)
|
|
{
|
|
case AOUT_VAR_CHAN_RSTEREO:
|
|
filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_RIGHT;
|
|
filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_LEFT;
|
|
break;
|
|
case AOUT_VAR_CHAN_STEREO:
|
|
break;
|
|
case AOUT_VAR_CHAN_LEFT:
|
|
filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_DISABLE;
|
|
fmt->i_physical_channels = AOUT_CHAN_CENTER;
|
|
aout_FormatPrepare (fmt);
|
|
break;
|
|
case AOUT_VAR_CHAN_RIGHT:
|
|
filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_DISABLE;
|
|
fmt->i_physical_channels = AOUT_CHAN_CENTER;
|
|
aout_FormatPrepare (fmt);
|
|
break;
|
|
case AOUT_VAR_CHAN_DOLBYS:
|
|
fmt->i_chan_mode = AOUT_CHANMODE_DOLBYSTEREO;
|
|
break;
|
|
case AOUT_VAR_CHAN_MONO:
|
|
/* Remix all channels into one */
|
|
for (size_t i = 0; i < AOUT_CHANIDX_MAX; ++ i)
|
|
filters_cfg->remap[i] = AOUT_CHANIDX_LEFT;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
var_Change(aout, "stereo-mode", VLC_VAR_SETVALUE,
|
|
(vlc_value_t) { .i_int = mode});
|
|
}
|
|
|
|
static bool aout_HasStereoMode(audio_output_t *aout, int mode)
|
|
{
|
|
bool mode_available = false;
|
|
vlc_value_t *vals;
|
|
size_t count;
|
|
|
|
if (!var_Change(aout, "stereo-mode", VLC_VAR_GETCHOICES,
|
|
&count, &vals, (char ***)NULL))
|
|
{
|
|
for (size_t i = 0; !mode_available && i < count; ++i)
|
|
{
|
|
if (vals[i].i_int == mode)
|
|
mode_available = true;
|
|
}
|
|
free(vals);
|
|
}
|
|
return mode_available;
|
|
}
|
|
|
|
static void aout_AddMixModeChoice(audio_output_t *aout, int mode,
|
|
const char *suffix,
|
|
const audio_sample_format_t *restrict fmt)
|
|
{
|
|
assert(suffix);
|
|
const char *text;
|
|
char *buffer = NULL;
|
|
|
|
if (fmt == NULL)
|
|
text = suffix;
|
|
else
|
|
{
|
|
const char *channels = aout_FormatPrintChannels(fmt);
|
|
if (asprintf(&buffer, "%s: %s", suffix, channels) < 0)
|
|
return;
|
|
text = buffer;
|
|
}
|
|
|
|
vlc_value_t val = { .i_int = mode };
|
|
var_Change(aout, "mix-mode", VLC_VAR_ADDCHOICE, val, text);
|
|
|
|
free(buffer);
|
|
}
|
|
|
|
static void aout_SetupMixModeChoices (audio_output_t *aout,
|
|
const audio_sample_format_t *restrict fmt)
|
|
{
|
|
if (fmt->i_channels <= 2)
|
|
return;
|
|
|
|
const bool has_spatialaudio = module_exists("spatialaudio");
|
|
|
|
/* Don't propose the mix option if we don't have the spatialaudio module
|
|
* and if the content is ambisonics */
|
|
if (fmt->channel_type != AUDIO_CHANNEL_TYPE_AMBISONICS || has_spatialaudio)
|
|
{
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_UNSET, _("Original"), fmt);
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_STEREO, _("Stereo"), NULL);
|
|
}
|
|
|
|
if (has_spatialaudio)
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL, _("Binaural"), NULL);
|
|
|
|
/* Only propose Original and Binaural for Ambisonics content */
|
|
if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS && has_spatialaudio)
|
|
return;
|
|
|
|
if (fmt->i_physical_channels != AOUT_CHANS_4_0)
|
|
{
|
|
static const audio_sample_format_t fmt_4_0 = {
|
|
.i_physical_channels = AOUT_CHANS_4_0,
|
|
.i_channels = 4,
|
|
};
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_4_0, _("4.0"), &fmt_4_0);
|
|
}
|
|
|
|
if (fmt->i_physical_channels != AOUT_CHANS_5_1)
|
|
{
|
|
static const audio_sample_format_t fmt_5_1 = {
|
|
.i_physical_channels = AOUT_CHANS_5_1,
|
|
.i_channels = 6,
|
|
};
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_5_1, _("5.1"), &fmt_5_1);
|
|
}
|
|
|
|
if (fmt->i_physical_channels != AOUT_CHANS_7_1)
|
|
{
|
|
static const audio_sample_format_t fmt_7_1 = {
|
|
.i_physical_channels = AOUT_CHANS_7_1,
|
|
.i_channels = 8,
|
|
};
|
|
aout_AddMixModeChoice(aout, AOUT_MIX_MODE_7_1, _("7.1"), &fmt_7_1);
|
|
}
|
|
}
|
|
|
|
static bool aout_HasMixModeChoice(audio_output_t *aout, int mode)
|
|
{
|
|
bool mode_available = false;
|
|
vlc_value_t *vals;
|
|
size_t count;
|
|
|
|
if (!var_Change(aout, "mix-mode", VLC_VAR_GETCHOICES,
|
|
&count, &vals, (char ***)NULL))
|
|
{
|
|
for (size_t i = 0; !mode_available && i < count; ++i)
|
|
{
|
|
if (vals[i].i_int == mode)
|
|
mode_available = true;
|
|
}
|
|
free(vals);
|
|
}
|
|
return mode_available;
|
|
}
|
|
|
|
|
|
static void aout_UpdateMixMode(audio_output_t *aout, int mode,
|
|
audio_sample_format_t *restrict fmt)
|
|
{
|
|
/* The user may have selected a different channels configuration. */
|
|
switch (mode)
|
|
{
|
|
case AOUT_MIX_MODE_UNSET:
|
|
break;
|
|
case AOUT_MIX_MODE_BINAURAL:
|
|
fmt->i_physical_channels = AOUT_CHANS_STEREO;
|
|
fmt->i_chan_mode = AOUT_CHANMODE_BINAURAL;
|
|
break;
|
|
case AOUT_MIX_MODE_STEREO:
|
|
fmt->i_physical_channels = AOUT_CHANS_STEREO;
|
|
break;
|
|
case AOUT_MIX_MODE_4_0:
|
|
fmt->i_physical_channels = AOUT_CHANS_4_0;
|
|
break;
|
|
case AOUT_MIX_MODE_5_1:
|
|
fmt->i_physical_channels = AOUT_CHANS_5_1;
|
|
break;
|
|
case AOUT_MIX_MODE_7_1:
|
|
fmt->i_physical_channels = AOUT_CHANS_7_1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
assert(mode == AOUT_VAR_CHAN_UNSET || aout_HasMixModeChoice(aout, mode));
|
|
|
|
var_Change(aout, "mix-mode", VLC_VAR_SETVALUE, (vlc_value_t) { .i_int = mode});
|
|
}
|
|
|
|
int aout_OutputNew(audio_output_t *aout, vlc_aout_stream *stream,
|
|
audio_sample_format_t *fmt, int input_profile,
|
|
audio_sample_format_t *filter_fmt,
|
|
aout_filters_cfg_t *filters_cfg)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
|
|
vlc_fourcc_t formats[] = {
|
|
fmt->i_format, 0, 0
|
|
};
|
|
|
|
var_Change(aout, "stereo-mode", VLC_VAR_CLEARCHOICES);
|
|
var_Change(aout, "mix-mode", VLC_VAR_CLEARCHOICES);
|
|
|
|
/* 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))
|
|
{
|
|
if (fmt->channel_type == AUDIO_CHANNEL_TYPE_BITMAP
|
|
&& aout_FormatNbChannels(fmt) == 0)
|
|
{
|
|
/* The output channel map is unknown, use the WAVE one. */
|
|
assert(fmt->i_channels > 0);
|
|
aout_SetWavePhysicalChannels(fmt);
|
|
}
|
|
|
|
if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
|
|
{
|
|
/* Set the maximum of channels to render ambisonics contents. The
|
|
* aout module will still be free to select less channels in order
|
|
* to respect the sink setup. */
|
|
fmt->i_physical_channels = AOUT_CHANS_7_1;
|
|
}
|
|
|
|
/* 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_SetupMixModeChoices(aout, fmt);
|
|
|
|
/* Prefer the user requested mode if available, otherwise, use the
|
|
* default one */
|
|
if (aout_HasMixModeChoice(aout, owner->requested_mix_mode))
|
|
aout_UpdateMixMode(aout, owner->requested_mix_mode, fmt);
|
|
|
|
aout_FormatPrepare (fmt);
|
|
assert (aout_FormatNbChannels(fmt) > 0);
|
|
}
|
|
else
|
|
{
|
|
switch (fmt->i_format)
|
|
{
|
|
case VLC_CODEC_DTS:
|
|
if (input_profile > 0)
|
|
{
|
|
assert(ARRAY_SIZE(formats) >= 3);
|
|
/* DTSHD can be played as DTSHD or as DTS */
|
|
formats[0] = VLC_CODEC_DTSHD;
|
|
formats[1] = VLC_CODEC_DTS;
|
|
}
|
|
break;
|
|
case VLC_CODEC_A52:
|
|
if (input_profile > 0)
|
|
{
|
|
assert(ARRAY_SIZE(formats) >= 3);
|
|
formats[0] = VLC_CODEC_EAC3;
|
|
formats[1] = VLC_CODEC_A52;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int stereo_mode = aout_PrepareStereoMode(aout, fmt);
|
|
|
|
if (stereo_mode != AOUT_VAR_CHAN_UNSET
|
|
&& aout_HasStereoMode(aout, stereo_mode))
|
|
aout_UpdateStereoMode(aout, stereo_mode, fmt, filters_cfg);
|
|
|
|
aout->current_sink_info.headphones = false;
|
|
|
|
vlc_mutex_lock(&owner->lock);
|
|
/* XXX: Remove when aout/stream support is complete (in all modules) */
|
|
assert(owner->main_stream == NULL);
|
|
|
|
int ret = VLC_EGENERIC;
|
|
for (size_t i = 0; formats[i] != 0 && ret != VLC_SUCCESS; ++i)
|
|
{
|
|
filter_fmt->i_format = fmt->i_format = formats[i];
|
|
owner->main_stream = stream;
|
|
ret = aout->start(aout, fmt);
|
|
if (ret != 0)
|
|
owner->main_stream = NULL;
|
|
}
|
|
vlc_mutex_unlock(&owner->lock);
|
|
if (ret)
|
|
{
|
|
if (AOUT_FMT_LINEAR(fmt))
|
|
msg_Err (aout, "failed to start audio output");
|
|
else
|
|
msg_Warn (aout, "failed to start passthrough audio output, "
|
|
"failing back to linear format");
|
|
return -1;
|
|
}
|
|
assert(aout->flush != NULL && aout->play != NULL);
|
|
|
|
/* Autoselect the headphones mode if available and if the user didn't
|
|
* request any mode */
|
|
if (aout->current_sink_info.headphones
|
|
&& owner->requested_mix_mode == AOUT_VAR_CHAN_UNSET
|
|
&& fmt->i_physical_channels == AOUT_CHANS_STEREO
|
|
&& aout_HasMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL))
|
|
{
|
|
assert(stereo_mode == AOUT_VAR_CHAN_UNSET);
|
|
aout_UpdateMixMode(aout, AOUT_MIX_MODE_BINAURAL, fmt);
|
|
}
|
|
|
|
aout_FormatPrepare (fmt);
|
|
assert (fmt->i_bytes_per_frame > 0 && fmt->i_frame_length > 0);
|
|
aout_FormatPrint (aout, "output", fmt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void aout_OutputDelete (audio_output_t *aout)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
vlc_mutex_lock(&owner->lock);
|
|
aout->stop (aout);
|
|
owner->main_stream = NULL;
|
|
vlc_mutex_unlock(&owner->lock);
|
|
}
|
|
|
|
float aout_VolumeGet (audio_output_t *aout)
|
|
{
|
|
return var_GetFloat (aout, "volume");
|
|
}
|
|
|
|
int aout_VolumeSet (audio_output_t *aout, float vol)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
int ret;
|
|
|
|
vlc_mutex_lock(&owner->lock);
|
|
ret = aout->volume_set ? aout->volume_set(aout, vol) : -1;
|
|
vlc_mutex_unlock(&owner->lock);
|
|
return ret ? -1 : 0;
|
|
}
|
|
|
|
int aout_VolumeUpdate (audio_output_t *aout, int value, float *volp)
|
|
{
|
|
int ret = -1;
|
|
const float defaultVolume = (float)AOUT_VOLUME_DEFAULT;
|
|
const float stepSize = var_InheritFloat (aout, "volume-step") / defaultVolume;
|
|
float vol = aout_VolumeGet (aout);
|
|
|
|
if (vol >= 0.f)
|
|
{
|
|
vol += (value * stepSize);
|
|
vol = (roundf (vol / stepSize)) * stepSize;
|
|
vol = clampf(vol, 0.f, AOUT_VOLUME_MAX / defaultVolume);
|
|
if (volp != NULL)
|
|
*volp = vol;
|
|
ret = aout_VolumeSet (aout, vol);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int aout_MuteGet (audio_output_t *aout)
|
|
{
|
|
return var_InheritBool (aout, "mute");
|
|
}
|
|
|
|
int aout_MuteSet (audio_output_t *aout, bool mute)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
int ret;
|
|
|
|
vlc_mutex_lock(&owner->lock);
|
|
ret = aout->mute_set ? aout->mute_set(aout, mute) : -1;
|
|
vlc_mutex_unlock(&owner->lock);
|
|
return ret ? -1 : 0;
|
|
}
|
|
|
|
char *aout_DeviceGet (audio_output_t *aout)
|
|
{
|
|
return var_GetNonEmptyString (aout, "device");
|
|
}
|
|
|
|
int aout_DeviceSet (audio_output_t *aout, const char *id)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
int ret;
|
|
|
|
vlc_mutex_lock(&owner->lock);
|
|
ret = aout->device_select ? aout->device_select(aout, id) : -1;
|
|
vlc_mutex_unlock(&owner->lock);
|
|
return ret ? -1 : 0;
|
|
}
|
|
|
|
int aout_DevicesList (audio_output_t *aout, char ***ids, char ***names)
|
|
{
|
|
aout_owner_t *owner = aout_owner (aout);
|
|
char **tabid, **tabname;
|
|
unsigned i = 0;
|
|
|
|
vlc_mutex_lock (&owner->dev.lock);
|
|
tabid = vlc_alloc (owner->dev.count, sizeof (*tabid));
|
|
tabname = vlc_alloc (owner->dev.count, sizeof (*tabname));
|
|
|
|
if (unlikely(tabid == NULL || tabname == NULL))
|
|
goto error;
|
|
|
|
*ids = tabid;
|
|
*names = tabname;
|
|
|
|
aout_dev_t *dev;
|
|
vlc_list_foreach(dev, &owner->dev.list, node)
|
|
{
|
|
tabid[i] = strdup(dev->id);
|
|
if (unlikely(tabid[i] == NULL))
|
|
goto error;
|
|
|
|
tabname[i] = strdup(dev->name);
|
|
if (unlikely(tabname[i] == NULL))
|
|
{
|
|
free(tabid[i]);
|
|
goto error;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
vlc_mutex_unlock (&owner->dev.lock);
|
|
|
|
return i;
|
|
|
|
error:
|
|
vlc_mutex_unlock(&owner->dev.lock);
|
|
while (i > 0)
|
|
{
|
|
i--;
|
|
free(tabname[i]);
|
|
free(tabid[i]);
|
|
}
|
|
free(tabname);
|
|
free(tabid);
|
|
return -1;
|
|
}
|
|
|
|
static void aout_ChangeViewpoint(audio_output_t *aout,
|
|
const vlc_viewpoint_t *p_viewpoint)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
|
|
vlc_mutex_lock(&owner->vp.lock);
|
|
owner->vp.value = *p_viewpoint;
|
|
atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
|
|
vlc_mutex_unlock(&owner->vp.lock);
|
|
}
|
|
|
|
vlc_audio_meter_plugin *
|
|
aout_AddMeterPlugin(audio_output_t *aout, const char *chain,
|
|
const struct vlc_audio_meter_plugin_owner *meter_plugin_owner)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
|
|
return vlc_audio_meter_AddPlugin(&owner->meter, chain, meter_plugin_owner);
|
|
}
|
|
|
|
void
|
|
aout_RemoveMeterPlugin(audio_output_t *aout, vlc_audio_meter_plugin *plugin)
|
|
{
|
|
aout_owner_t *owner = aout_owner(aout);
|
|
|
|
vlc_audio_meter_RemovePlugin(&owner->meter, plugin);
|
|
}
|