output.c 22.5 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
 * $Id$
6 7 8
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *
Jean-Baptiste Kempf's avatar
LGPL  
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
LGPL  
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
LGPL  
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>
Clément Stenac's avatar
Clément Stenac committed
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
static const char unset_str[1] = ""; /* Non-NULL constant string pointer */

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


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

    vlc_assert_locked (&owner->lock);
}

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

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

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
/**
 * Supply or update the current custom ("hardware") volume.
 * @note This only makes sense after calling aout_VolumeHardInit().
 * @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)
{
98
    (cork ? var_IncInteger : var_DecInteger) (aout->obj.parent, "corks");
99 100
}

101 102
static void aout_DeviceNotify (audio_output_t *aout, const char *id)
{
103
    var_SetString (aout, "device", (id != NULL) ? id : "");
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 147 148 149
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);
}

150 151 152 153 154
static void aout_RestartNotify (audio_output_t *aout, unsigned mode)
{
    aout_RequestRestart (aout, mode);
}

155 156 157 158
static int aout_GainNotify (audio_output_t *aout, float gain)
{
    aout_owner_t *owner = aout_owner (aout);

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

165 166 167
static int FilterCallback (vlc_object_t *obj, const char *var,
                           vlc_value_t prev, vlc_value_t cur, void *data)
{
168 169 170
    if (strcmp(prev.psz_string, cur.psz_string))
        aout_InputRequestRestart ((audio_output_t *)obj);
    (void) var; (void) data;
171 172 173
    return VLC_SUCCESS;
}

174 175 176 177 178 179
#undef aout_New
/**
 * Creates an audio output object and initializes an output module.
 */
audio_output_t *aout_New (vlc_object_t *parent)
{
180 181
    vlc_value_t val, text;

182 183 184 185 186 187 188 189
    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);
190
    vlc_mutex_init (&owner->req.lock);
191
    vlc_mutex_init (&owner->dev.lock);
192 193 194 195
    owner->req.device = (char *)unset_str;
    owner->req.volume = -1.f;
    owner->req.mute = -1;

196 197 198 199 200 201 202
    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);
203
    var_Create (aout, "device", VLC_VAR_STRING);
204
    var_AddCallback (aout, "device", var_CopyDevice, parent);
205 206 207 208

    aout->event.volume_report = aout_VolumeNotify;
    aout->event.mute_report = aout_MuteNotify;
    aout->event.policy_report = aout_PolicyNotify;
209 210
    aout->event.device_report = aout_DeviceNotify;
    aout->event.hotplug_report = aout_HotplugNotify;
211
    aout->event.gain_request = aout_GainNotify;
212
    aout->event.restart_request = aout_RestartNotify;
213 214 215 216 217 218

    /* Audio output module initialization */
    aout->start = NULL;
    aout->stop = NULL;
    aout->volume_set = NULL;
    aout->mute_set = NULL;
219
    aout->device_select = NULL;
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
    owner->module = module_need (aout, "audio output", "$aout", false);
    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 */
    var_Create (aout, "visual", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
    text.psz_string = _("Visualizations");
    var_Change (aout, "visual", VLC_VAR_SETTEXT, &text, NULL);
    val.psz_string = (char *)"";
    text.psz_string = _("Disable");
    var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    val.psz_string = (char *)"spectrometer";
    text.psz_string = _("Spectrometer");
    var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    val.psz_string = (char *)"scope";
    text.psz_string = _("Scope");
    var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    val.psz_string = (char *)"spectrum";
    text.psz_string = _("Spectrum");
    var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    val.psz_string = (char *)"vuMeter";
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
251
    text.psz_string = _("VU meter");
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
    var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    /* Look for goom plugin */
    if (module_exists ("goom"))
    {
        val.psz_string = (char *)"goom";
        text.psz_string = (char *)"Goom";
        var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    }
    /* Look for libprojectM plugin */
    if (module_exists ("projectm"))
    {
        val.psz_string = (char *)"projectm";
        text.psz_string = (char*)"projectM";
        var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    }
    /* Look for VSXu plugin */
    if (module_exists ("vsxu"))
    {
        val.psz_string = (char *)"vsxu";
        text.psz_string = (char*)"Vovoid VSXu";
        var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    }
274 275 276 277 278 279 280
    /* Look for glspectrum plugin */
    if (module_exists ("glspectrum"))
    {
        val.psz_string = (char *)"glspectrum";
        text.psz_string = (char*)"3D spectrum";
        var_Change (aout, "visual", VLC_VAR_ADDCHOICE, &val, &text);
    }
281 282 283 284 285 286 287 288
    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);
