Coverage Report

Created: 2026-05-21 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/src/audio_output/output.c
Line
Count
Source
1
/*****************************************************************************
2
 * output.c : internal management of output streams for the audio output
3
 *****************************************************************************
4
 * Copyright (C) 2002-2004 VLC authors and VideoLAN
5
 *
6
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
7
 *
8
 * This program is free software; you can redistribute it and/or modify it
9
 * under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program; if not, write to the Free Software Foundation,
20
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21
 *****************************************************************************/
22
23
#ifdef HAVE_CONFIG_H
24
# include "config.h"
25
#endif
26
27
#include <stdlib.h>
28
#include <assert.h>
29
30
#include <vlc_common.h>
31
#include <vlc_configuration.h>
32
#include <vlc_aout.h>
33
#include <vlc_modules.h>
34
#include <vlc_atomic.h>
35
36
#include "../libvlc.h"
37
#include "aout_internal.h"
38
39
typedef struct aout_dev
40
{
41
    struct vlc_list node;
42
    char *name;
43
    char id[1];
44
} aout_dev_t;
45
46
47
/* Local functions */
48
49
static inline float clampf(const float value, const float min, const float max)
50
0
{
51
0
    if (value < min)
52
0
        return min;
53
0
    else if (value > max)
54
0
        return max;
55
0
    else
56
0
        return value;
57
0
}
58
59
static int var_Copy (vlc_object_t *src, const char *name, vlc_value_t prev,
60
                     vlc_value_t value, void *data)
61
0
{
62
0
    vlc_object_t *dst = data;
63
64
0
    (void) src; (void) prev;
65
0
    return var_Set (dst, name, value);
66
0
}
67
68
static int var_CopyDevice (vlc_object_t *src, const char *name,
69
                           vlc_value_t prev, vlc_value_t value, void *data)
70
0
{
71
0
    vlc_object_t *dst = data;
72
73
0
    (void) src; (void) name; (void) prev;
74
0
    return var_Set (dst, "audio-device", value);
75
0
}
76
77
static void aout_TimingNotify(audio_output_t *aout, vlc_tick_t system_ts,
78
                              vlc_tick_t audio_ts)
79
0
{
80
0
    aout_owner_t *owner = aout_owner (aout);
81
0
    assert(owner->main_stream);
82
0
    vlc_aout_stream_NotifyTiming(owner->main_stream, system_ts, audio_ts);
83
0
}
84
85
static void aout_DrainedNotify(audio_output_t *aout)
86
0
{
87
0
    aout_owner_t *owner = aout_owner (aout);
88
0
    assert(owner->main_stream);
89
0
    vlc_aout_stream_NotifyDrained(owner->main_stream);
90
0
}
91
92
/**
93
 * Supply or update the current custom ("hardware") volume.
94
 *
95
 * @param aout the audio output notifying the new volume
96
 * @param volume current custom volume
97
 *
98
 * @warning The caller (i.e. the audio output plug-in) is responsible for
99
 * interlocking and synchronizing call to this function and to the
100
 * audio_output_t.volume_set callback. This ensures that VLC gets correct
101
 * volume information (possibly with a latency).
102
 */
103
static void aout_VolumeNotify (audio_output_t *aout, float volume)
104
0
{
105
0
    var_SetFloat (aout, "volume", volume);
106
0
}
107
108
static void aout_MuteNotify (audio_output_t *aout, bool mute)
109
0
{
110
0
    var_SetBool (aout, "mute", mute);
111
0
}
112
113
static void aout_PolicyNotify (audio_output_t *aout, bool cork)
114
0
{
115
0
    (cork ? var_IncInteger : var_DecInteger)(vlc_object_parent(aout), "corks");
116
0
}
117
118
static void aout_DeviceNotify (audio_output_t *aout, const char *id)
119
0
{
120
0
    var_SetString (aout, "device", (id != NULL) ? id : "");
121
0
}
122
123
static void aout_HotplugNotify (audio_output_t *aout,
124
                                const char *id, const char *name)
