output.c 29.7 KB
Newer Older
1
2
3
/*****************************************************************************
 * output.c : internal management of output streams for the audio output
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002-2004 VLC authors and VideoLAN
5
6
7
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
8
9
10
 * 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
11
 * (at your option) any later version.
12
 *
13
14
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
15
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
17
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
18
19
20
 * 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.
21
22
 *****************************************************************************/

23
24
25
26
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

27
28
29
#include <stdlib.h>
#include <assert.h>

30
#include <vlc_common.h>
zorglub's avatar
zorglub committed
31
#include <vlc_aout.h>
32
#include <vlc_modules.h>
Quentin Chateau's avatar
Quentin Chateau committed
33
#include <vlc_atomic.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
34

35
#include "libvlc.h"
36
37
#include "aout_internal.h"

38
typedef struct aout_dev
39
{
40
    struct vlc_list node;
41
42
    char *name;
    char id[1];
43
} aout_dev_t;
44
45


46
/* Local functions */
47

48
49
50
51
52
53
54
55
56
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);
}

57
58
59
60
61
62
63
64
65
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);
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
66
67
68
69
70
71
static void aout_TimingNotify(audio_output_t *aout, vlc_tick_t system_ts,
                              vlc_tick_t audio_ts)
{
    aout_RequestRetiming(aout, system_ts, audio_ts);
}

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * Supply or update the current custom ("hardware") 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)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
93
    (cork ? var_IncInteger : var_DecInteger)(vlc_object_parent(aout), "corks");
94
95
}

96
97
static void aout_DeviceNotify (audio_output_t *aout, const char *id)
{
98
    var_SetString (aout, "device", (id != NULL) ? id : "");
99
100
}

101
102
103
104
static void aout_HotplugNotify (audio_output_t *aout,
                                const char *id, const char *name)
{
    aout_owner_t *owner = aout_owner (aout);
105
    aout_dev_t *dev = NULL, *p;
106
107

    vlc_mutex_lock (&owner->dev.lock);
108
    vlc_list_foreach(p, &owner->dev.list, node)
109
    {
110
111
112
        if (!strcmp (id, p->id))
        {
            dev = p;
113
            break;
114
        }
115
116
117
118
119
120
121
122
123
124
    }

    if (name != NULL)
    {
        if (dev == NULL) /* Added device */
        {
            dev = malloc (sizeof (*dev) + strlen (id));
            if (unlikely(dev == NULL))
                goto out;
            strcpy (dev->id, id);
125
            vlc_list_append(&dev->node, &owner->dev.list);
126
127
128
129
130
131
132
133
134
135
136
            owner->dev.count++;
        }
        else /* Modified device */
            free (dev->name);
        dev->name = strdup (name);
    }
    else
    {
        if (dev != NULL) /* Removed device */
        {
            owner->dev.count--;
137
            vlc_list_remove(&dev->node);
138
139
140
141
142
143
144
145
            free (dev->name);
            free (dev);
        }
    }
out:
    vlc_mutex_unlock (&owner->dev.lock);
}

146
147
148
149
150
static void aout_RestartNotify (audio_output_t *aout, unsigned mode)
{
    aout_RequestRestart (aout, mode);
}

151
152
153
154
static int aout_GainNotify (audio_output_t *aout, float gain)
{
    aout_owner_t *owner = aout_owner (aout);

155
    vlc_mutex_assert(&owner->lock);
156
157
158
159
160
    aout_volume_SetVolume (owner->volume, gain);
    /* XXX: ideally, return -1 if format cannot be amplified */
    return 0;
}

161
static const struct vlc_audio_output_events aout_events = {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
162
    aout_TimingNotify,
163
164
165
166
167
168
169
170
171
    aout_VolumeNotify,
    aout_MuteNotify,
    aout_PolicyNotify,
    aout_DeviceNotify,
    aout_HotplugNotify,
    aout_RestartNotify,
    aout_GainNotify,
};

172
173
174
static int FilterCallback (vlc_object_t *obj, const char *var,
                           vlc_value_t prev, vlc_value_t cur, void *data)
{
175
176
177
    if (strcmp(prev.psz_string, cur.psz_string))
        aout_InputRequestRestart ((audio_output_t *)obj);
    (void) var; (void) data;
178
179
180
    return VLC_SUCCESS;
}

181
182
183
184
185
186
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;