289
    var_AddCallback (aout, "audio-filter", FilterCallback, NULL);
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    text.psz_string = _("Audio filters");
    var_Change (aout, "audio-filter", VLC_VAR_SETTEXT, &text, NULL);


    var_Create (aout, "audio-visual", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
    text.psz_string = _("Audio visualizations");
    var_Change (aout, "audio-visual", VLC_VAR_SETTEXT, &text, NULL);

    /* Replay gain */
    var_Create (aout, "audio-replay-gain-mode",
                VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    text.psz_string = _("Replay gain");
    var_Change (aout, "audio-replay-gain-mode", VLC_VAR_SETTEXT, &text, NULL);
    cfg = config_FindConfig (VLC_OBJECT(aout), "audio-replay-gain-mode");
    if (likely(cfg != NULL))
        for (unsigned i = 0; i < cfg->list_count; i++)
        {
            val.psz_string = cfg->list.psz[i];
            text.psz_string = vlc_gettext(cfg->list_text[i]);
            var_Change (aout, "audio-replay-gain-mode", VLC_VAR_ADDCHOICE,
                            &val, &text);
        }

313
    /* Equalizer */
Mark Lee's avatar
Mark Lee committed
314 315
    var_Create (aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
    var_Create (aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
316
    var_Create (aout, "equalizer-preset", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
Mark Lee's avatar
Mark Lee committed
317

318 319 320 321 322 323 324 325 326 327
    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);

328
    aout_OutputLock (aout);
329 330 331 332
    module_unneed (aout, owner->module);
    /* Protect against late call from intf.c */
    aout->volume_set = NULL;
    aout->mute_set = NULL;
333
    aout->device_select = NULL;
334
    aout_OutputUnlock (aout);
335

336
    var_DelCallback (aout, "audio-filter", FilterCallback, NULL);
337 338
    var_DelCallback (aout, "device", var_CopyDevice, aout->obj.parent);
    var_DelCallback (aout, "mute", var_Copy, aout->obj.parent);
339
    var_SetFloat (aout, "volume", -1.f);
340
    var_DelCallback (aout, "volume", var_Copy, aout->obj.parent);
341 342 343 344 345 346 347 348 349 350 351
    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);

352 353 354 355 356 357 358 359
    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);
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
360 361
    assert (owner->req.device == unset_str);
    vlc_mutex_destroy (&owner->req.lock);
362 363 364 365 366
    vlc_mutex_destroy (&owner->lock);
}

/**
 * Starts an audio output stream.
367
 * \param fmt audio output stream format [IN/OUT]
368 369
 * \warning The caller must hold the audio output lock.
 */
370
int aout_OutputNew (audio_output_t *aout, audio_sample_format_t *restrict fmt)
371
{
372
    aout_OutputAssertLocked (aout);
373

374 375 376 377 378 379 380 381 382 383 384
    /* 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))
    {   /* Try to stay in integer domain if possible for no/slow FPU. */
        fmt->i_format = (fmt->i_bitspersample > 16) ? VLC_CODEC_FL32
                                                    : VLC_CODEC_S16N;
        aout_FormatPrepare (fmt);
    }

    if (aout->start (aout, fmt))
385 386
    {
        msg_Err (aout, "module not functional");
387 388
        return -1;
    }
389

390
    /* The user may have selected a different channels configuration. */
391 392
    var_AddCallback (aout, "stereo-mode", aout_ChannelsRestart, NULL);
    switch (var_GetInteger (aout, "stereo-mode"))
393
    {
394
        case AOUT_VAR_CHAN_RSTEREO:
395 396
            fmt->i_original_channels |= AOUT_CHAN_REVERSESTEREO;
            break;
397
        case AOUT_VAR_CHAN_STEREO:
398
            fmt->i_original_channels = AOUT_CHANS_STEREO;
399 400
            break;
        case AOUT_VAR_CHAN_LEFT:
401
            fmt->i_original_channels = AOUT_CHAN_LEFT;
402 403
            break;
        case AOUT_VAR_CHAN_RIGHT:
404
            fmt->i_original_channels = AOUT_CHAN_RIGHT;
405 406
            break;
        case AOUT_VAR_CHAN_DOLBYS:
407
            fmt->i_original_channels = AOUT_CHANS_STEREO|AOUT_CHAN_DOLBYSTEREO;
408 409 410
            break;
        default:
        {
411 412
            if ((fmt->i_original_channels & AOUT_CHAN_PHYSMASK)
                                                          != AOUT_CHANS_STEREO)
413 414 415 416
                 break;

            vlc_value_t val, txt;
            val.i_int = 0;
417
            var_Change (aout, "stereo-mode", VLC_VAR_DELCHOICE, &val, NULL);
418
            if (fmt->i_original_channels & AOUT_CHAN_DOLBYSTEREO)
419 420 421 422 423 424 425 426 427
            {
                val.i_int = AOUT_VAR_CHAN_DOLBYS;
                txt.psz_string = _("Dolby Surround");
            }
            else
            {
                val.i_int = AOUT_VAR_CHAN_STEREO;
                txt.psz_string = _("Stereo");
            }
428 429
            var_Change (aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, &txt);
            var_Change (aout, "stereo-mode", VLC_VAR_SETVALUE, &val, NULL);
430 431
            val.i_int = AOUT_VAR_CHAN_LEFT;
            txt.psz_string = _("Left");
432
            var_Change (aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, &txt);
433
            if (fmt->i_original_channels & AOUT_CHAN_DUALMONO)
434
            {   /* Go directly to the left channel. */
435
                fmt->i_original_channels = AOUT_CHAN_LEFT;
436
                var_Change (aout, "stereo-mode", VLC_VAR_SETVALUE, &val, NULL);
437 438 439
            }
            val.i_int = AOUT_VAR_CHAN_RIGHT;
            txt.psz_string = _("Right");
440
            var_Change (aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, &txt);
441 442
            val.i_int = AOUT_VAR_CHAN_RSTEREO;
            txt.psz_string = _("Reverse stereo");
443
            var_Change (aout, "stereo-mode", VLC_VAR_ADDCHOICE, &val, &txt);
444
        }
445 446
    }

447
    aout_FormatPrepare (fmt);
Thomas Guillem's avatar
Thomas Guillem committed
448
    assert (fmt->i_bytes_per_frame > 0 && fmt->i_frame_length > 0);
449
    aout_FormatPrint (aout, "output", fmt);
450 451 452
    return 0;
}