125
0
{
126
0
    aout_owner_t *owner = aout_owner (aout);
127
0
    aout_dev_t *dev = NULL, *p;
128
129
0
    vlc_mutex_lock (&owner->dev.lock);
130
0
    vlc_list_foreach(p, &owner->dev.list, node)
131
0
    {
132
0
        if (!strcmp (id, p->id))
133
0
        {
134
0
            dev = p;
135
0
            break;
136
0
        }
137
0
    }
138
139
0
    if (name != NULL)
140
0
    {
141
0
        if (dev == NULL) /* Added device */
142
0
        {
143
0
            dev = malloc (sizeof (*dev) + strlen (id));
144
0
            if (unlikely(dev == NULL))
145
0
                goto out;
146
0
            strcpy (dev->id, id);
147
0
            vlc_list_append(&dev->node, &owner->dev.list);
148
0
            owner->dev.count++;
149
0
        }
150
0
        else /* Modified device */
151
0
            free (dev->name);
152
0
        dev->name = strdup (name);
153
0
    }
154
0
    else
155
0
    {
156
0
        if (dev != NULL) /* Removed device */
157
0
        {
158
0
            owner->dev.count--;
159
0
            vlc_list_remove(&dev->node);
160
0
            free (dev->name);
161
0
            free (dev);
162
0
        }
163
0
    }
164
0
out:
165
0
    vlc_mutex_unlock (&owner->dev.lock);
166
0
}
167
168
static void aout_RestartNotify (audio_output_t *aout, bool restart_dec)
169
0
{
170
0
    aout_owner_t *owner = aout_owner (aout);
171
0
    if (owner->main_stream)
172
0
    {
173
0
        unsigned mode = restart_dec ? AOUT_RESTART_OUTPUT_DEC : AOUT_RESTART_OUTPUT;
174
0
        vlc_aout_stream_RequestRestart(owner->main_stream, mode);
175
0
    }
176
0
}
177
178
void aout_InputRequestRestart(audio_output_t *aout)
179
0
{
180
0
    aout_owner_t *owner = aout_owner (aout);
181
0
    if (owner->main_stream)
182
0
        vlc_aout_stream_RequestRestart(owner->main_stream, AOUT_RESTART_FILTERS);
183
0
}
184
185
static int aout_GainNotify (audio_output_t *aout, float gain)
186
0
{
187
0
    aout_owner_t *owner = aout_owner (aout);
188
189
0
    vlc_mutex_assert(&owner->lock);
190
    /* XXX: ideally, return -1 if format cannot be amplified */
191
0
    if (owner->main_stream != NULL)
192
0
        vlc_aout_stream_NotifyGain(owner->main_stream, gain);
193
0
    return 0;
194
0
}
195
196
static const struct vlc_audio_output_events aout_events = {
197
    aout_TimingNotify,
198
    aout_DrainedNotify,
199
    aout_VolumeNotify,
200
    aout_MuteNotify,
201
    aout_PolicyNotify,
202
    aout_DeviceNotify,
203
    aout_HotplugNotify,
204
    aout_RestartNotify,
205
    aout_GainNotify,
206
};
207
208
static int FilterCallback (vlc_object_t *obj, const char *var,
209
                           vlc_value_t prev, vlc_value_t cur, void *data)
210
0
{
211
0
    if (strcmp(prev.psz_string, cur.psz_string))
212
0
        aout_InputRequestRestart ((audio_output_t *)obj);
213
0
    (void) var; (void) data;
214
0
    return VLC_SUCCESS;
215
0
}
216
217
static int StereoModeCallback (vlc_object_t *obj, const char *varname,
218
                               vlc_value_t oldval, vlc_value_t newval, void *data)
219
0
{
220
0
    audio_output_t *aout = (audio_output_t *)obj;
221
0
    (void)varname; (void)oldval; (void)newval; (void)data;
222
223
0
    aout_owner_t *owner = aout_owner (aout);
224
0
    vlc_mutex_lock (&owner->lock);
225
0
    owner->requested_stereo_mode = newval.i_int;
226
0
    vlc_mutex_unlock (&owner->lock);
227
228
0
    if (owner->main_stream)
229
0
        vlc_aout_stream_RequestRestart(owner->main_stream, AOUT_RESTART_OUTPUT);
230
0
    return 0;
231
0
}
232
233
static int MixModeCallback (vlc_object_t *obj, const char *varname,
234
                               vlc_value_t oldval, vlc_value_t newval, void *data)
235
0
{
236
0
    audio_output_t *aout = (audio_output_t *)obj;
237
0
    (void)varname; (void)oldval; (void)newval; (void)data;
238
239
0
    aout_owner_t *owner = aout_owner (aout);
240
0
    vlc_mutex_lock (&owner->lock);
241
0
    owner->requested_mix_mode = newval.i_int;
242
0
    vlc_mutex_unlock (&owner->lock);
243
244
0
    if (owner->main_stream)
245
0
        vlc_aout_stream_RequestRestart(owner->main_stream, AOUT_RESTART_OUTPUT);
246
0
    return 0;
247
0
}
248
249
static void aout_ChangeViewpoint(audio_output_t *, const vlc_viewpoint_t *);
250
251
static int ViewpointCallback (vlc_object_t *obj, const char *var,
252
                              vlc_value_t prev, vlc_value_t cur, void *data)
253
0
{
254
0
    if( cur.p_address != NULL )
255
0
        aout_ChangeViewpoint((audio_output_t *)obj, cur.p_address );
256
0
    (void) var; (void) data; (void) prev;
257
0
    return VLC_SUCCESS;
258
0
}
259
260
#undef aout_New
261
/**
262
 * Creates an audio output object and initializes an output module.
263
 */