187
188
189
190
191
    aout_owner_t *owner = aout_owner (aout);
    vlc_mutex_lock (&owner->lock);
    owner->requested_stereo_mode = newval.i_int;
    vlc_mutex_unlock (&owner->lock);

192
    aout_RestartRequest (aout, AOUT_RESTART_STEREOMODE);
193
194
195
    return 0;
}

Thomas Guillem's avatar
Thomas Guillem committed
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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;
}

211
212
static void aout_ChangeViewpoint(audio_output_t *, const vlc_viewpoint_t *);

213
214
215
216
217
218
219
220
221
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;
}

222
223
224
225
226
227
#undef aout_New
/**
 * Creates an audio output object and initializes an output module.
 */
audio_output_t *aout_New (vlc_object_t *parent)
{
228
    vlc_value_t val;
229

230
231
232
233
234
235
236
237
    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);
238
    vlc_mutex_init (&owner->dev.lock);
239
    vlc_mutex_init (&owner->vp.lock);
240
    vlc_viewpoint_init (&owner->vp.value);
241
    vlc_list_init(&owner->dev.list);
242
    atomic_init (&owner->vp.update, false);
Quentin Chateau's avatar
Quentin Chateau committed
243
    vlc_atomic_rc_init(&owner->rc);
244
    vlc_audio_meter_Init(&owner->meter, aout);
245

246
247
248
    /* Audio output module callbacks */
    var_Create (aout, "volume", VLC_VAR_FLOAT);
    var_AddCallback (aout, "volume", var_Copy, parent);
249
    var_Create (aout, "mute", VLC_VAR_BOOL);
250
    var_AddCallback (aout, "mute", var_Copy, parent);
251
    var_Create (aout, "device", VLC_VAR_STRING);
252
    var_AddCallback (aout, "device", var_CopyDevice, parent);
253

254
    aout->events = &aout_events;
255
256
257
258
259
260

    /* Audio output module initialization */
    aout->start = NULL;
    aout->stop = NULL;
    aout->volume_set = NULL;
    aout->mute_set = NULL;
261
    aout->device_select = NULL;
262
    owner->module = module_need_var(aout, "audio output", "aout");
263
264
265
    if (owner->module == NULL)
    {
        msg_Err (aout, "no suitable audio output module");
266
        vlc_object_delete(aout);
267
268
        return NULL;
    }
269
    assert(aout->start && aout->stop);
270
271
272
273
274
275
276
277

    /*
     * Persistent audio output variables
     */
    module_config_t *cfg;
    char *str;

    /* Visualizations */
278
    var_Create (aout, "visual", VLC_VAR_STRING);
279
    var_Change(aout, "visual", VLC_VAR_SETTEXT, _("Visualizations"));
280
    val.psz_string = (char *)"";
281
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Disable"));
282
    val.psz_string = (char *)"spectrometer";
283
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrometer"));
284
    val.psz_string = (char *)"scope";
285
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Scope"));
286
    val.psz_string = (char *)"spectrum";
287
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrum"));
288
    val.psz_string = (char *)"vuMeter";
289
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("VU meter"));
290
291
292
293
    /* Look for goom plugin */
    if (module_exists ("goom"))
    {
        val.psz_string = (char *)"goom";
294
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Goom");
295
296
297
298
299
    }
    /* Look for libprojectM plugin */
    if (module_exists ("projectm"))
    {
        val.psz_string = (char *)"projectm";
300
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "projectM");
301
302
303
304
305
    }
    /* Look for VSXu plugin */
    if (module_exists ("vsxu"))
    {
        val.psz_string = (char *)"vsxu";
306
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Vovoid VSXU");
307
    }
308
309
310
311
    /* Look for glspectrum plugin */
    if (module_exists ("glspectrum"))
    {
        val.psz_string = (char *)"glspectrum";
312
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "3D spectrum");
313
    }
314
315
316
317
318
319
320
321
    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);
322
    var_AddCallback (aout, "audio-filter", FilterCallback, NULL);
323
    var_Change(aout, "audio-filter", VLC_VAR_SETTEXT, _("Audio filters"));
324

325
    var_Create (aout, "viewpoint", VLC_VAR_ADDRESS );
326
    var_AddCallback (aout, "viewpoint", ViewpointCallback, NULL);
