output.c 23.1 KB
Newer Older
1 2 3
/*****************************************************************************
 * output.c : internal management of output streams for the audio output
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002-2004 VLC authors and VideoLAN
5
 * $Id$
6 7 8
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
9 10 11
 * 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
12
 * (at your option) any later version.
13
 *
14 15
 * 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
Jean-Baptiste Kempf committed
16 17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
19 20 21
 * 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.
22 23
 *****************************************************************************/

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

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

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

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

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


46
/* Local functions */
47 48 49 50 51 52 53
static void aout_OutputAssertLocked (audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner (aout);

    vlc_assert_locked (&owner->lock);
}

54 55 56 57 58 59 60 61 62 63 64
static void aout_Destructor( vlc_object_t * p_this );

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

65 66 67 68 69 70 71 72 73
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);
}

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
/**
 * 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)
{
95
    (cork ? var_IncInteger : var_DecInteger) (aout->obj.parent, "corks");
96 97
}

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

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
static void aout_HotplugNotify (audio_output_t *aout,
                                const char *id, const char *name)
{
    aout_owner_t *owner = aout_owner (aout);
    aout_dev_t *dev, **pp = &owner->dev.list;

    vlc_mutex_lock (&owner->dev.lock);
    while ((dev = *pp) != NULL)
    {
        if (!strcmp (id, dev->id))
            break;
        pp = &dev->next;
    }

    if (name != NULL)
    {
        if (dev == NULL) /* Added device */
        {
            dev = malloc (sizeof (*dev) + strlen (id));
            if (unlikely(dev == NULL))
                goto out;
            dev->next = NULL;
            strcpy (dev->id, id);
            *pp = dev;
            owner->dev.count++;
        }
        else /* Modified device */
            free (dev->name);
        dev->name = strdup (name);
    }
    else
    {
        if (dev != NULL) /* Removed device */
        {
            owner->dev.count--;
            *pp = dev->next;
            free (dev->name);
            free (dev);
        }
    }
out:
    vlc_mutex_unlock (&owner->dev.lock);
}

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

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

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

162 163 164 165 166 167 168 169 170 171
static const struct vlc_audio_output_events aout_events = {
    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;
}

196 197
static void aout_ChangeViewpoint(audio_output_t *, const vlc_viewpoint_t *);

198 199 200 201 202 203 204 205 206
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;
}

207 208 209 210 211 212 213
static void aout_OutputLock(audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner(aout);

    vlc_mutex_lock(&owner->lock);
}

214
static void aout_OutputUnlock(audio_output_t *aout)
215 216 217
{
    aout_owner_t *owner = aout_owner(aout);

218
    vlc_mutex_unlock(&owner->lock);
219 220
}

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

229 230 231 232 233 234 235 236
    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);
237
    vlc_mutex_init (&owner->dev.lock);
238
    vlc_mutex_init (&owner->vp.lock);
239 240
    vlc_viewpoint_init (&owner->vp.value);
    atomic_init (&owner->vp.update, false);
241

