vlc/src/audio_output/output.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);
}