327
328

    var_Create (aout, "audio-visual", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
329
330
    var_Change(aout, "audio-visual", VLC_VAR_SETTEXT,
               _("Audio visualizations"));
331
332
333
334

    /* Replay gain */
    var_Create (aout, "audio-replay-gain-mode",
                VLC_VAR_STRING | VLC_VAR_DOINHERIT );
335
336
    var_Change(aout, "audio-replay-gain-mode", VLC_VAR_SETTEXT,
               _("Replay gain"));
337
    cfg = config_FindConfig("audio-replay-gain-mode");
338
339
340
    if (likely(cfg != NULL))
        for (unsigned i = 0; i < cfg->list_count; i++)
        {
341
            val.psz_string = (char *)cfg->list.psz[i];
342
343
            var_Change(aout, "audio-replay-gain-mode", VLC_VAR_ADDCHOICE,
                       val, vlc_gettext(cfg->list_text[i]));
344
345
        }

346
347
    /* Stereo mode */
    var_Create (aout, "stereo-mode", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
348
    owner->requested_stereo_mode = var_GetInteger (aout, "stereo-mode");
349

350
    var_AddCallback (aout, "stereo-mode", StereoModeCallback, NULL);
351
    var_Change(aout, "stereo-mode", VLC_VAR_SETTEXT, _("Stereo audio mode"));
352

Thomas Guillem's avatar
Thomas Guillem committed
353
354
355
356
357
358
    /* 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"));

359
    /* Equalizer */
Mark Lee's avatar
Mark Lee committed
360
361
    var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
    var_Create (aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
362
    var_Create (aout, "equalizer-preset", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
Mark Lee's avatar
Mark Lee committed
363

364
365
    owner->bitexact = var_InheritBool (aout, "audio-bitexact");

366
367
368
    return aout;
}

369
370
audio_output_t *aout_Hold(audio_output_t *aout)
{
371
372
    aout_owner_t *owner = aout_owner(aout);

Quentin Chateau's avatar
Quentin Chateau committed
373
    vlc_atomic_rc_inc(&owner->rc);
374
375
376
    return aout;
}

377
378
379
380
381
382
383
/**
 * Deinitializes an audio output module and destroys an audio output object.
 */
void aout_Destroy (audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner (aout);

384
    vlc_mutex_lock(&owner->lock);
385
386
387
388
    module_unneed (aout, owner->module);
    /* Protect against late call from intf.c */
    aout->volume_set = NULL;
    aout->mute_set = NULL;
389
    aout->device_select = NULL;
390
    vlc_audio_meter_Destroy(&owner->meter);
391
    vlc_mutex_unlock(&owner->lock);
392

393
    var_DelCallback (aout, "viewpoint", ViewpointCallback, NULL);
394
    var_DelCallback (aout, "audio-filter", FilterCallback, NULL);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
395
396
    var_DelCallback(aout, "device", var_CopyDevice, vlc_object_parent(aout));
    var_DelCallback(aout, "mute", var_Copy, vlc_object_parent(aout));
397
    var_SetFloat (aout, "volume", -1.f);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
398
    var_DelCallback(aout, "volume", var_Copy, vlc_object_parent(aout));
399
    var_DelCallback (aout, "stereo-mode", StereoModeCallback, NULL);
Thomas Guillem's avatar
Thomas Guillem committed
400
    var_DelCallback (aout, "mix-mode", MixModeCallback, NULL);
401
    aout_Release(aout);
402
403
}

404
405
void aout_Release(audio_output_t *aout)
{
406
407
    aout_owner_t *owner = aout_owner(aout);

Quentin Chateau's avatar
Quentin Chateau committed
408
    if (!vlc_atomic_rc_dec(&owner->rc))
409
410
        return;

411
412
    aout_dev_t *dev;
    vlc_list_foreach(dev, &owner->dev.list, node)
413
    {
414
        vlc_list_remove(&dev->node);
415
416
417
418
        free (dev->name);
        free (dev);
    }

419
    vlc_object_delete(VLC_OBJECT(aout));
420
421
}

422
static int aout_PrepareStereoMode(audio_output_t *aout,
423
                                  const audio_sample_format_t *restrict fmt)
424
{
425
426
    aout_owner_t *owner = aout_owner (aout);

427
    /* Fill Stereo mode choices */
428
429
    vlc_value_t val;
    const char *txt;
430
    val.i_int = 0;
431

432
    if (!AOUT_FMT_LINEAR(fmt) || fmt->i_channels != 2)
433
        return AOUT_VAR_CHAN_UNSET;
434

435
    int i_default_mode = owner->requested_stereo_mode;
436

437
    val.i_int = AOUT_VAR_CHAN_MONO;
438
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Mono"));
439

440
    if (fmt->i_chan_mode & AOUT_CHANMODE_DOLBYSTEREO)
441
442
    {
        val.i_int = AOUT_VAR_CHAN_DOLBYS;
443
        txt = _("Dolby Surround");
444
445
446
447
    }
    else
    {
        val.i_int = AOUT_VAR_CHAN_STEREO;
448
        txt = _("Stereo");
449
    }
450
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, txt);
451

452
453
454
455
456
457
458
459
460
461
462
463
464
    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"));
465

466
467
    return i_default_mode;
}
468

469
470
471
472
static void aout_UpdateStereoMode(audio_output_t *aout, int mode,
                                  audio_sample_format_t *restrict fmt,
                                  aout_filters_cfg_t *filters_cfg)
{
473
    /* The user may have selected a different channels configuration. */
474
    switch (mode)
475
    {
476
        case AOUT_VAR_CHAN_RSTEREO:
477
478
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_RIGHT;
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_LEFT;
479
            break;
480
481
482
        case AOUT_VAR_CHAN_STEREO:
            break;
        case AOUT_VAR_CHAN_LEFT:
483
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_DISABLE;
484
485
            fmt->i_physical_channels = AOUT_CHAN_CENTER;
            aout_FormatPrepare (fmt);
486
487
            break;
        case AOUT_VAR_CHAN_RIGHT:
488
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_DISABLE;
489
490
            fmt->i_physical_channels = AOUT_CHAN_CENTER;
            aout_FormatPrepare (fmt);
491
492
            break;
        case AOUT_VAR_CHAN_DOLBYS:
493
            fmt->i_chan_mode = AOUT_CHANMODE_DOLBYSTEREO;
494
            break;
495
496
497
498
499
        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;
500
        default:
501
            break;
502
    }
503
504

    var_Change(aout, "stereo-mode", VLC_VAR_SETVALUE,
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
               (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;
525
526
}

527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
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);
}

Thomas Guillem's avatar
Thomas Guillem committed
551
552
553
554
555
556
static void aout_SetupMixModeChoices (audio_output_t *aout,
                                      const audio_sample_format_t *restrict fmt)
{
    if (fmt->i_channels <= 2)
        return;

557
558
    const bool has_spatialaudio = module_exists("spatialaudio");

559
    aout_AddMixModeChoice(aout, AOUT_MIX_MODE_UNSET, _("Original"), fmt);
Thomas Guillem's avatar
Thomas Guillem committed
560

561
    if (fmt->channel_type != AUDIO_CHANNEL_TYPE_AMBISONICS && has_spatialaudio)
562
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_STEREO, _("Stereo"), NULL);
Thomas Guillem's avatar
Thomas Guillem committed
563

564
565
    if (has_spatialaudio)
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL, _("Binaural"), NULL);
566

567
    /* Only propose Original and Binaural for Ambisonics content */
568
    if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS && has_spatialaudio)