264
audio_output_t *aout_New (vlc_object_t *parent)
265
0
{
266
0
    vlc_value_t val;
267
268
0
    audio_output_t *aout = vlc_custom_create (parent, sizeof (aout_instance_t),
269
0
                                              "audio output");
270
0
    if (unlikely(aout == NULL))
271
0
        return NULL;
272
273
0
    aout_owner_t *owner = aout_owner (aout);
274
275
0
    vlc_mutex_init (&owner->lock);
276
0
    vlc_mutex_init (&owner->dev.lock);
277
0
    vlc_mutex_init (&owner->vp.lock);
278
0
    vlc_viewpoint_init (&owner->vp.value);
279
0
    vlc_list_init(&owner->dev.list);
280
0
    atomic_init (&owner->vp.update, false);
281
0
    vlc_atomic_rc_init(&owner->rc);
282
0
    vlc_audio_meter_Init(&owner->meter, aout);
283
284
0
    owner->main_stream = NULL;
285
286
    /* Audio output module callbacks */
287
0
    var_Create (aout, "volume", VLC_VAR_FLOAT);
288
0
    var_SetFloat(aout, "volume", -1.f);
289
0
    var_AddCallback (aout, "volume", var_Copy, parent);
290
0
    var_Create (aout, "mute", VLC_VAR_BOOL);
291
0
    var_AddCallback (aout, "mute", var_Copy, parent);
292
0
    var_Create (aout, "device", VLC_VAR_STRING);
293
0
    var_AddCallback (aout, "device", var_CopyDevice, parent);
294
295
0
    aout->events = &aout_events;
296
297
    /* Audio output module initialization */
298
0
    aout->start = NULL;
299
0
    aout->stop = NULL;
300
0
    aout->volume_set = NULL;
301
0
    aout->mute_set = NULL;
302
0
    aout->device_select = NULL;
303
0
    owner->module = module_need_var(aout, "audio output", "aout");
304
0
    if (owner->module == NULL)
305
0
    {
306
0
        msg_Err (aout, "no suitable audio output module");
307
0
        vlc_object_delete(aout);
308
0
        return NULL;
309
0
    }
310
0
    assert(aout->start && aout->stop);
311
312
    /*
313
     * Persistent audio output variables
314
     */
315
0
    module_config_t *cfg;
316
0
    char *str;
317
318
    /* Visualizations */
319
0
    var_Create (aout, "visual", VLC_VAR_STRING);
320
0
    var_Change(aout, "visual", VLC_VAR_SETTEXT, _("Visualizations"));
321
0
    val.psz_string = (char *)"";
322
0
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Disable"));
323
0
    val.psz_string = (char *)"spectrometer";
324
0
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrometer"));
325
0
    val.psz_string = (char *)"scope";
326
0
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Scope"));
327
0
    val.psz_string = (char *)"spectrum";
328
0
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("Spectrum"));
329
0
    val.psz_string = (char *)"vuMeter";
330
0
    var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, _("VU meter"));
331
    /* Look for goom plugin */
332
0
    if (module_exists ("goom"))
333
0
    {
334
0
        val.psz_string = (char *)"goom";
335
0
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Goom");
336
0
    }
337
    /* Look for libprojectM plugin */
338
0
    if (module_exists ("projectm"))
339
0
    {
340
0
        val.psz_string = (char *)"projectm";
341
0
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "projectM");
342
0
    }
343
    /* Look for VSXu plugin */
344
0
    if (module_exists ("vsxu"))
345
0
    {
346
0
        val.psz_string = (char *)"vsxu";
347
0
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "Vovoid VSXU");
348
0
    }
349
    /* Look for glspectrum plugin */
350
0
    if (module_exists ("glspectrum"))
351
0
    {
352
0
        val.psz_string = (char *)"glspectrum";
353
0
        var_Change(aout, "visual", VLC_VAR_ADDCHOICE, val, "3D spectrum");
354
0
    }
355
0
    str = var_GetNonEmptyString (aout, "effect-list");
356
0
    if (str != NULL)
357
0
    {
358
0
        var_SetString (aout, "visual", str);
359
0
        free (str);
360
0
    }
