wasapi.c 19 KB
Newer Older
1 2 3 4 5
/*****************************************************************************
 * wasapi.c : Windows Audio Session API output plugin for VLC
 *****************************************************************************
 * Copyright (C) 2012 Rémi Denis-Courmont
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
6 7
 * 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
8 9 10 11 12
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 15 16 17 18 19 20 21
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
22
# include <config.h>
23 24 25 26
#endif

#define INITGUID
#define COBJMACROS
27
#define CONST_VTABLE
28
#define NONEWWAVE
29

30
#include <stdlib.h>
31 32 33
#include <assert.h>

#include <vlc_common.h>
34
#include <vlc_codecs.h>
35
#include <vlc_aout.h>
36
#include <vlc_plugin.h>
37 38

#include <audioclient.h>
39
#include "audio_output/mmdevice.h"
40

41 42 43 44 45 46 47 48 49
/* 00000092-0000-0010-8000-00aa00389b71 */
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL,
            WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00,
            0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

/* 00000008-0000-0010-8000-00aa00389b71 */
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEC61937_DTS,
            WAVE_FORMAT_DTS_MS, 0x0000, 0x0010, 0x80, 0x00,
            0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
50

Thomas Guillem's avatar
Thomas Guillem committed
51 52 53 54 55
/* 0000000b-0cea-0010-8000-00aa00389b71 */
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD,
            0x000b, 0x0cea, 0x0010, 0x80, 0x00,
            0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

56 57 58 59 60
/* 0000000a-0cea-0010-8000-00aa00389b71 */
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS,
            0x000a, 0x0cea, 0x0010, 0x80, 0x00,
            0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

61 62 63 64 65
/* 0000000c-0cea-0010-8000-00aa00389b71 */
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
            0x000c, 0x0cea, 0x0010, 0x80, 0x00,
            0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

66
static BOOL CALLBACK InitFreq(INIT_ONCE *once, void *param, void **context)
67
{
68 69
    (void) once; (void) context;
    return QueryPerformanceFrequency(param);
70 71
}

72 73
static LARGE_INTEGER freq; /* performance counters frequency */

74 75 76 77 78 79 80 81 82 83 84
static UINT64 GetQPC(void)
{
    LARGE_INTEGER counter;

    if (!QueryPerformanceCounter(&counter))
        abort();

    lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
    return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
}

85
typedef struct aout_stream_sys
86 87
{
    IAudioClient *client;
88

89 90
    uint8_t chans_table[AOUT_CHAN_MAX];
    uint8_t chans_to_reorder;
91

92
    vlc_fourcc_t format; /**< Sample format */
93
    unsigned rate; /**< Sample rate */
Thomas Guillem's avatar
Thomas Guillem committed
94
    unsigned block_align;
95
    UINT64 written; /**< Frames written to the buffer */
96
    UINT32 frames; /**< Total buffer size (frames) */
97
} aout_stream_sys_t;
98

99 100

/*** VLC audio output callbacks ***/
101
static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
102
{
103
    aout_stream_sys_t *sys = s->sys;
104
    void *pv;
105
    UINT64 pos, qpcpos, freq;
106 107
    HRESULT hr;

108
    hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
109
    if (FAILED(hr))
110
    {
111
        msg_Err(s, "cannot get clock (error 0x%lx)", hr);
112 113
        return hr;
    }
114

115 116 117
    IAudioClock *clock = pv;

    hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
118
    if (SUCCEEDED(hr))
119 120 121
        hr = IAudioClock_GetFrequency(clock, &freq);
    IAudioClock_Release(clock);
    if (FAILED(hr))
122
    {
123 124
        msg_Err(s, "cannot get position (error 0x%lx)", hr);
        return hr;
125
    }
126 127 128 129 130 131 132 133 134 135 136

    lldiv_t w = lldiv(sys->written, sys->rate);
    lldiv_t r = lldiv(pos, freq);

    static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken");

    *delay = ((w.quot - r.quot) * CLOCK_FREQ)
           + ((w.rem * CLOCK_FREQ) / sys->rate)
           - ((r.rem * CLOCK_FREQ) / freq)
           - ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));

137
    return hr;
138 139
}