242 243 244 245 246 247 248
    vlc_object_set_destructor (aout, aout_Destructor);

    /* Audio output module callbacks */
    var_Create (aout, "volume", VLC_VAR_FLOAT);
    var_AddCallback (aout, "volume", var_Copy, parent);
    var_Create (aout, "mute", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    var_AddCallback (aout, "mute", var_Copy, parent);
249
    var_Create (aout, "device", VLC_VAR_STRING);
250
    var_AddCallback (aout, "device", var_CopyDevice, parent);
251 252
    /* TODO: 3.0 HACK: only way to signal DTS_HD to aout modules. */
    var_Create (aout, "dtshd", VLC_VAR_BOOL);
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 266 267 268 269 270 271 272 273 274 275 276
    if (owner->module == NULL)
    {
        msg_Err (aout, "no suitable audio output module");
        vlc_object_release (aout);
        return NULL;
    }

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

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

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

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

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

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

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

352
    /* Equalizer */
Mark Lee's avatar
Mark Lee committed
353 354
    var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
    var_Create (aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
355
    var_Create (aout, "equalizer-preset", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
Mark Lee's avatar
Mark Lee committed
356

357 358 359 360 361 362 363 364 365 366
    return aout;
}

/**
 * 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);

367
    aout_OutputLock (aout);
368 369 370 371
    module_unneed (aout, owner->module);
    /* Protect against late call from intf.c */
    aout->volume_set = NULL;
    aout->mute_set = NULL;
372
    aout->device_select = NULL;
373
    aout_OutputUnlock (aout);
374

375
    var_DelCallback (aout, "viewpoint", ViewpointCallback, NULL);
376
    var_DelCallback (aout, "audio-filter", FilterCallback, NULL);
377 378
    var_DelCallback (aout, "device", var_CopyDevice, aout->obj.parent);
    var_DelCallback (aout, "mute", var_Copy, aout->obj.parent);
379
    var_SetFloat (aout, "volume", -1.f);
380
    var_DelCallback (aout, "volume", var_Copy, aout->obj.parent);
381
    var_DelCallback (aout, "stereo-mode", StereoModeCallback, NULL);
382 383 384 385 386 387 388 389 390 391 392
    vlc_object_release (aout);
}

/**
 * Destroys the audio output lock used (asynchronously) by interface functions.
 */
static void aout_Destructor (vlc_object_t *obj)
{
    audio_output_t *aout = (audio_output_t *)obj;
    aout_owner_t *owner = aout_owner (aout);

393 394 395 396 397 398 399 400
    vlc_mutex_destroy (&owner->dev.lock);
    for (aout_dev_t *dev = owner->dev.list, *next; dev != NULL; dev = next)
    {
        next = dev->next;
        free (dev->name);
        free (dev);
    }

401
    vlc_mutex_destroy (&owner->vp.lock);
402 403 404
    vlc_mutex_destroy (&owner->lock);
}

405 406 407
static void aout_PrepareStereoMode (audio_output_t *aout,
                                    audio_sample_format_t *restrict fmt,
                                    aout_filters_cfg_t *filters_cfg,
408
                                    audio_channel_type_t input_chan_type,
409
                                    unsigned i_nb_input_channels)
410
{
411 412
    aout_owner_t *owner = aout_owner (aout);

413
    /* Fill Stereo mode choices */
414
    var_Change(aout, "stereo-mode", VLC_VAR_CLEARCHOICES);
415 416
    vlc_value_t val;
    const char *txt;
417
    val.i_int = 0;
418

419
    if (!AOUT_FMT_LINEAR(fmt) || i_nb_input_channels == 1)
420 421
        return;

422 423 424
    int i_output_mode = owner->requested_stereo_mode;
    int i_default_mode = AOUT_VAR_CHAN_UNSET;

425
    val.i_int = AOUT_VAR_CHAN_MONO;
426
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Mono"));
427

428
    if (i_nb_input_channels != 2)
429
    {
430
        val.i_int = AOUT_VAR_CHAN_UNSET;
431
        var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Original"));
432
    }
433
    if (fmt->i_chan_mode & AOUT_CHANMODE_DOLBYSTEREO)
434 435
    {
        val.i_int = AOUT_VAR_CHAN_DOLBYS;
436
        txt = _("Dolby Surround");
437 438 439 440
    }
    else
    {
        val.i_int = AOUT_VAR_CHAN_STEREO;
441
        txt = _("Stereo");
442
    }
443
    var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, txt);
444

445
    if (i_nb_input_channels == 2)
446
    {
447 448 449 450
        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 */
451

452
        val.i_int = AOUT_VAR_CHAN_LEFT;
453
        var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, _("Left"));
454
        val.i_int = AOUT_VAR_CHAN_RIGHT;
455
        var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val, _("Right"));
456 457

        val.i_int = AOUT_VAR_CHAN_RSTEREO;
458 459
        var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val,
                   _("Reverse stereo"));