361
362
0
    var_Create (aout, "audio-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
363
0
    var_AddCallback (aout, "audio-filter", FilterCallback, NULL);
364
0
    var_Change(aout, "audio-filter", VLC_VAR_SETTEXT, _("Audio filters"));
365
366
0
    var_Create (aout, "viewpoint", VLC_VAR_ADDRESS );
367
0
    var_AddCallback (aout, "viewpoint", ViewpointCallback, NULL);
368
369
0
    var_Create (aout, "audio-visual", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
370
0
    var_Change(aout, "audio-visual", VLC_VAR_SETTEXT,
371
0
               _("Audio visualizations"));
372
373
    /* Replay gain */
374
0
    var_Create (aout, "audio-replay-gain-mode",
375
0
                VLC_VAR_STRING | VLC_VAR_DOINHERIT );
376
0
    var_Change(aout, "audio-replay-gain-mode", VLC_VAR_SETTEXT,
377
0
               _("Replay gain"));
378
0
    cfg = config_FindConfig("audio-replay-gain-mode");
379
0
    if (likely(cfg != NULL))
380
0
        for (unsigned i = 0; i < cfg->list_count; i++)
381
0
        {
382
0
            val.psz_string = (char *)cfg->list.psz[i];
383
0
            var_Change(aout, "audio-replay-gain-mode", VLC_VAR_ADDCHOICE,
384
0
                       val, vlc_gettext(cfg->list_text[i]));
385
0
        }
386
387
    /* Stereo mode */
388
0
    var_Create (aout, "stereo-mode", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
389
0
    owner->requested_stereo_mode = var_GetInteger (aout, "stereo-mode");
390
391
0
    var_AddCallback (aout, "stereo-mode", StereoModeCallback, NULL);
392
0
    var_Change(aout, "stereo-mode", VLC_VAR_SETTEXT, _("Stereo audio mode"));
393
394
    /* Mix mode */
395
0
    var_Create (aout, "mix-mode", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
396
0
    owner->requested_mix_mode = var_GetInteger (aout, "mix-mode");
397
0
    var_AddCallback (aout, "mix-mode", MixModeCallback, NULL);
398
0
    var_Change(aout, "mix-mode", VLC_VAR_SETTEXT, _("Audio mix mode"));
399
400
    /* Equalizer */
401
0
    int doinherit = module_exists("equalizer") ? VLC_VAR_DOINHERIT : 0;
402
0
    var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | doinherit);
403
0
    var_Create (aout, "equalizer-bands", VLC_VAR_STRING | doinherit);
404
0
    var_Create (aout, "equalizer-preset", VLC_VAR_STRING | doinherit);
405
406
0
    owner->bitexact = var_InheritBool (aout, "audio-bitexact");
407
408
0
    return aout;
409
0
}
410
411
audio_output_t *aout_Hold(audio_output_t *aout)
412
0
{
413
0
    aout_owner_t *owner = aout_owner(aout);
414
415
0
    vlc_atomic_rc_inc(&owner->rc);
416
0
    return aout;
417
0
}
418
419
/**
420
 * Deinitializes an audio output module and destroys an audio output object.
421
 */
422
static void aout_Destroy (audio_output_t *aout)
423
0
{
424
0
    aout_owner_t *owner = aout_owner (aout);
425
426
0
    vlc_mutex_lock(&owner->lock);
427
0
    module_unneed (aout, owner->module);
428
    /* Protect against late call from intf.c */
429
0
    aout->volume_set = NULL;
430
0
    aout->mute_set = NULL;
431
0
    aout->device_select = NULL;
432
0
    vlc_audio_meter_Destroy(&owner->meter);
433
0
    vlc_mutex_unlock(&owner->lock);
434
435
0
    var_DelCallback (aout, "viewpoint", ViewpointCallback, NULL);
436
0
    var_DelCallback (aout, "audio-filter", FilterCallback, NULL);
437
0
    var_DelCallback(aout, "device", var_CopyDevice, vlc_object_parent(aout));
438
0
    var_DelCallback(aout, "mute", var_Copy, vlc_object_parent(aout));
439
0
    var_SetFloat (aout, "volume", -1.f);
440
0
    var_DelCallback(aout, "volume", var_Copy, vlc_object_parent(aout));
441
0
    var_DelCallback (aout, "stereo-mode", StereoModeCallback, NULL);
442
0
    var_DelCallback (aout, "mix-mode", MixModeCallback, NULL);
443
444
0
    aout_dev_t *dev;
445
0
    vlc_list_foreach(dev, &owner->dev.list, node)
446
0
    {
447
0
        vlc_list_remove(&dev->node);
448
0
        free (dev->name);
449
0
        free (dev);
450
0
    }
451
452
0
    vlc_object_delete(VLC_OBJECT(aout));
453
0
}
454
455
void aout_Release(audio_output_t *aout)
456
0
{
457
0
    aout_owner_t *owner = aout_owner(aout);
458
459
0
    if (!vlc_atomic_rc_dec(&owner->rc))
460
0
        return;
461
462
0
    aout_Destroy(aout);
463
0
}
464
465
static int aout_PrepareStereoMode(audio_output_t *aout,
466
                                  const audio_sample_format_t *restrict fmt)
467
0
{
468
0
    aout_owner_t *owner = aout_owner (aout);
469
470
    /* Fill Stereo mode choices */
471
0
    vlc_value_t val;
472
0
    const char *txt;
473
0
    val.i_int = 0;
474
475
0
    if (!AOUT_FMT_LINEAR(fmt) || fmt->i_channels != 2)
476
0
        return AOUT_VAR_CHAN_UNSET;
477
478
0
    int i_default_mode = owner->requested_stereo_mode;
479
480
0
    val.i_int = AOUT_VAR_CHAN_MONO;
481
0
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Mono"));
482
483
0
    if (fmt->i_chan_mode & AOUT_CHANMODE_DOLBYSTEREO)
484
0
    {
485
0
        val.i_int = AOUT_VAR_CHAN_DOLBYS;
486
0
        txt = _("Dolby Surround");
487
0
    }
488
0
    else
489
0
    {
490
0
        val.i_int = AOUT_VAR_CHAN_STEREO;
491
0
        txt = _("Stereo");
492
0
    }
493
0
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, txt);
494
495
0
    if (fmt->i_chan_mode & AOUT_CHANMODE_DUALMONO)
496
0
        i_default_mode = AOUT_VAR_CHAN_LEFT;
497
0
    else
498
0
        i_default_mode = val.i_int; /* Stereo or Dolby Surround */
499
500
0
    val.i_int = AOUT_VAR_CHAN_LEFT;
501
0
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Left"));
502
0
    val.i_int = AOUT_VAR_CHAN_RIGHT;
503
0
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Right"));
504
505
0
    val.i_int = AOUT_VAR_CHAN_RSTEREO;
506
0
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val,
507
0
               _("Reverse stereo"));
508
509
0
    return i_default_mode;
510
0
}
511
512
static void aout_UpdateStereoMode(audio_output_t *aout, int mode,
513
                                  audio_sample_format_t *restrict fmt,
514
                                  aout_filters_cfg_t *filters_cfg)