569
570
        return;

Thomas Guillem's avatar
Thomas Guillem committed
571
572
    if (fmt->i_physical_channels != AOUT_CHANS_4_0)
    {
573
574
575
576
577
        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);
Thomas Guillem's avatar
Thomas Guillem committed
578
579
580
581
    }

    if (fmt->i_physical_channels != AOUT_CHANS_5_1)
    {
582
583
584
585
586
        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);
Thomas Guillem's avatar
Thomas Guillem committed
587
588
589
590
    }

    if (fmt->i_physical_channels != AOUT_CHANS_7_1)
    {
591
592
593
594
595
        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);
Thomas Guillem's avatar
Thomas Guillem committed
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    }
}

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,
620
                               audio_sample_format_t *restrict fmt)
Thomas Guillem's avatar
Thomas Guillem committed
621
622
623
624
625
626
{
    /* The user may have selected a different channels configuration. */
    switch (mode)
    {
        case AOUT_MIX_MODE_UNSET:
            break;
627
628
        case AOUT_MIX_MODE_BINAURAL:
            fmt->i_physical_channels = AOUT_CHANS_STEREO;
629
            fmt->i_chan_mode = AOUT_CHANMODE_BINAURAL;
630
            break;
Thomas Guillem's avatar
Thomas Guillem committed
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
        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});
}

652
653
/**
 * Starts an audio output stream.
654
655
 * \param output_codec codec accepted by the module, it can be different than
 * the codec from the mixer_format in case of DTSHD/DTS or EAC3/AC3 fallback
656
 * \warning The caller must NOT hold the audio output lock.
657
 */