453
/**
454
 * Stops the audio output stream (undoes aout_OutputNew()).
455
 * \note This can only be called after a successful aout_OutputNew().
456
 * \warning The caller must hold the audio output lock.
457 458
 */
void aout_OutputDelete (audio_output_t *aout)
459
{
460
    aout_OutputAssertLocked (aout);
461

462
    var_DelCallback (aout, "stereo-mode", aout_ChannelsRestart, NULL);
463 464
    if (aout->stop != NULL)
        aout->stop (aout);
465 466
}

467
int aout_OutputTimeGet (audio_output_t *aout, mtime_t *delay)
468
{
469
    aout_OutputAssertLocked (aout);
470 471 472

    if (aout->time_get == NULL)
        return -1;
473
    return aout->time_get (aout, delay);
474 475
}

476 477
/**
 * Plays a decoded audio buffer.
478
 * \note This can only be called after a successful aout_OutputNew().
479
 * \warning The caller must hold the audio output lock.
480
 */
481
void aout_OutputPlay (audio_output_t *aout, block_t *block)
482
{
483
    aout_OutputAssertLocked (aout);
Thomas Guillem's avatar
Thomas Guillem committed
484 485
#ifndef NDEBUG
    aout_owner_t *owner = aout_owner (aout);
486 487
    assert (owner->mixer_format.i_frame_length > 0);
    assert (block->i_buffer == 0 || block->i_buffer / block->i_nb_samples ==
Thomas Guillem's avatar
Thomas Guillem committed
488 489 490
            owner->mixer_format.i_bytes_per_frame /
            owner->mixer_format.i_frame_length);
#endif
491
    aout->play (aout, block);
492 493
}