460 461
    }

462 463
    if (input_chan_type == AUDIO_CHANNEL_TYPE_AMBISONICS
     || i_nb_input_channels > 2)
464 465
    {
        val.i_int = AOUT_VAR_CHAN_HEADPHONES;
466 467
        var_Change(aout, "stereo-mode", VLC_VAR_ADDCHOICE, val,
                   _("Headphones"));
468

469 470 471 472 473
        if (aout->current_sink_info.headphones)
            i_default_mode = AOUT_VAR_CHAN_HEADPHONES;
    }

    bool mode_available = false;
474
    vlc_list_t vals;
475
    if (!var_Change(aout, "stereo-mode", VLC_VAR_GETCHOICES, &vals,
476
                    (char ***)NULL))
477
    {
478
        for (int i = 0; !mode_available && i < vals.i_count; ++i)
479
        {
480
            if (vals.p_values[i].i_int == i_output_mode)
481
                mode_available = true;
482
        }
483
        var_FreeList(&vals, NULL);
484
    }
485 486
    if (!mode_available)
        i_output_mode = i_default_mode;
487

488
    /* The user may have selected a different channels configuration. */
489
    switch (i_output_mode)
490
    {
491
        case AOUT_VAR_CHAN_RSTEREO:
492 493
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_RIGHT;
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_LEFT;
494
            break;
495 496 497
        case AOUT_VAR_CHAN_STEREO:
            break;
        case AOUT_VAR_CHAN_LEFT:
498
            filters_cfg->remap[AOUT_CHANIDX_RIGHT] = AOUT_CHANIDX_DISABLE;
499 500
            break;
        case AOUT_VAR_CHAN_RIGHT:
501
            filters_cfg->remap[AOUT_CHANIDX_LEFT] = AOUT_CHANIDX_DISABLE;
502 503
            break;
        case AOUT_VAR_CHAN_DOLBYS:
504
            fmt->i_chan_mode = AOUT_CHANMODE_DOLBYSTEREO;
505
            break;
506 507 508
        case AOUT_VAR_CHAN_HEADPHONES:
            filters_cfg->headphones = true;
            break;
509 510 511 512 513
        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;
514
        default:
515
            break;
516
    }
517 518

    var_Change(aout, "stereo-mode", VLC_VAR_SETVALUE,
519
               (vlc_value_t) { .i_int = i_output_mode });
520 521 522 523 524
}

/**
 * Starts an audio output stream.
 * \param fmt audio output stream format [IN/OUT]
525
 * \warning The caller must NOT hold the audio output lock.
526 527 528 529
 */
int aout_OutputNew (audio_output_t *aout, audio_sample_format_t *restrict fmt,
                    aout_filters_cfg_t *filters_cfg)
{
530
    aout_owner_t *owner = aout_owner (aout);
531

532 533
    audio_channel_type_t input_chan_type = fmt->channel_type;
    unsigned i_nb_input_channels = fmt->i_channels;
534 535 536 537 538

    /* 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
539 540 541 542 543 544 545 546 547
    {
        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);
        }

548 549 550 551 552 553 554 555
        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
556
        /* Try to stay in integer domain if possible for no/slow FPU. */
557 558 559
        fmt->i_format = (fmt->i_bitspersample > 16) ? VLC_CODEC_FL32
                                                    : VLC_CODEC_S16N;

560 561 562 563
        if (fmt->i_physical_channels == AOUT_CHANS_STEREO
         && (owner->requested_stereo_mode == AOUT_VAR_CHAN_LEFT
          || owner->requested_stereo_mode == AOUT_VAR_CHAN_RIGHT))
            fmt->i_physical_channels = AOUT_CHAN_CENTER;
564

565
        aout_FormatPrepare (fmt);
566
        assert (aout_FormatNbChannels(fmt) > 0);
567 568
    }

569 570
    aout->current_sink_info.headphones = false;