140
static HRESULT Play(aout_stream_t *s, block_t *block)
141
{
142
    aout_stream_sys_t *sys = s->sys;
143 144
    void *pv;
    HRESULT hr;
145

146 147
    if (sys->chans_to_reorder)
        aout_ChannelReorder(block->p_buffer, block->i_buffer,
148
                          sys->chans_to_reorder, sys->chans_table, sys->format);
149

150 151 152
    hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
    if (FAILED(hr))
    {
153
        msg_Err(s, "cannot get render client (error 0x%lx)", hr);
154 155 156 157
        goto out;
    }

    IAudioRenderClient *render = pv;
158
    for (;;)
159 160 161 162 163
    {
        UINT32 frames;
        hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
        if (FAILED(hr))
        {
164
            msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
165 166 167 168 169 170 171 172 173
            break;
        }

        assert(frames <= sys->frames);
        frames = sys->frames - frames;
        if (frames > block->i_nb_samples)
            frames = block->i_nb_samples;

        BYTE *dst;
174
        hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
175 176
        if (FAILED(hr))
        {
177
            msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
178 179 180
            break;
        }

Thomas Guillem's avatar
Thomas Guillem committed
181
        const size_t copy = frames * sys->block_align;
182 183

        memcpy(dst, block->p_buffer, copy);
184
        hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
185 186
        if (FAILED(hr))
        {
187
            msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
188 189
            break;
        }
190
        IAudioClient_Start(sys->client);
191 192 193 194

        block->p_buffer += copy;
        block->i_buffer -= copy;
        block->i_nb_samples -= frames;
195
        sys->written += frames;
196 197
        if (block->i_nb_samples == 0)
            break; /* done */
198

199
        /* Out of buffer space, sleep */
200
        msleep(sys->frames * (CLOCK_FREQ / 2) / sys->rate);
201
    }
202 203
    IAudioRenderClient_Release(render);
out:
204
    block_Release(block);
205

206
    return hr;
207 208
}

209
static HRESULT Pause(aout_stream_t *s, bool paused)
210
{
211
    aout_stream_sys_t *sys = s->sys;
212 213
    HRESULT hr;

214 215 216 217
    if (paused)
        hr = IAudioClient_Stop(sys->client);
    else
        hr = IAudioClient_Start(sys->client);
218
    if (FAILED(hr))
219
        msg_Warn(s, "cannot %s stream (error 0x%lx)",
220
                 paused ? "stop" : "start", hr);
221
    return hr;
222 223
}

224
static HRESULT Flush(aout_stream_t *s)
225
{
226
    aout_stream_sys_t *sys = s->sys;
227 228
    HRESULT hr;

229
    IAudioClient_Stop(sys->client);
230

231
    hr = IAudioClient_Reset(sys->client);
232
    if (SUCCEEDED(hr))
233 234
    {
        msg_Dbg(s, "reset");
235
        sys->written = 0;
236 237 238
    }
    else
        msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
239
    return hr;
240 241
}

242 243

/*** Initialization / deinitialization **/
244 245 246 247 248 249 250 251 252 253 254 255 256
static const uint32_t chans_out[] = {
    SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
    SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
    SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
    SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
};
static const uint32_t chans_in[] = {
    SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
    SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
    SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
    SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
};

257 258 259 260 261 262 263
static void vlc_HdmiToWave(WAVEFORMATEXTENSIBLE_IEC61937 *restrict wf_iec61937,
                           audio_sample_format_t *restrict audio)
{
    WAVEFORMATEXTENSIBLE *wf = &wf_iec61937->FormatExt;

    switch (audio->i_format)
    {
Thomas Guillem's avatar
Thomas Guillem committed
264 265 266 267 268 269
    case VLC_CODEC_DTS:
        wf->SubFormat = _KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
        wf->Format.nChannels = 8;
        wf->dwChannelMask = KSAUDIO_SPEAKER_7POINT1;
        audio->i_rate = 768000;
        break;
270 271 272 273 274
    case VLC_CODEC_EAC3:
        wf->SubFormat = _KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
        wf->Format.nChannels = 2;
        wf->dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
        break;
275 276 277 278 279
    case VLC_CODEC_TRUEHD:
    case VLC_CODEC_MLP:
        wf->SubFormat = _KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
        wf->Format.nChannels = 8;
        wf->dwChannelMask = KSAUDIO_SPEAKER_7POINT1;
280
        audio->i_rate = 768000;
281
        break;
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    default:
        vlc_assert_unreachable();
    }
    wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
    wf->Format.nSamplesPerSec = 192000;
    wf->Format.wBitsPerSample = 16;
    wf->Format.nBlockAlign = wf->Format.wBitsPerSample / 8 * wf->Format.nChannels;
    wf->Format.nAvgBytesPerSec = wf->Format.nSamplesPerSec * wf->Format.nBlockAlign;
    wf->Format.cbSize = sizeof (*wf_iec61937) - sizeof (wf->Format);

    wf->Samples.wValidBitsPerSample = wf->Format.wBitsPerSample;

    wf_iec61937->dwEncodedSamplesPerSec = audio->i_rate;
    wf_iec61937->dwEncodedChannelCount = audio->i_channels;
    wf_iec61937->dwAverageBytesPerSec = 0;

    audio->i_format = VLC_CODEC_SPDIFL;
299 300
    audio->i_bytes_per_frame = wf->Format.nBlockAlign;
    audio->i_frame_length = 1;
301 302
}