494 495 496 497 498 499 500
static void PauseDefault (audio_output_t *aout, bool pause, mtime_t date)
{
    if (pause)
        aout_OutputFlush (aout, false);
    (void) date;
}

501 502 503 504
/**
 * Notifies the audio output (if any) of pause/resume events.
 * This enables the output to expedite pause, instead of waiting for its
 * buffers to drain.
505
 * \note This can only be called after a successful aout_OutputNew().
506
 * \warning The caller must hold the audio output lock.
507
 */
508
void aout_OutputPause( audio_output_t *aout, bool pause, mtime_t date )
509
{
510
    aout_OutputAssertLocked (aout);
511
    ((aout->pause != NULL) ? aout->pause : PauseDefault) (aout, pause, date);
512 513
}

514 515 516
/**
 * Flushes or drains the audio output buffers.
 * This enables the output to expedite seek and stop.
517
 * \param wait if true, wait for buffer playback (i.e. drain),
518
 *             if false, discard the buffers immediately (i.e. flush)
519
 * \note This can only be called after a successful aout_OutputNew().
520
 * \warning The caller must hold the audio output lock.
521 522 523
 */
void aout_OutputFlush( audio_output_t *aout, bool wait )
{
524
    aout_OutputAssertLocked( aout );
525
    aout->flush (aout, wait);
526
}
527

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
static int aout_OutputVolumeSet (audio_output_t *aout, float vol)
{
    aout_OutputAssertLocked (aout);
    return (aout->volume_set != NULL) ? aout->volume_set (aout, vol) : -1;
}

static int aout_OutputMuteSet (audio_output_t *aout, bool mute)
{
    aout_OutputAssertLocked (aout);
    return (aout->mute_set != NULL) ? aout->mute_set (aout, mute) : -1;
}

static int aout_OutputDeviceSet (audio_output_t *aout, const char *id)
{
    aout_OutputAssertLocked (aout);
    return (aout->device_select != NULL) ? aout->device_select (aout, id) : -1;
}

546 547 548 549 550 551 552
void aout_OutputLock (audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner (aout);

    vlc_mutex_lock (&owner->lock);
}

553 554 555 556 557 558 559
static int aout_OutputTryLock (audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner (aout);

    return vlc_mutex_trylock (&owner->lock);
}