571 572 573 574
    aout_OutputLock(aout);
    int ret = aout->start(aout, fmt);
    aout_OutputUnlock(aout);
    if (ret)
575 576 577 578 579
    {
        msg_Err (aout, "module not functional");
        return -1;
    }

580
    aout_PrepareStereoMode (aout, fmt, filters_cfg, input_chan_type,
581
                            i_nb_input_channels);
582

583
    aout_FormatPrepare (fmt);
584
    assert (fmt->i_bytes_per_frame > 0 && fmt->i_frame_length > 0);
585
    aout_FormatPrint (aout, "output", fmt);
586 587 588
    return 0;
}

589
/**
590
 * Stops the audio output stream (undoes aout_OutputNew()).
591
 * \note This can only be called after a successful aout_OutputNew().
592
 * \warning The caller must NOT hold the audio output lock.
593 594
 */
void aout_OutputDelete (audio_output_t *aout)
595
{
596
    aout_OutputLock(aout);
597 598
    if (aout->stop != NULL)
        aout->stop (aout);
599
    aout_OutputUnlock(aout);
600 601
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
602 603 604 605 606 607 608 609 610 611 612 613 614
/**
 * 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.
615
 * \return 0 on success, -1 on failure.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
616 617 618
 */
int aout_VolumeSet (audio_output_t *aout, float vol)
{
619
    int ret;
620

621
    aout_OutputLock(aout);
622
    ret = aout->volume_set(aout, vol);
623
    aout_OutputUnlock(aout);
624
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
625 626
}

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
/**
 * 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
654 655 656 657 658 659 660 661 662 663 664
/**
 * 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.
665
 * \return 0 on success, -1 on failure.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
666 667 668
 */
int aout_MuteSet (audio_output_t *aout, bool mute)
{
669
    int ret;
670

671
    aout_OutputLock(aout);
672
    ret = aout->mute_set(aout, mute);
673
    aout_OutputUnlock(aout);
674
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
}

/**
 * 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
690
 * \return zero on success, non-zero on error.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
691 692 693
 */
int aout_DeviceSet (audio_output_t *aout, const char *id)
{
694
    int ret;
695

696
    aout_OutputLock(aout);
697
    ret = aout->device_select(aout, id);
698
    aout_OutputUnlock(aout);
699
    return ret ? -1 : 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
700 701 702 703 704 705 706 707
}

/**
 * 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.
 *
708
 * \param ids pointer to a table of device identifiers [OUT]
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
709 710 711 712 713 714
 * \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)
{
715 716
    aout_owner_t *owner = aout_owner (aout);
    char **tabid, **tabname;
717
    unsigned i = 0;
718 719

    vlc_mutex_lock (&owner->dev.lock);
720 721
    tabid = vlc_alloc (owner->dev.count, sizeof (*tabid));
    tabname = vlc_alloc (owner->dev.count, sizeof (*tabname));
722

723 724 725 726 727 728 729 730 731 732 733
    if (unlikely(tabid == NULL || tabname == NULL))
        goto error;

    *ids = tabid;
    *names = tabname;

    for (aout_dev_t *dev = owner->dev.list; dev != NULL; dev = dev->next)
    {
        tabid[i] = strdup(dev->id);
        if (unlikely(tabid[i] == NULL))
            goto error;
734

735 736 737 738 739
        tabname[i] = strdup(dev->name);
        if (unlikely(tabname[i] == NULL))
        {
            free(tabid[i]);
            goto error;
740
        }
741 742

        i++;
743 744
    }
    vlc_mutex_unlock (&owner->dev.lock);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
745

746 747 748 749 750
    return i;

error:
    vlc_mutex_unlock(&owner->dev.lock);
    while (i > 0)
751
    {
752 753 754
        i--;
        free(tabname[i]);
        free(tabid[i]);
755
    }
756 757 758
    free(tabname);
    free(tabid);
    return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
759
}
760 761 762 763 764 765 766 767

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;
768
    atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
769 770
    vlc_mutex_unlock(&owner->vp.lock);
}