Thomas Guillem's avatar
Thomas Guillem committed
658
int aout_OutputNew (audio_output_t *aout)
659
{
660
    aout_owner_t *owner = aout_owner (aout);
Thomas Guillem's avatar
Thomas Guillem committed
661
    audio_sample_format_t *fmt = &owner->mixer_format;
662
    audio_sample_format_t *filter_fmt = &owner->filter_format;
Thomas Guillem's avatar
Thomas Guillem committed
663
    aout_filters_cfg_t *filters_cfg = &owner->filters_cfg;
664

665
666
667
    vlc_fourcc_t formats[] = {
        fmt->i_format, 0, 0
    };
668

669
    var_Change(aout, "stereo-mode", VLC_VAR_CLEARCHOICES);
Thomas Guillem's avatar
Thomas Guillem committed
670
    var_Change(aout, "mix-mode", VLC_VAR_CLEARCHOICES);
671

672
673
674
675
    /* 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))
Thomas Guillem's avatar
Thomas Guillem committed
676
677
678
679
680
681
682
683
684
    {
        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);
        }

685
686
687
688
689
690
691
692
        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;
        }

Thomas Guillem's avatar
Thomas Guillem committed
693
        /* Try to stay in integer domain if possible for no/slow FPU. */
694
695
696
        fmt->i_format = (fmt->i_bitspersample > 16) ? VLC_CODEC_FL32
                                                    : VLC_CODEC_S16N;

Thomas Guillem's avatar
Thomas Guillem committed
697
698
699
700
701
        aout_SetupMixModeChoices(aout, fmt);

        /* Prefer the user requested mode if available, otherwise, use the
         * default one */
        if (aout_HasMixModeChoice(aout, owner->requested_mix_mode))
702
            aout_UpdateMixMode(aout, owner->requested_mix_mode, fmt);
Thomas Guillem's avatar
Thomas Guillem committed
703

704
        aout_FormatPrepare (fmt);
705
        assert (aout_FormatNbChannels(fmt) > 0);
706
    }
707
708
709
710
711
712
713
714
715
716
717
718
719
    else
    {
        switch (fmt->i_format)
        {
            case VLC_CODEC_DTS:
                if (owner->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;
720
721
722
723
724
725
726
727
            case VLC_CODEC_A52:
                if (owner->input_profile > 0)
                {
                    assert(ARRAY_SIZE(formats) >= 3);
                    formats[0] = VLC_CODEC_EAC3;
                    formats[1] = VLC_CODEC_A52;
                }
                break;
728
729
730
731
            default:
                break;
        }
    }
732

733
    int stereo_mode = aout_PrepareStereoMode(aout, fmt);
734
735
736
737
738

    if (stereo_mode != AOUT_VAR_CHAN_UNSET
     && aout_HasStereoMode(aout, stereo_mode))
        aout_UpdateStereoMode(aout, stereo_mode, fmt, filters_cfg);

739
740
    aout->current_sink_info.headphones = false;

741
    vlc_mutex_lock(&owner->lock);
742
743
744
745
746
747
    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];
        ret = aout->start(aout, fmt);
    }
748
    vlc_mutex_unlock(&owner->lock);
749
    if (ret)
750
    {
751
752
753
754
755
        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");
756
757
        return -1;
    }
758
    assert(aout->flush && aout->play && aout->time_get && aout->pause);
759

760
761
762
763
764
765
766
    /* 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
     && aout_HasMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL))
    {
        assert(fmt->i_physical_channels == AOUT_CHANS_STEREO);
767
        assert(stereo_mode == AOUT_VAR_CHAN_UNSET);
768
        aout_UpdateMixMode(aout, AOUT_MIX_MODE_BINAURAL, fmt);
769
770
    }

771
    aout_FormatPrepare (fmt);
Thomas Guillem's avatar
Thomas Guillem committed
772
    assert (fmt->i_bytes_per_frame > 0 && fmt->i_frame_length > 0);
773
    aout_FormatPrint (aout, "output", fmt);
774
775
776
    return 0;
}

777
/**
778
 * Stops the audio output stream (undoes aout_OutputNew()).
779
 * \note This can only be called after a successful aout_OutputNew().
780
 * \warning The caller must NOT hold the audio output lock.
781
782
 */