560 561 562 563
void aout_OutputUnlock (audio_output_t *aout)
{
    aout_owner_t *owner = aout_owner (aout);

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    vlc_assert_locked (&owner->lock);
    vlc_mutex_lock (&owner->req.lock);

    if (owner->req.device != unset_str)
    {
        aout_OutputDeviceSet (aout, owner->req.device);
        free (owner->req.device);
        owner->req.device = (char *)unset_str;
    }

    if (owner->req.volume >= 0.f)
    {
        aout_OutputVolumeSet (aout, owner->req.volume);
        owner->req.volume = -1.f;
    }

    if (owner->req.mute >= 0)
    {
        aout_OutputMuteSet (aout, owner->req.mute);
        owner->req.mute = -1;
    }

586
    vlc_mutex_unlock (&owner->lock);
587 588 589 590 591 592
    /* If another thread is blocked waiting for owner->req.lock at this point,
     * this aout_OutputUnlock() call will not see and apply its change request.
     * The other thread will need to apply the change request itself, which
     * implies it is able to (try-)lock owner->lock. Therefore this thread must
     * release owner->lock _before_ owner->req.lock. Do not reorder!!! */
    vlc_mutex_unlock (&owner->req.lock);
593
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
594 595 596 597 598 599 600 601 602 603 604 605 606 607

/**
 * 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.
608
 * \return 0 on success, -1 on failure (TODO).
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
609 610 611
 */
int aout_VolumeSet (audio_output_t *aout, float vol)
{
612
    aout_owner_t *owner = aout_owner (aout);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
613

614 615 616 617 618 619 620 621
    assert (vol >= 0.f);
    vlc_mutex_lock (&owner->req.lock);
    owner->req.volume = vol;
    vlc_mutex_unlock (&owner->req.lock);

    if (aout_OutputTryLock (aout) == 0)
        aout_OutputUnlock (aout);
    return 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
622 623 624 625 626 627 628 629 630 631 632 633 634
}

/**
 * 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.
635
 * \return 0 on success, -1 on failure (TODO).
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
636 637 638
 */
int aout_MuteSet (audio_output_t *aout, bool mute)
{
639
    aout_owner_t *owner = aout_owner (aout);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
640

641 642 643 644 645 646 647
    vlc_mutex_lock (&owner->req.lock);
    owner->req.mute = mute;
    vlc_mutex_unlock (&owner->req.lock);

    if (aout_OutputTryLock (aout) == 0)
        aout_OutputUnlock (aout);
    return 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
}

/**
 * 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
663
 * \return zero on success, non-zero on error (TODO).
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
664 665 666
 */
int aout_DeviceSet (audio_output_t *aout, const char *id)
{
667
    aout_owner_t *owner = aout_owner (aout);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
668

669 670 671 672 673 674 675 676 677
    char *dev = NULL;
    if (id != NULL)
    {
        dev = strdup (id);
        if (unlikely(dev == NULL))
            return -1;
    }

    vlc_mutex_lock (&owner->req.lock);
678 679
    if (owner->req.device != unset_str)
        free (owner->req.device);
680 681 682 683 684 685
    owner->req.device = dev;
    vlc_mutex_unlock (&owner->req.lock);

    if (aout_OutputTryLock (aout) == 0)
        aout_OutputUnlock (aout);
    return 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
686 687 688 689 690 691 692 693
}

/**
 * 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.
 *
694
 * \param ids pointer to a table of device identifiers [OUT]
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
695 696 697 698 699 700
 * \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)
{
701 702
    aout_owner_t *owner = aout_owner (aout);
    char **tabid, **tabname;
703
    unsigned i = 0;
704 705

    vlc_mutex_lock (&owner->dev.lock);
706 707 708
    tabid = malloc (sizeof (*tabid) * owner->dev.count);
    tabname = malloc (sizeof (*tabname) * owner->dev.count);

709 710 711 712 713 714 715 716 717 718 719
    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;
720

721 722 723 724 725
        tabname[i] = strdup(dev->name);
        if (unlikely(tabname[i] == NULL))
        {
            free(tabid[i]);
            goto error;
726
        }
727 728

        i++;
729 730
    }
    vlc_mutex_unlock (&owner->dev.lock);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
731

732 733 734 735 736
    return i;

error:
    vlc_mutex_unlock(&owner->dev.lock);
    while (i > 0)
737
    {
738 739 740
        i--;
        free(tabname[i]);
        free(tabid[i]);
741
    }
742 743 744
    free(tabname);
    free(tabid);
    return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
745
}