515
0
{
516
    /* The user may have selected a different channels configuration. */
517
0
    switch (mode)
518
0
    {
519
0
        case AOUT_VAR_CHAN_RSTEREO:
520
0
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_RIGHT;
521
0
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_LEFT;
522
0
            break;
523
0
        case AOUT_VAR_CHAN_STEREO:
524
0
            break;
525
0
        case AOUT_VAR_CHAN_LEFT:
526
0
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_DISABLE;
527
0
            fmt->i_physical_channels = AOUT_CHAN_CENTER;
528
0
            aout_FormatPrepare (fmt);
529
0
            break;
530
0
        case AOUT_VAR_CHAN_RIGHT:
531
0
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_DISABLE;
532
0
            fmt->i_physical_channels = AOUT_CHAN_CENTER;
533
0
            aout_FormatPrepare (fmt);
534
0
            break;
535
0
        case AOUT_VAR_CHAN_DOLBYS:
536
0
            fmt->i_chan_mode = AOUT_CHANMODE_DOLBYSTEREO;
537
0
            break;
538
0
        case AOUT_VAR_CHAN_MONO:
539
            /* Remix all channels into one */
540
0
            for (size_t i = 0; i < AOUT_CHANIDX_MAX; ++ i)
541
0
                filters_cfg->remap[i] = AOUT_CHANIDX_LEFT;
542
0
            break;
543
0
        default:
544
0
            break;
545
0
    }
546
547
0
    var_Change(aout, "stereo-mode", VLC_VAR_SETVALUE,
548
0
               (vlc_value_t) { .i_int = mode});
549
0
}
550
551
static bool aout_HasStereoMode(audio_output_t *aout, int mode)
552
0
{
553
0
    bool mode_available = false;
554
0
    vlc_value_t *vals;
555
0
    size_t count;
556
557
0
    if (!var_Change(aout, "stereo-mode", VLC_VAR_GETCHOICES,
558
0
                    &count, &vals, (char ***)NULL))
559
0
    {
560
0
        for (size_t i = 0; !mode_available && i < count; ++i)
561
0
        {
562
0
            if (vals[i].i_int == mode)
563
0
                mode_available = true;
564
0
        }
565
0
        free(vals);
566
0
    }
567
0
    return mode_available;
568
0
}
569
570
static void aout_AddMixModeChoice(audio_output_t *aout, int mode,
571
                                  const char *suffix,
572
                                  const audio_sample_format_t *restrict fmt)
573
0
{
574
0
    assert(suffix);
575
0
    const char *text;
576
0
    char *buffer = NULL;
577
578
0
    if (fmt == NULL)
579
0
        text = suffix;
580
0
    else
581
0
    {
582
0
        const char *channels = aout_FormatPrintChannels(fmt);
583
0
        if (asprintf(&buffer, "%s: %s", suffix, channels) < 0)
584
0
            return;
585
0
        text = buffer;
586
0
    }
587
588
0
    vlc_value_t val = { .i_int = mode };
589
0
    var_Change(aout, "mix-mode", VLC_VAR_ADDCHOICE, val, text);
590
591
0
    free(buffer);
592
0
}
593
594
static void aout_SetupMixModeChoices (audio_output_t *aout,
595
                                      const audio_sample_format_t *restrict fmt)
596
0
{
597
0
    if (fmt->i_channels <= 2)
598
0
        return;
599
600
0
    const bool has_spatialaudio = module_exists("spatialaudio");
601
602
    /* Don't propose the mix option if we don't have the spatialaudio module
603
     * and if the content is ambisonics */
604
0
    if (fmt->channel_type != AUDIO_CHANNEL_TYPE_AMBISONICS || has_spatialaudio)
605
0
    {
606
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_UNSET, _("Original"), fmt);
607
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_STEREO, _("Stereo"), NULL);
608
0
    }
609
610
0
    if (has_spatialaudio)
611
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL, _("Binaural"), NULL);
612
613
    /* Only propose Original and Binaural for Ambisonics content */
614
0
    if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS && has_spatialaudio)
615
0
        return;
616
617
0
    if (fmt->i_physical_channels != AOUT_CHANS_4_0)