303 304 305
static void vlc_SpdifToWave(WAVEFORMATEXTENSIBLE *restrict wf,
                            audio_sample_format_t *restrict audio)
{
306 307 308 309 310 311 312 313 314 315 316 317 318
    switch (audio->i_format)
    {
    case VLC_CODEC_DTS:
        wf->SubFormat = _KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
        break;
    case VLC_CODEC_SPDIFL:
    case VLC_CODEC_SPDIFB:
    case VLC_CODEC_A52:
        wf->SubFormat = _KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
        break;
    default:
        vlc_assert_unreachable();
    }
319 320 321 322 323 324 325 326 327 328 329 330

    wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
    wf->Format.nChannels = 2; /* To prevent channel re-ordering */
    wf->Format.nSamplesPerSec = audio->i_rate;
    wf->Format.wBitsPerSample = 16;
    wf->Format.nBlockAlign = 4; /* wf->Format.wBitsPerSample / 8 * wf->Format.nChannels  */
    wf->Format.nAvgBytesPerSec = wf->Format.nSamplesPerSec * wf->Format.nBlockAlign;
    wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);

    wf->Samples.wValidBitsPerSample = wf->Format.wBitsPerSample;

    wf->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
331 332 333 334

    audio->i_format = VLC_CODEC_SPDIFL;
    audio->i_bytes_per_frame = wf->Format.nBlockAlign;
    audio->i_frame_length = 1;
335 336
}

337 338 339 340 341 342
static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
                       audio_sample_format_t *restrict audio)
{
    switch (audio->i_format)
    {
        case VLC_CODEC_FL64:
343 344
            audio->i_format = VLC_CODEC_FL32;
        case VLC_CODEC_FL32:
345 346 347
            wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
            break;

348 349
        case VLC_CODEC_U8:
            audio->i_format = VLC_CODEC_S16N;
350 351 352
        case VLC_CODEC_S16N:
            wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
            break;
353

354 355 356 357
        default:
            audio->i_format = VLC_CODEC_FL32;
            wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
            break;
358 359 360 361 362 363 364 365 366 367 368 369 370 371
    }
    aout_FormatPrepare (audio);

    wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
    wf->Format.nChannels = audio->i_channels;
    wf->Format.nSamplesPerSec = audio->i_rate;
    wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
    wf->Format.nBlockAlign = audio->i_bytes_per_frame;
    wf->Format.wBitsPerSample = audio->i_bitspersample;
    wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);

    wf->Samples.wValidBitsPerSample = audio->i_bitspersample;

    wf->dwChannelMask = 0;
372 373 374
    for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
        if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
            wf->dwChannelMask |= chans_in[i];
375 376 377 378 379 380
}

static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
                        audio_sample_format_t *restrict audio)
{
    audio->i_rate = wf->nSamplesPerSec;
381
    audio->i_physical_channels = 0;
382 383 384 385 386

    if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
    {
        const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;

387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
        if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
        {
            switch (wf->wBitsPerSample)
            {
                case 64:
                    audio->i_format = VLC_CODEC_FL64;
                    break;
                case 32:
                    audio->i_format = VLC_CODEC_FL32;
                    break;
                default:
                    return -1;
            }
        }
        else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
        {
            switch (wf->wBitsPerSample)
            {
                case 32:
                    audio->i_format = VLC_CODEC_S32N;
                    break;
                case 16:
                    audio->i_format = VLC_CODEC_S16N;
                    break;
                default:
                    return -1;
            }
        }

        if (wfe->Samples.wValidBitsPerSample != wf->wBitsPerSample)
            return -1;

419 420 421 422
        for (unsigned i = 0; chans_in[i]; i++)
            if (wfe->dwChannelMask & chans_in[i])
                audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
    }
423 424
    else
        return -1;
425 426 427

    aout_FormatPrepare (audio);

428 429 430 431 432
    if (wf->nChannels != audio->i_channels)
        return -1;
    return 0;
}