void aout_OutputDelete (audio_output_t *aout)
783
{
784
785
    aout_owner_t *owner = aout_owner(aout);
    vlc_mutex_lock(&owner->lock);
786
    aout->stop (aout);
787
    vlc_mutex_unlock(&owner->lock);
788
789
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
790
791
792
793
794
795
796
797
798
799
800
801
802
/**
 * 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.
803
 * \return 0 on success, -1 on failure.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
804
805
806
 */
int aout_VolumeSet (audio_output_t *aout, float vol)
{
807
    aout_owner_t *owner = aout_owner(aout);
808
    int ret;
809

810
    vlc_mutex_lock(&owner->lock);
811
    ret = aout->volume_set ? aout->volume_set(aout, vol) : -1;
812
    vlc_mutex_unlock(&owner->lock);
813
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
814
815
}

816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
/**
 * Raises the volume.
 * \param value how much to increase (> 0) or decrease (< 0) the volume
 * \param volp if non-NULL, will contain contain the resulting volume
 */
int aout_VolumeUpdate (audio_output_t *aout, int value, float *volp)
{
    int ret = -1;
    float stepSize = var_InheritFloat (aout, "volume-step") / (float)AOUT_VOLUME_DEFAULT;
    float delta = value * stepSize;
    float vol = aout_VolumeGet (aout);

    if (vol >= 0.f)
    {
        vol += delta;
        if (vol < 0.f)
            vol = 0.f;
        if (vol > 2.f)
            vol = 2.f;
        vol = (roundf (vol / stepSize)) * stepSize;
        if (volp != NULL)
            *volp = vol;
        ret = aout_VolumeSet (aout, vol);
    }
    return ret;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
843
844
845
846
847
848
849
850
851
852
853
/**
 * 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.
854
 * \return 0 on success, -1 on failure.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
855
856
857
 */
int aout_MuteSet (audio_output_t *aout, bool mute)
{
858
    aout_owner_t *owner = aout_owner(aout);
859
    int ret;
860

861
    vlc_mutex_lock(&owner->lock);
862
    ret = aout->mute_set ? aout->mute_set(aout, mute) : -1;
863
    vlc_mutex_unlock(&owner->lock);
864
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
}

/**
 * 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
880
 * \return zero on success, non-zero on error.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
881
882
883
 */
int aout_DeviceSet (audio_output_t *aout, const char *id)
{
884
    aout_owner_t *owner = aout_owner(aout);
885
    int ret;
886

887
    vlc_mutex_lock(&owner->lock);
888
    ret = aout->device_select ? aout->device_select(aout, id) : -1;
889
    vlc_mutex_unlock(&owner->lock);
890
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
891
892
893
894
895
896
897
898
}

/**
 * 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.
 *
899
 * \param ids pointer to a table of device identifiers [OUT]
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
900
901
902
903
904
905
 * \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)
{
906
907
    aout_owner_t *owner = aout_owner (aout);
    char **tabid, **tabname;
908
    unsigned i = 0;
909
910

    vlc_mutex_lock (&owner->dev.lock);
Thomas Guillem's avatar
Thomas Guillem committed
911
912
    tabid = vlc_alloc (owner->dev.count, sizeof (*tabid));
    tabname = vlc_alloc (owner->dev.count, sizeof (*tabname));
913

914
915
916
917
918
919
    if (unlikely(tabid == NULL || tabname == NULL))
        goto error;

    *ids = tabid;
    *names = tabname;

920
921
    aout_dev_t *dev;
    vlc_list_foreach(dev, &owner->dev.list, node)
922
923
924
925
    {
        tabid[i] = strdup(dev->id);
        if (unlikely(tabid[i] == NULL))
            goto error;
926

927
928
929
930
931
        tabname[i] = strdup(dev->name);
        if (unlikely(tabname[i] == NULL))
        {
            free(tabid[i]);
            goto error;
932
        }
933
934

        i++;
935
936
    }
    vlc_mutex_unlock (&owner->dev.lock);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
937

938
939
940
941
942
    return i;

error:
    vlc_mutex_unlock(&owner->dev.lock);
    while (i > 0)
943
    {
944
945
946
        i--;
        free(tabname[i]);
        free(tabid[i]);
947
    }
948
949
950
    free(tabname);
    free(tabid);
    return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
951
}
952
953
954
955
956
957
958
959

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;
960
    atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
961
962
    vlc_mutex_unlock(&owner->vp.lock);
}
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979

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);
}