618
0
    {
619
0
        static const audio_sample_format_t fmt_4_0 = {
620
0
            .i_physical_channels = AOUT_CHANS_4_0,
621
0
            .i_channels = 4,
622
0
        };
623
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_4_0, _("4.0"), &fmt_4_0);
624
0
    }
625
626
0
    if (fmt->i_physical_channels != AOUT_CHANS_5_1)
627
0
    {
628
0
        static const audio_sample_format_t fmt_5_1 = {
629
0
            .i_physical_channels = AOUT_CHANS_5_1,
630
0
            .i_channels = 6,
631
0
        };
632
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_5_1, _("5.1"), &fmt_5_1);
633
0
    }
634
635
0
    if (fmt->i_physical_channels != AOUT_CHANS_7_1)
636
0
    {
637
0
        static const audio_sample_format_t fmt_7_1 = {
638
0
            .i_physical_channels = AOUT_CHANS_7_1,
639
0
            .i_channels = 8,
640
0
        };
641
0
        aout_AddMixModeChoice(aout, AOUT_MIX_MODE_7_1, _("7.1"), &fmt_7_1);
642
0
    }
643
0
}
644
645
static bool aout_HasMixModeChoice(audio_output_t *aout, int mode)
646
0
{
647
0
    bool mode_available = false;
648
0
    vlc_value_t *vals;
649
0
    size_t count;
650
651
0
    if (!var_Change(aout, "mix-mode", VLC_VAR_GETCHOICES,
652
0
                    &count, &vals, (char ***)NULL))
653
0
    {
654
0
        for (size_t i = 0; !mode_available && i < count; ++i)
655
0
        {
656
0
            if (vals[i].i_int == mode)
657
0
                mode_available = true;
658
0
        }
659
0
        free(vals);
660
0
    }
661
0
    return mode_available;
662
0
}
663
664
665
static void aout_UpdateMixMode(audio_output_t *aout, int mode,
666
                               audio_sample_format_t *restrict fmt)
667
0
{
668
    /* The user may have selected a different channels configuration. */
669
0
    switch (mode)
670
0
    {
671
0
        case AOUT_MIX_MODE_UNSET:
672
0
            break;
673
0
        case AOUT_MIX_MODE_BINAURAL:
674
0
            fmt->i_physical_channels = AOUT_CHANS_STEREO;
675
0
            fmt->i_chan_mode = AOUT_CHANMODE_BINAURAL;
676
0
            break;
677
0
        case AOUT_MIX_MODE_STEREO:
678
0
            fmt->i_physical_channels = AOUT_CHANS_STEREO;
679
0
            break;
680
0
        case AOUT_MIX_MODE_4_0:
681
0
            fmt->i_physical_channels = AOUT_CHANS_4_0;
682
0
            break;
683
0
        case AOUT_MIX_MODE_5_1:
684
0
            fmt->i_physical_channels = AOUT_CHANS_5_1;
685
0
            break;
686
0
        case AOUT_MIX_MODE_7_1:
687
0
            fmt->i_physical_channels = AOUT_CHANS_7_1;
688
0
            break;
689
0
        default:
690
0
            break;
691
0
    }
692
693
0
    assert(mode == AOUT_VAR_CHAN_UNSET || aout_HasMixModeChoice(aout, mode));
694
695
0
    var_Change(aout, "mix-mode", VLC_VAR_SETVALUE, (vlc_value_t) { .i_int = mode});
696
0
}
697
698
int aout_OutputNew(audio_output_t *aout, vlc_aout_stream *stream,
699
                   audio_sample_format_t *fmt, int input_profile,
700
                   audio_sample_format_t *filter_fmt,
701
                   aout_filters_cfg_t *filters_cfg)