433 434 435 436 437 438 439 440 441 442 443 444 445 446
static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
                                    uint8_t *restrict table)
{
    uint32_t mask = 0;

    if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
    {
        const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;

        mask = wfe->dwChannelMask;
    }
    return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
}

Thomas Guillem's avatar
Thomas Guillem committed
447

448 449
static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict pfmt,
                     const GUID *sid)
450
{
451 452 453 454 455
    static INIT_ONCE freq_once = INIT_ONCE_STATIC_INIT;

    if (!InitOnceExecuteOnce(&freq_once, InitFreq, &freq, NULL))
        return E_FAIL;

456
    aout_stream_sys_t *sys = malloc(sizeof (*sys));
457 458 459
    if (unlikely(sys == NULL))
        return E_OUTOFMEMORY;
    sys->client = NULL;
460

461
    /* Configure audio stream */
462 463
    WAVEFORMATEXTENSIBLE_IEC61937 wf_iec61937;
    WAVEFORMATEXTENSIBLE *pwfe = &wf_iec61937.FormatExt;
464
    WAVEFORMATEX *pwf = &pwfe->Format, *pwf_closest, *pwf_mix = NULL;
465
    AUDCLNT_SHAREMODE shared_mode;
466
    REFERENCE_TIME buffer_duration;
467
    audio_sample_format_t fmt = *pfmt;
Thomas Guillem's avatar
Thomas Guillem committed
468 469
    bool b_spdif = AOUT_FMT_SPDIF(&fmt);
    bool b_hdmi = AOUT_FMT_HDMI(&fmt);
470 471 472 473 474 475 476 477 478 479 480
    bool b_dtshd = false;

    if (fmt.i_format == VLC_CODEC_DTS)
    {
        b_dtshd = var_GetBool(s->obj.parent, "dtshd");
        if (b_dtshd)
        {
            b_hdmi = true;
            b_spdif = false;
        }
    }
481

482 483 484 485 486 487 488 489 490
    void *pv;
    HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
    if (FAILED(hr))
    {
        msg_Err(s, "cannot activate client (error 0x%lx)", hr);
        goto error;
    }
    sys->client = pv;

Thomas Guillem's avatar
Thomas Guillem committed
491
    if (b_spdif)
492
    {
493 494
        vlc_SpdifToWave(pwfe, &fmt);
        shared_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
495 496
        /* The max buffer duration in exclusive mode is 2 seconds */
        buffer_duration = AOUT_MAX_PREPARE_TIME;
497
    }
Thomas Guillem's avatar
Thomas Guillem committed
498
    else if (b_hdmi)
499 500
    {
        vlc_HdmiToWave(&wf_iec61937, &fmt);
501
        shared_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
502 503
        /* The max buffer duration in exclusive mode is 2 seconds */
        buffer_duration = AOUT_MAX_PREPARE_TIME;
504
    }
505
    else if (AOUT_FMT_LINEAR(&fmt))
506 507
    {
        shared_mode = AUDCLNT_SHAREMODE_SHARED;
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528

        if (fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
        {
            fmt.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;

            /* Render Ambisonics on the native mix format */
            hr = IAudioClient_GetMixFormat(sys->client, &pwf_mix);
            if (FAILED(hr) || vlc_FromWave(pwf_mix, &fmt))
                vlc_ToWave(pwfe, &fmt); /* failed, fallback to default */
            else
                pwf = pwf_mix;

            /* Setup low latency in order to quickly react to ambisonics filters
             * viewpoint changes. */
            buffer_duration = AOUT_MIN_PREPARE_TIME;
        }
        else
        {
            vlc_ToWave(pwfe, &fmt);
            buffer_duration = AOUT_MAX_PREPARE_TIME * 10;
        }
529
    }
530
    else
531 532
    {
        hr = E_FAIL;
533
        goto error;
534
    }
535

536
    hr = IAudioClient_IsFormatSupported(sys->client, shared_mode,
Thomas Guillem's avatar
Thomas Guillem committed
537
                                        pwf, &pwf_closest);
538

539 540
    if (FAILED(hr))
    {
Thomas Guillem's avatar
Thomas Guillem committed
541 542 543 544 545 546
        if (pfmt->i_format == VLC_CODEC_DTS && b_hdmi)
        {
            msg_Warn(s, "cannot negotiate DTS at 768khz IEC958 rate (HDMI), "
                     "fallback to 48kHz (S/PDIF)");
            IAudioClient_Release(sys->client);
            free(sys);
547 548
            var_SetBool(s->obj.parent, "dtshd", false);
            return Start(s, pfmt, sid);
Thomas Guillem's avatar
Thomas Guillem committed
549
        }
550 551 552 553
        msg_Err(s, "cannot negotiate audio format (error 0x%lx)%s", hr,
                hr == AUDCLNT_E_UNSUPPORTED_FORMAT
                && fmt.i_format == VLC_CODEC_SPDIFL ?
                ": digital pass-through not supported" : "");
554 555 556 557 558
        goto error;
    }

    if (hr == S_FALSE)
    {
Thomas Guillem's avatar
Thomas Guillem committed
559 560
        assert(pwf_closest != NULL);
        if (vlc_FromWave(pwf_closest, &fmt))
561
        {
Thomas Guillem's avatar
Thomas Guillem committed
562
            CoTaskMemFree(pwf_closest);
563
            msg_Err(s, "unsupported audio format");
564
            hr = E_INVALIDARG;
565 566
            goto error;
        }
567
        shared_mode = AUDCLNT_SHAREMODE_SHARED;
568
        msg_Dbg(s, "modified format");
Thomas Guillem's avatar
Thomas Guillem committed
569
        pwf = pwf_closest;
570 571
    }
    else
Thomas Guillem's avatar
Thomas Guillem committed
572
        assert(pwf_closest == NULL);
573

574 575
    sys->chans_to_reorder = fmt.i_format != VLC_CODEC_SPDIFL ?
                            vlc_CheckWaveOrder(pwf, sys->chans_table) : 0;
576
    sys->format = fmt.i_format;
Thomas Guillem's avatar
Thomas Guillem committed
577
    sys->block_align = pwf->nBlockAlign;
578
    sys->rate = pwf->nSamplesPerSec;
579

580 581
    hr = IAudioClient_Initialize(sys->client, shared_mode, 0, buffer_duration,
                                 0, pwf, sid);
Thomas Guillem's avatar
Thomas Guillem committed
582
    CoTaskMemFree(pwf_closest);
583 584
    if (FAILED(hr))
    {
585
        msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
586 587 588 589 590 591
        goto error;
    }

    hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
    if (FAILED(hr))
    {
592
        msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
593 594
        goto error;
    }
595 596 597 598 599 600 601 602 603 604
    msg_Dbg(s, "buffer size    : %"PRIu32" frames", sys->frames);

    REFERENCE_TIME latT, defT, minT;
    if (SUCCEEDED(IAudioClient_GetStreamLatency(sys->client, &latT))
     && SUCCEEDED(IAudioClient_GetDevicePeriod(sys->client, &defT, &minT)))
    {
        msg_Dbg(s, "maximum latency: %"PRIu64"00 ns", latT);
        msg_Dbg(s, "default period : %"PRIu64"00 ns", defT);
        msg_Dbg(s, "minimum period : %"PRIu64"00 ns", minT);
    }
605

606
    CoTaskMemFree(pwf_mix);
607
    *pfmt = fmt;
608
    sys->written = 0;
609 610 611 612 613
    s->sys = sys;
    s->time_get = TimeGet;
    s->play = Play;
    s->pause = Pause;
    s->flush = Flush;
614
    return S_OK;
615
error:
616
    CoTaskMemFree(pwf_mix);
617 618
    if (sys->client != NULL)
        IAudioClient_Release(sys->client);
619
    free(sys);
620
    return hr;
621 622
}

623
static void Stop(aout_stream_t *s)
624
{
625
    aout_stream_sys_t *sys = s->sys;
626

627
    IAudioClient_Stop(sys->client); /* should not be needed */
628
    IAudioClient_Release(sys->client);
Hannes Domani's avatar
Hannes Domani committed
629 630

    free(sys);
631 632
}

633 634 635
vlc_module_begin()
    set_shortname("WASAPI")
    set_description(N_("Windows Audio Session API output"))
636
    set_capability("aout stream", 50)
637 638 639 640
    set_category(CAT_AUDIO)
    set_subcategory(SUBCAT_AUDIO_AOUT)
    set_callbacks(Start, Stop)
vlc_module_end()