702
0
{
703
0
    aout_owner_t *owner = aout_owner (aout);
704
705
0
    vlc_fourcc_t formats[] = {
706
0
        fmt->i_format, 0, 0
707
0
    };
708
709
0
    var_Change(aout, "stereo-mode", VLC_VAR_CLEARCHOICES);
710
0
    var_Change(aout, "mix-mode", VLC_VAR_CLEARCHOICES);
711
712
    /* Ideally, the audio filters would be created before the audio output,
713
     * and the ideal audio format would be the output of the filters chain.
714
     * But that scheme would not really play well with digital pass-through. */
715
0
    if (AOUT_FMT_LINEAR(fmt))
716
0
    {
717
0
        if (fmt->channel_type == AUDIO_CHANNEL_TYPE_BITMAP
718
0
         && aout_FormatNbChannels(fmt) == 0)
719
0
        {
720
            /* The output channel map is unknown, use the WAVE one. */
721
0
            assert(fmt->i_channels > 0);
722
0
            aout_SetWavePhysicalChannels(fmt);
723
0
        }
724
725
0
        if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
726
0
        {
727
            /* Set the maximum of channels to render ambisonics contents. The
728
             * aout module will still be free to select less channels in order
729
             * to respect the sink setup. */
730
0
            fmt->i_physical_channels = AOUT_CHANS_7_1;
731
0
        }
732
733
        /* Try to stay in integer domain if possible for no/slow FPU. */
734
0
        fmt->i_format = (fmt->i_bitspersample > 16) ? VLC_CODEC_FL32
735
0
                                                    : VLC_CODEC_S16N;
736
737
0
        aout_SetupMixModeChoices(aout, fmt);
738
739
        /* Prefer the user requested mode if available, otherwise, use the
740
         * default one */
741
0
        if (aout_HasMixModeChoice(aout, owner->requested_mix_mode))
742
0
            aout_UpdateMixMode(aout, owner->requested_mix_mode, fmt);
743
744
0
        aout_FormatPrepare (fmt);
745
0
        assert (aout_FormatNbChannels(fmt) > 0);
746
0
    }
747
0
    else
748
0
    {
749
0
        switch (fmt->i_format)
750
0
        {
751
0
            static_assert(ARRAY_SIZE(formats) >= 3, "fallback array too small");
752
0
            case VLC_CODEC_DTS:
753
0
                if (input_profile > 0)
754
0
                {
755
                    /* DTSHD can be played as DTSHD or as DTS */
756
0
                    formats[0] = VLC_CODEC_DTSHD;
757
0
                    formats[1] = VLC_CODEC_DTS;
758
0
                }
759
0
                break;
760
0
            case VLC_CODEC_A52:
761
0
                if (input_profile > 0)
762
0
                {
763
0
                    formats[0] = VLC_CODEC_EAC3;
764
0
                    formats[1] = VLC_CODEC_A52;
765
0
                }
766
0
                break;
767
0
            default:
768
0
                break;
769
0
        }
770
0
    }
771
772
0
    int stereo_mode = aout_PrepareStereoMode(aout, fmt);
773
774
0
    if (stereo_mode != AOUT_VAR_CHAN_UNSET
775
0
     && aout_HasStereoMode(aout, stereo_mode))
776
0
        aout_UpdateStereoMode(aout, stereo_mode, fmt, filters_cfg);
777
778
0
    aout->current_sink_info.headphones = false;
779
780
0
    vlc_mutex_lock(&owner->lock);
781
    /* XXX: Remove when aout/stream support is complete (in all modules) */
782
0
    assert(owner->main_stream == NULL);
783
784
0
    int ret = VLC_EGENERIC;
785
0
    for (size_t i = 0; formats[i] != 0 && ret != VLC_SUCCESS; ++i)
786
0
    {
787
0
        filter_fmt->i_format = fmt->i_format = formats[i];
788
0
        owner->main_stream = stream;
789
0
        ret = aout->start(aout, fmt);
790
0
        if (ret != 0)
791
0
            owner->main_stream = NULL;
792
0
    }
793
0
    vlc_mutex_unlock(&owner->lock);
794
0
    if (ret)
795
0
    {
796
0
        if (AOUT_FMT_LINEAR(fmt))
797
0
            msg_Err (aout, "failed to start audio output");
798
0
        else
799
0
            msg_Warn (aout, "failed to start passthrough audio output, "
800
0
                      "failing back to linear format");
801
0
        return -1;
802
0
    }
803
0
    assert(aout->flush != NULL && aout->play != NULL);
804
805
    /* Autoselect the headphones mode if available and if the user didn't
806
     * request any mode */
807
0
    if (aout->current_sink_info.headphones
808
0
     && owner->requested_mix_mode == AOUT_VAR_CHAN_UNSET
809
0
     && fmt->i_physical_channels == AOUT_CHANS_STEREO
810
0
     && aout_HasMixModeChoice(aout, AOUT_MIX_MODE_BINAURAL))
811
0
    {
812
0
        assert(stereo_mode == AOUT_VAR_CHAN_UNSET);
813
0
        aout_UpdateMixMode(aout, AOUT_MIX_MODE_BINAURAL, fmt);
814
0
    }
815
816
0
    aout_FormatPrepare (fmt);
817
0
    assert (fmt->i_bytes_per_frame > 0 && fmt->i_frame_length > 0);
818
0
    aout_FormatPrint (aout, "output", fmt);
819
820
0
    return 0;
821
0
}
822
823
void aout_OutputDelete (audio_output_t *aout)
824
0
{
825
0
    aout_owner_t *owner = aout_owner(aout);
826
0
    vlc_mutex_lock(&owner->lock);
827
0
    aout->stop (aout);
828
0
    owner->main_stream = NULL;
829
0
    vlc_mutex_unlock(&owner->lock);
830
0
}
831
832
float aout_VolumeGet (audio_output_t *aout)
833
0
{
834
0
    return var_GetFloat (aout, "volume");
835
0
}
836
837
int aout_VolumeSet (audio_output_t *aout, float vol)
838
0
{
839
0
    aout_owner_t *owner = aout_owner(aout);
840
0
    int ret;
841
842
0
    vlc_mutex_lock(&owner->lock);
843
0
    ret = aout->volume_set ? aout->volume_set(aout, vol) : -1;
844
0
    vlc_mutex_unlock(&owner->lock);
845
0
    return ret ? -1 : 0;
846
0
}
847
848
int aout_VolumeUpdate (audio_output_t *aout, int value, float *volp)
849
0
{
850
0
    int ret = -1;
851
0
    const float defaultVolume = (float)AOUT_VOLUME_DEFAULT;
852
0
    const float stepSize = var_InheritFloat (aout, "volume-step") / defaultVolume;
853
0
    float vol = aout_VolumeGet (aout);
854
855
0
    if (vol >= 0.f)
856
0
    {
857
0
        vol += (value * stepSize);
858
0
        vol = (roundf (vol / stepSize)) * stepSize;
859
0
        vol = clampf(vol, 0.f, AOUT_VOLUME_MAX / defaultVolume);
860
0
        if (volp != NULL)
861
0
            *volp = vol;
862
0
        ret = aout_VolumeSet (aout, vol);
863
0
    }
864
0
    return ret;
865
0
}
866
867
int aout_MuteGet (audio_output_t *aout)
868
0
{
869
0
    return var_InheritBool (aout, "mute");
870
0
}
871
872
int aout_MuteSet (audio_output_t *aout, bool mute)
873
0
{
874
0
    aout_owner_t *owner = aout_owner(aout);
875
0
    int ret;
876
877
0
    vlc_mutex_lock(&owner->lock);
878
0
    ret = aout->mute_set ? aout->mute_set(aout, mute) : -1;
879
0
    vlc_mutex_unlock(&owner->lock);
880
0
    return ret ? -1 : 0;
881
0
}
882
883
char *aout_DeviceGet (audio_output_t *aout)
884
0
{
885
0
    return var_GetNonEmptyString (aout, "device");
886
0
}
887
888
int aout_DeviceSet (audio_output_t *aout, const char *id)
889
0
{
890
0
    aout_owner_t *owner = aout_owner(aout);
891
0
    int ret;
892
893
0
    vlc_mutex_lock(&owner->lock);
894
0
    ret = aout->device_select ? aout->device_select(aout, id) : -1;
895
0
    vlc_mutex_unlock(&owner->lock);
896
0
    return ret ? -1 : 0;
897
0
}
898
899
int aout_DevicesList (audio_output_t *aout, char ***ids, char ***names)
900
0
{
901
0
    aout_owner_t *owner = aout_owner (aout);
902
0
    char **tabid, **tabname;
903
0
    unsigned i = 0;
904
905
0
    vlc_mutex_lock (&owner->dev.lock);
906
0
    tabid = vlc_alloc (owner->dev.count, sizeof (*tabid));
907
0
    tabname = vlc_alloc (owner->dev.count, sizeof (*tabname));
908
909
0
    if (unlikely(tabid == NULL || tabname == NULL))
910
0
        goto error;
911
912
0
    *ids = tabid;
913
0
    *names = tabname;
914
915
0
    aout_dev_t *dev;
916
0
    vlc_list_foreach(dev, &owner->dev.list, node)
917
0
    {
918
0
        tabid[i] = strdup(dev->id);
919
0
        if (unlikely(tabid[i] == NULL))
920
0
            goto error;
921
922
0
        tabname[i] = strdup(dev->name);
923
0
        if (unlikely(tabname[i] == NULL))
924
0
        {
925
0
            free(tabid[i]);
926
0
            goto error;
927
0
        }
928
929
0
        i++;
930
0
    }
931
0
    vlc_mutex_unlock (&owner->dev.lock);
932
933
0
    return i;
934
935
0
error:
936
0
    vlc_mutex_unlock(&owner->dev.lock);
937
0
    while (i > 0)
938
0
    {
939
0
        i--;
940
0
        free(tabname[i]);
941
0
        free(tabid[i]);
942
0
    }
943
0
    free(tabname);
944
0
    free(tabid);
945
0
    return -1;
946
0
}
947
948
static void aout_ChangeViewpoint(audio_output_t *aout,
949
                                 const vlc_viewpoint_t *p_viewpoint)
950
0
{
951
0
    aout_owner_t *owner = aout_owner(aout);
952
953
0
    vlc_mutex_lock(&owner->vp.lock);
954
0
    owner->vp.value = *p_viewpoint;
955
0
    atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
956
0
    vlc_mutex_unlock(&owner->vp.lock);
957
0
}
958
959
vlc_audio_meter_plugin *
960
aout_AddMeterPlugin(audio_output_t *aout, const char *chain,
961
                    const struct vlc_audio_meter_plugin_owner *meter_plugin_owner)
962
0
{
963
0
    aout_owner_t *owner = aout_owner(aout);
964
965
0
    return vlc_audio_meter_AddPlugin(&owner->meter, chain, meter_plugin_owner);
966
0
}
967
968
void
969
aout_RemoveMeterPlugin(audio_output_t *aout, vlc_audio_meter_plugin *plugin)
970
0
{
971
0
    aout_owner_t *owner = aout_owner(aout);
972
973
0
    vlc_audio_meter_RemovePlugin(&owner->meter, plugin);
974
0
}