Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/libs/faudio/src/FAudio_platform_win32.c
8726 views
1
/* FAudio - XAudio Reimplementation for FNA
2
*
3
* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team
4
*
5
* This software is provided 'as-is', without any express or implied warranty.
6
* In no event will the authors be held liable for any damages arising from
7
* the use of this software.
8
*
9
* Permission is granted to anyone to use this software for any purpose,
10
* including commercial applications, and to alter it and redistribute it
11
* freely, subject to the following restrictions:
12
*
13
* 1. The origin of this software must not be misrepresented; you must not
14
* claim that you wrote the original software. If you use this software in a
15
* product, an acknowledgment in the product documentation would be
16
* appreciated but is not required.
17
*
18
* 2. Altered source versions must be plainly marked as such, and must not be
19
* misrepresented as being the original software.
20
*
21
* 3. This notice may not be removed or altered from any source distribution.
22
*
23
* Ethan "flibitijibibo" Lee <[email protected]>
24
*
25
*/
26
27
#ifdef FAUDIO_WIN32_PLATFORM
28
29
#include <stddef.h>
30
31
#define COBJMACROS
32
#include <windows.h>
33
#include <mfidl.h>
34
#include <mfapi.h>
35
#include <mfreadwrite.h>
36
#include <propvarutil.h>
37
38
#include <initguid.h>
39
#include <audioclient.h>
40
#include <mmdeviceapi.h>
41
#include <devpkey.h>
42
43
#define _SPEAKER_POSITIONS_ /* Defined by SDK. */
44
#include "FAudio_internal.h"
45
46
static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 };
47
static IMMDeviceEnumerator *device_enumerator;
48
static HRESULT init_hr;
49
50
struct FAudioWin32PlatformData
51
{
52
IAudioClient *client;
53
HANDLE audioThread;
54
HANDLE stopEvent;
55
};
56
57
struct FAudioAudioClientThreadArgs
58
{
59
WAVEFORMATEXTENSIBLE format;
60
IAudioClient *client;
61
HANDLE events[2];
62
FAudio *audio;
63
UINT updateSize;
64
};
65
66
void FAudio_Log(char const *msg)
67
{
68
OutputDebugStringA(msg);
69
}
70
71
static HMODULE kernelbase = NULL;
72
static HRESULT (WINAPI *my_SetThreadDescription)(HANDLE, PCWSTR) = NULL;
73
74
static void FAudio_resolve_SetThreadDescription(void)
75
{
76
kernelbase = LoadLibraryA("kernelbase.dll");
77
if (!kernelbase)
78
return;
79
80
my_SetThreadDescription = (HRESULT (WINAPI *)(HANDLE, PCWSTR)) GetProcAddress(kernelbase, "SetThreadDescription");
81
if (!my_SetThreadDescription)
82
{
83
FreeLibrary(kernelbase);
84
kernelbase = NULL;
85
}
86
}
87
88
static void FAudio_set_thread_name(char const *name)
89
{
90
int ret;
91
WCHAR *nameW;
92
93
if (!my_SetThreadDescription)
94
return;
95
96
ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
97
98
nameW = FAudio_malloc(ret * sizeof(WCHAR));
99
if (!nameW)
100
return;
101
102
ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, nameW, ret);
103
if (ret)
104
my_SetThreadDescription(GetCurrentThread(), nameW);
105
106
FAudio_free(nameW);
107
}
108
109
static HRESULT FAudio_FillAudioClientBuffer(
110
struct FAudioAudioClientThreadArgs *args,
111
IAudioRenderClient *client,
112
UINT frames,
113
UINT padding
114
) {
115
HRESULT hr = S_OK;
116
BYTE *buffer;
117
118
while (padding + args->updateSize <= frames)
119
{
120
hr = IAudioRenderClient_GetBuffer(
121
client,
122
frames - padding,
123
&buffer
124
);
125
if (FAILED(hr)) return hr;
126
127
FAudio_zero(
128
buffer,
129
args->updateSize * args->format.Format.nBlockAlign
130
);
131
132
if (args->audio->active)
133
{
134
FAudio_INTERNAL_UpdateEngine(
135
args->audio,
136
(float*) buffer
137
);
138
}
139
140
hr = IAudioRenderClient_ReleaseBuffer(
141
client,
142
args->updateSize,
143
0
144
);
145
if (FAILED(hr)) return hr;
146
147
padding += args->updateSize;
148
}
149
150
return hr;
151
}
152
153
static DWORD WINAPI FAudio_AudioClientThread(void *user)
154
{
155
struct FAudioAudioClientThreadArgs *args = user;
156
IAudioRenderClient *render_client;
157
HRESULT hr = S_OK;
158
UINT frames, padding = 0;
159
160
FAudio_set_thread_name(__func__);
161
162
hr = IAudioClient_GetService(
163
args->client,
164
&IID_IAudioRenderClient,
165
(void **)&render_client
166
);
167
FAudio_assert(!FAILED(hr) && "Failed to get IAudioRenderClient service!");
168
169
hr = IAudioClient_GetBufferSize(args->client, &frames);
170
FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient buffer size!");
171
172
hr = FAudio_FillAudioClientBuffer(args, render_client, frames, 0);
173
FAudio_assert(!FAILED(hr) && "Failed to initialize IAudioClient buffer!");
174
175
hr = IAudioClient_Start(args->client);
176
FAudio_assert(!FAILED(hr) && "Failed to start IAudioClient!");
177
178
while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0)
179
{
180
hr = IAudioClient_GetCurrentPadding(args->client, &padding);
181
if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
182
{
183
/* Device was removed, just exit */
184
break;
185
}
186
FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!");
187
188
hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding);
189
FAudio_assert(!FAILED(hr) && "Failed to fill IAudioClient buffer!");
190
}
191
192
hr = IAudioClient_Stop(args->client);
193
FAudio_assert(!FAILED(hr) && "Failed to stop IAudioClient!");
194
195
IAudioRenderClient_Release(render_client);
196
FAudio_free(args);
197
return 0;
198
}
199
200
/* Sets `defaultDeviceIndex` to the default audio device index in
201
* `deviceCollection`.
202
* On failure, `defaultDeviceIndex` is not modified and the latest error is
203
* returned. */
204
static HRESULT FAudio_DefaultDeviceIndex(
205
IMMDeviceCollection *deviceCollection,
206
uint32_t* defaultDeviceIndex
207
) {
208
IMMDevice *device;
209
HRESULT hr;
210
uint32_t i, count;
211
WCHAR *default_guid;
212
WCHAR *device_guid;
213
214
/* Open the default device and get its GUID. */
215
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
216
device_enumerator,
217
eRender,
218
eConsole,
219
&device
220
);
221
if (FAILED(hr))
222
{
223
return hr;
224
}
225
hr = IMMDevice_GetId(device, &default_guid);
226
if (FAILED(hr))
227
{
228
IMMDevice_Release(device);
229
return hr;
230
}
231
232
/* Free the default device. */
233
IMMDevice_Release(device);
234
235
hr = IMMDeviceCollection_GetCount(deviceCollection, &count);
236
if (FAILED(hr))
237
{
238
CoTaskMemFree(default_guid);
239
return hr;
240
}
241
242
for (i = 0; i < count; i += 1)
243
{
244
/* Open the device and get its GUID. */
245
hr = IMMDeviceCollection_Item(deviceCollection, i, &device);
246
if (FAILED(hr)) {
247
CoTaskMemFree(default_guid);
248
return hr;
249
}
250
hr = IMMDevice_GetId(device, &device_guid);
251
if (FAILED(hr))
252
{
253
CoTaskMemFree(default_guid);
254
IMMDevice_Release(device);
255
return hr;
256
}
257
258
if (lstrcmpW(default_guid, device_guid) == 0)
259
{
260
/* Device found. */
261
CoTaskMemFree(default_guid);
262
CoTaskMemFree(device_guid);
263
IMMDevice_Release(device);
264
*defaultDeviceIndex = i;
265
return S_OK;
266
}
267
268
CoTaskMemFree(device_guid);
269
IMMDevice_Release(device);
270
}
271
272
/* This should probably never happen. Just in case, set
273
* `defaultDeviceIndex` to 0 and return S_OK. */
274
CoTaskMemFree(default_guid);
275
*defaultDeviceIndex = 0;
276
return S_OK;
277
}
278
279
/* Open `device`, corresponding to `deviceIndex`. `deviceIndex` 0 always
280
* corresponds to the default device. XAudio reorders the devices so that the
281
* default device is always at index 0, so we mimick this behavior here by
282
* swapping the devices at indexes 0 and `defaultDeviceIndex`.
283
*/
284
static HRESULT FAudio_OpenDevice(uint32_t deviceIndex, IMMDevice **device)
285
{
286
IMMDeviceCollection *deviceCollection;
287
HRESULT hr;
288
uint32_t defaultDeviceIndex;
289
uint32_t actualIndex;
290
291
*device = NULL;
292
293
hr = IMMDeviceEnumerator_EnumAudioEndpoints(
294
device_enumerator,
295
eRender,
296
DEVICE_STATE_ACTIVE,
297
&deviceCollection
298
);
299
if (FAILED(hr))
300
{
301
return hr;
302
}
303
304
/* Get the default device index. */
305
hr = FAudio_DefaultDeviceIndex(deviceCollection, &defaultDeviceIndex);
306
if (FAILED(hr))
307
{
308
IMMDeviceCollection_Release(deviceCollection);
309
return hr;
310
}
311
312
if (deviceIndex == 0) {
313
/* Default device. */
314
actualIndex = defaultDeviceIndex;
315
} else if (deviceIndex == defaultDeviceIndex) {
316
/* Open the device at index 0 instead of the "correct" one. */
317
actualIndex = 0;
318
} else {
319
/* Otherwise, just open the device. */
320
actualIndex = deviceIndex;
321
322
}
323
hr = IMMDeviceCollection_Item(deviceCollection, actualIndex, device);
324
if (FAILED(hr))
325
{
326
IMMDeviceCollection_Release(deviceCollection);
327
return hr;
328
}
329
330
IMMDeviceCollection_Release(deviceCollection);
331
332
return hr;
333
}
334
335
void FAudio_PlatformInit(
336
FAudio *audio,
337
uint32_t flags,
338
uint32_t deviceIndex,
339
FAudioWaveFormatExtensible *mixFormat,
340
uint32_t *updateSize,
341
void** platformDevice
342
) {
343
struct FAudioAudioClientThreadArgs *args;
344
struct FAudioWin32PlatformData *data;
345
REFERENCE_TIME duration;
346
IMMDevice *device = NULL;
347
HRESULT hr;
348
HANDLE audioEvent = NULL;
349
BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE);
350
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC)
351
BOOL has_neon = TRUE;
352
#elif defined(__arm__) || defined(_M_ARM)
353
BOOL has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);
354
#else
355
BOOL has_neon = FALSE;
356
#endif
357
FAudio_INTERNAL_InitSIMDFunctions(has_sse2, has_neon);
358
FAudio_resolve_SetThreadDescription();
359
360
FAudio_PlatformAddRef();
361
362
*platformDevice = NULL;
363
364
args = FAudio_malloc(sizeof(*args));
365
FAudio_assert(!!args && "Failed to allocate FAudio thread args!");
366
367
data = FAudio_malloc(sizeof(*data));
368
FAudio_assert(!!data && "Failed to allocate FAudio platform data!");
369
FAudio_zero(data, sizeof(*data));
370
371
args->format.Format.wFormatTag = mixFormat->Format.wFormatTag;
372
args->format.Format.nChannels = mixFormat->Format.nChannels;
373
args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec;
374
args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec;
375
args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign;
376
args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample;
377
args->format.Format.cbSize = mixFormat->Format.cbSize;
378
379
if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
380
{
381
args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample;
382
args->format.dwChannelMask = mixFormat->dwChannelMask;
383
FAudio_memcpy(
384
&args->format.SubFormat,
385
&mixFormat->SubFormat,
386
sizeof(GUID)
387
);
388
}
389
390
audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
391
FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!");
392
393
data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
394
FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!");
395
396
hr = FAudio_OpenDevice(deviceIndex, &device);
397
FAudio_assert(!FAILED(hr) && "Failed to get audio device!");
398
399
hr = IMMDevice_Activate(
400
device,
401
&IID_IAudioClient,
402
CLSCTX_ALL,
403
NULL,
404
(void **)&data->client
405
);
406
FAudio_assert(!FAILED(hr) && "Failed to create audio client!");
407
IMMDevice_Release(device);
408
409
if (flags & FAUDIO_1024_QUANTUM) duration = 213333;
410
else duration = 100000;
411
412
hr = IAudioClient_Initialize(
413
data->client,
414
AUDCLNT_SHAREMODE_SHARED,
415
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,
416
duration * 3,
417
0,
418
&args->format.Format,
419
&GUID_NULL
420
);
421
FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!");
422
423
hr = IAudioClient_SetEventHandle(data->client, audioEvent);
424
FAudio_assert(!FAILED(hr) && "Failed to set audio client event!");
425
426
mixFormat->Format.wFormatTag = args->format.Format.wFormatTag;
427
mixFormat->Format.nChannels = args->format.Format.nChannels;
428
mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec;
429
mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec;
430
mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign;
431
mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample;
432
433
if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
434
{
435
mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);
436
mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample;
437
mixFormat->dwChannelMask = args->format.dwChannelMask;
438
FAudio_memcpy(
439
&mixFormat->SubFormat,
440
&args->format.SubFormat,
441
sizeof(GUID)
442
);
443
}
444
else
445
{
446
mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx);
447
}
448
449
args->client = data->client;
450
args->events[0] = audioEvent;
451
args->events[1] = data->stopEvent;
452
args->audio = audio;
453
if (flags & FAUDIO_1024_QUANTUM) args->updateSize = (UINT)(args->format.Format.nSamplesPerSec / (1000.0 / (64.0 / 3.0)));
454
else args->updateSize = args->format.Format.nSamplesPerSec / 100;
455
456
data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL);
457
FAudio_assert(!!data->audioThread && "Failed to create audio client thread!");
458
459
*updateSize = args->updateSize;
460
*platformDevice = data;
461
return;
462
}
463
464
void FAudio_PlatformQuit(void* platformDevice)
465
{
466
struct FAudioWin32PlatformData *data = platformDevice;
467
468
SetEvent(data->stopEvent);
469
WaitForSingleObject(data->audioThread, INFINITE);
470
if (data->client) IAudioClient_Release(data->client);
471
if (kernelbase)
472
{
473
my_SetThreadDescription = NULL;
474
FreeLibrary(kernelbase);
475
kernelbase = NULL;
476
}
477
FAudio_PlatformRelease();
478
}
479
480
void FAudio_PlatformAddRef()
481
{
482
HRESULT hr;
483
EnterCriticalSection(&faudio_cs);
484
if (!device_enumerator)
485
{
486
init_hr = CoInitialize(NULL);
487
hr = CoCreateInstance(
488
&CLSID_MMDeviceEnumerator,
489
NULL,
490
CLSCTX_INPROC_SERVER,
491
&IID_IMMDeviceEnumerator,
492
(void**)&device_enumerator
493
);
494
FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!");
495
}
496
else IMMDeviceEnumerator_AddRef(device_enumerator);
497
LeaveCriticalSection(&faudio_cs);
498
}
499
500
void FAudio_PlatformRelease()
501
{
502
EnterCriticalSection(&faudio_cs);
503
if (!IMMDeviceEnumerator_Release(device_enumerator))
504
{
505
device_enumerator = NULL;
506
if (SUCCEEDED(init_hr)) CoUninitialize();
507
}
508
LeaveCriticalSection(&faudio_cs);
509
}
510
511
uint32_t FAudio_PlatformGetDeviceCount(void)
512
{
513
IMMDeviceCollection *device_collection;
514
uint32_t count;
515
HRESULT hr;
516
517
FAudio_PlatformAddRef();
518
519
hr = IMMDeviceEnumerator_EnumAudioEndpoints(
520
device_enumerator,
521
eRender,
522
DEVICE_STATE_ACTIVE,
523
&device_collection
524
);
525
if (FAILED(hr)) {
526
FAudio_PlatformRelease();
527
return 0;
528
}
529
530
hr = IMMDeviceCollection_GetCount(device_collection, &count);
531
if (FAILED(hr)) {
532
IMMDeviceCollection_Release(device_collection);
533
FAudio_PlatformRelease();
534
return 0;
535
}
536
537
IMMDeviceCollection_Release(device_collection);
538
539
FAudio_PlatformRelease();
540
541
return count;
542
}
543
544
uint32_t FAudio_PlatformGetDeviceDetails(
545
uint32_t index,
546
FAudioDeviceDetails *details
547
) {
548
WAVEFORMATEX *format, *obtained;
549
WAVEFORMATEXTENSIBLE *ext;
550
IAudioClient *client;
551
IMMDevice *device;
552
IPropertyStore* properties;
553
PROPVARIANT deviceName;
554
uint32_t count = 0;
555
uint32_t ret = 0;
556
HRESULT hr;
557
WCHAR *str;
558
GUID sub;
559
560
FAudio_memset(details, 0, sizeof(FAudioDeviceDetails));
561
562
FAudio_PlatformAddRef();
563
564
count = FAudio_PlatformGetDeviceCount();
565
if (index >= count)
566
{
567
FAudio_PlatformRelease();
568
return FAUDIO_E_INVALID_CALL;
569
}
570
571
if (FAILED(hr = FAudio_OpenDevice(index, &device)))
572
return hr;
573
574
if (index == 0)
575
{
576
details->Role = FAudioGlobalDefaultDevice;
577
}
578
else
579
{
580
details->Role = FAudioNotDefaultDevice;
581
}
582
583
/* Set the Device Display Name */
584
if (FAILED(hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &properties)))
585
{
586
IMMDevice_Release(device);
587
return hr;
588
}
589
if (FAILED(hr = IPropertyStore_GetValue(properties, (PROPERTYKEY *)&DEVPKEY_Device_FriendlyName, &deviceName)))
590
{
591
IPropertyStore_Release(properties);
592
IMMDevice_Release(device);
593
return hr;
594
}
595
lstrcpynW((LPWSTR)details->DisplayName, deviceName.pwszVal, ARRAYSIZE(details->DisplayName) - 1);
596
PropVariantClear(&deviceName);
597
IPropertyStore_Release(properties);
598
599
/* Set the Device ID */
600
if (FAILED(hr = IMMDevice_GetId(device, &str)))
601
{
602
IMMDevice_Release(device);
603
return hr;
604
}
605
lstrcpynW((LPWSTR)details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1);
606
CoTaskMemFree(str);
607
608
if (FAILED(hr = IMMDevice_Activate(device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&client)))
609
{
610
IMMDevice_Release(device);
611
return hr;
612
}
613
614
if (FAILED(hr = IAudioClient_GetMixFormat(client, &format)))
615
{
616
IAudioClient_Release(client);
617
IMMDevice_Release(device);
618
return hr;
619
}
620
621
if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
622
{
623
ext = (WAVEFORMATEXTENSIBLE *)format;
624
sub = ext->SubFormat;
625
FAudio_memcpy(
626
&ext->SubFormat,
627
&DATAFORMAT_SUBTYPE_PCM,
628
sizeof(GUID)
629
);
630
631
hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED, format, &obtained);
632
if (FAILED(hr))
633
{
634
ext->SubFormat = sub;
635
}
636
else if (obtained)
637
{
638
CoTaskMemFree(format);
639
format = obtained;
640
}
641
}
642
643
details->OutputFormat.Format.wFormatTag = format->wFormatTag;
644
details->OutputFormat.Format.nChannels = format->nChannels;
645
details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec;
646
details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec;
647
details->OutputFormat.Format.nBlockAlign = format->nBlockAlign;
648
details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample;
649
details->OutputFormat.Format.cbSize = format->cbSize;
650
651
if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
652
{
653
ext = (WAVEFORMATEXTENSIBLE *)format;
654
details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample;
655
details->OutputFormat.dwChannelMask = ext->dwChannelMask;
656
FAudio_memcpy(
657
&details->OutputFormat.SubFormat,
658
&ext->SubFormat,
659
sizeof(GUID)
660
);
661
}
662
else
663
{
664
details->OutputFormat.dwChannelMask = GetMask(format->nChannels);
665
}
666
667
CoTaskMemFree(format);
668
669
IAudioClient_Release(client);
670
671
IMMDevice_Release(device);
672
673
FAudio_PlatformRelease();
674
675
return ret;
676
}
677
678
FAudioMutex FAudio_PlatformCreateMutex(void)
679
{
680
CRITICAL_SECTION *cs;
681
682
cs = FAudio_malloc(sizeof(CRITICAL_SECTION));
683
if (!cs) return NULL;
684
685
InitializeCriticalSection(cs);
686
687
return cs;
688
}
689
690
void FAudio_PlatformLockMutex(FAudioMutex mutex)
691
{
692
if (mutex) EnterCriticalSection(mutex);
693
}
694
695
void FAudio_PlatformUnlockMutex(FAudioMutex mutex)
696
{
697
if (mutex) LeaveCriticalSection(mutex);
698
}
699
700
void FAudio_PlatformDestroyMutex(FAudioMutex mutex)
701
{
702
if (mutex) DeleteCriticalSection(mutex);
703
FAudio_free(mutex);
704
}
705
706
struct FAudioThreadArgs
707
{
708
FAudioThreadFunc func;
709
const char *name;
710
void* data;
711
};
712
713
static DWORD WINAPI FaudioThreadWrapper(void *user)
714
{
715
struct FAudioThreadArgs *args = user;
716
DWORD ret;
717
718
FAudio_set_thread_name(args->name);
719
ret = args->func(args->data);
720
721
FAudio_free(args);
722
return ret;
723
}
724
725
FAudioThread FAudio_PlatformCreateThread(
726
FAudioThreadFunc func,
727
const char *name,
728
void* data
729
) {
730
struct FAudioThreadArgs *args;
731
732
if (!(args = FAudio_malloc(sizeof(*args)))) return NULL;
733
args->func = func;
734
args->name = name;
735
args->data = data;
736
737
return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL);
738
}
739
740
void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval)
741
{
742
WaitForSingleObject(thread, INFINITE);
743
if (retval != NULL) GetExitCodeThread(thread, (DWORD *)retval);
744
}
745
746
void FAudio_PlatformThreadPriority(FAudioThreadPriority priority)
747
{
748
/* FIXME */
749
}
750
751
uint64_t FAudio_PlatformGetThreadID(void)
752
{
753
return GetCurrentThreadId();
754
}
755
756
void FAudio_sleep(uint32_t ms)
757
{
758
Sleep(ms);
759
}
760
761
uint32_t FAudio_timems()
762
{
763
return GetTickCount();
764
}
765
766
/* FAudio I/O */
767
768
static size_t FAUDIOCALL FAudio_FILE_read(
769
void *data,
770
void *dst,
771
size_t size,
772
size_t count
773
) {
774
if (!data) return 0;
775
return fread(dst, size, count, data);
776
}
777
778
static int64_t FAUDIOCALL FAudio_FILE_seek(
779
void *data,
780
int64_t offset,
781
int whence
782
) {
783
if (!data) return -1;
784
fseek(data, (long)offset, whence);
785
return ftell(data);
786
}
787
788
static int FAUDIOCALL FAudio_FILE_close(void *data)
789
{
790
if (!data) return 0;
791
fclose(data);
792
return 0;
793
}
794
795
FAudioIOStream* FAudio_fopen(const char *path)
796
{
797
FAudioIOStream *io;
798
errno_t err = -1;
799
800
io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));
801
if (!io) return NULL;
802
803
err = fopen_s((FILE**)&(io->data), path, "rb");
804
805
if (err != 0 || io->data == NULL)
806
{
807
FAudio_free(io);
808
return NULL;
809
}
810
811
io->read = FAudio_FILE_read;
812
io->seek = FAudio_FILE_seek;
813
io->close = FAudio_FILE_close;
814
io->lock = FAudio_PlatformCreateMutex();
815
816
return io;
817
}
818
819
struct FAudio_mem
820
{
821
char *mem;
822
int64_t len;
823
int64_t pos;
824
};
825
826
static size_t FAUDIOCALL FAudio_mem_read(
827
void *data,
828
void *dst,
829
size_t size,
830
size_t count
831
) {
832
struct FAudio_mem *io = data;
833
size_t len = size * count;
834
835
if (!data) return 0;
836
837
while (len && len > (UINT)(io->len - io->pos)) len -= size;
838
FAudio_memcpy(dst, io->mem + io->pos, len);
839
io->pos += len;
840
841
return len;
842
}
843
844
static int64_t FAUDIOCALL FAudio_mem_seek(
845
void *data,
846
int64_t offset,
847
int whence
848
) {
849
struct FAudio_mem *io = data;
850
if (!data) return -1;
851
852
if (whence == SEEK_SET)
853
{
854
if (io->len > offset) io->pos = offset;
855
else io->pos = io->len;
856
}
857
if (whence == SEEK_CUR)
858
{
859
if (io->len > io->pos + offset) io->pos += offset;
860
else io->pos = io->len;
861
}
862
if (whence == SEEK_END)
863
{
864
if (io->len > offset) io->pos = io->len - offset;
865
else io->pos = 0;
866
}
867
868
return io->pos;
869
}
870
871
static int FAUDIOCALL FAudio_mem_close(void *data)
872
{
873
if (!data) return 0;
874
FAudio_free(data);
875
return 0;
876
}
877
878
FAudioIOStream* FAudio_memopen(void *mem, int len)
879
{
880
struct FAudio_mem *data;
881
FAudioIOStream *io;
882
883
io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));
884
if (!io) return NULL;
885
886
data = FAudio_malloc(sizeof(struct FAudio_mem));
887
if (!data)
888
{
889
FAudio_free(io);
890
return NULL;
891
}
892
893
data->mem = mem;
894
data->len = len;
895
data->pos = 0;
896
897
io->data = data;
898
io->read = FAudio_mem_read;
899
io->seek = FAudio_mem_seek;
900
io->close = FAudio_mem_close;
901
io->lock = FAudio_PlatformCreateMutex();
902
return io;
903
}
904
905
uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset)
906
{
907
struct FAudio_mem *memio = io->data;
908
return (uint8_t *)memio->mem + offset;
909
}
910
911
void FAudio_close(FAudioIOStream *io)
912
{
913
io->close(io->data);
914
FAudio_PlatformDestroyMutex((FAudioMutex) io->lock);
915
FAudio_free(io);
916
}
917
918
/* XNA Song implementation over Win32 MF */
919
920
static FAudioWaveFormatEx activeSongFormat;
921
IMFSourceReader *activeSong;
922
static uint8_t *songBuffer;
923
static SIZE_T songBufferSize;
924
925
static float songVolume = 1.0f;
926
static FAudio *songAudio = NULL;
927
static FAudioMasteringVoice *songMaster = NULL;
928
929
static FAudioSourceVoice *songVoice = NULL;
930
static FAudioVoiceCallback callbacks;
931
932
/* Internal Functions */
933
934
static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext)
935
{
936
IMFMediaBuffer *media_buffer;
937
FAudioBuffer buffer;
938
IMFSample *sample;
939
HRESULT hr;
940
DWORD flags, buffer_size = 0;
941
BYTE *buffer_ptr;
942
943
LOG_FUNC_ENTER(songAudio);
944
945
FAudio_memset(&buffer, 0, sizeof(buffer));
946
947
hr = IMFSourceReader_ReadSample(
948
activeSong,
949
MF_SOURCE_READER_FIRST_AUDIO_STREAM,
950
0,
951
NULL,
952
&flags,
953
NULL,
954
&sample
955
);
956
FAudio_assert(!FAILED(hr) && "Failed to read audio sample!");
957
958
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
959
{
960
buffer.Flags = FAUDIO_END_OF_STREAM;
961
}
962
else
963
{
964
hr = IMFSample_ConvertToContiguousBuffer(
965
sample,
966
&media_buffer
967
);
968
FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!");
969
970
hr = IMFMediaBuffer_Lock(
971
media_buffer,
972
&buffer_ptr,
973
NULL,
974
&buffer_size
975
);
976
FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!");
977
978
if (songBufferSize < buffer_size)
979
{
980
songBufferSize = buffer_size;
981
songBuffer = FAudio_realloc(songBuffer, songBufferSize);
982
FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!");
983
}
984
FAudio_memcpy(songBuffer, buffer_ptr, buffer_size);
985
986
hr = IMFMediaBuffer_Unlock(media_buffer);
987
FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!");
988
989
IMFMediaBuffer_Release(media_buffer);
990
IMFSample_Release(sample);
991
}
992
993
if (buffer_size > 0)
994
{
995
buffer.AudioBytes = buffer_size;
996
buffer.pAudioData = songBuffer;
997
buffer.PlayBegin = 0;
998
buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign;
999
buffer.LoopBegin = 0;
1000
buffer.LoopLength = 0;
1001
buffer.LoopCount = 0;
1002
buffer.pContext = NULL;
1003
FAudioSourceVoice_SubmitSourceBuffer(
1004
songVoice,
1005
&buffer,
1006
NULL
1007
);
1008
}
1009
1010
LOG_FUNC_EXIT(songAudio);
1011
}
1012
1013
static void XNA_SongKill()
1014
{
1015
if (songVoice != NULL)
1016
{
1017
FAudioSourceVoice_Stop(songVoice, 0, 0);
1018
FAudioVoice_DestroyVoice(songVoice);
1019
songVoice = NULL;
1020
}
1021
if (activeSong)
1022
{
1023
IMFSourceReader_Release(activeSong);
1024
activeSong = NULL;
1025
}
1026
FAudio_free(songBuffer);
1027
songBuffer = NULL;
1028
songBufferSize = 0;
1029
}
1030
1031
/* "Public" API */
1032
1033
FAUDIOAPI void XNA_SongInit()
1034
{
1035
HRESULT hr;
1036
1037
hr = MFStartup(MF_VERSION, MFSTARTUP_FULL);
1038
FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!");
1039
1040
FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR);
1041
FAudio_CreateMasteringVoice(
1042
songAudio,
1043
&songMaster,
1044
FAUDIO_DEFAULT_CHANNELS,
1045
FAUDIO_DEFAULT_SAMPLERATE,
1046
0,
1047
0,
1048
NULL
1049
);
1050
}
1051
1052
FAUDIOAPI void XNA_SongQuit()
1053
{
1054
XNA_SongKill();
1055
FAudioVoice_DestroyVoice(songMaster);
1056
FAudio_Release(songAudio);
1057
MFShutdown();
1058
}
1059
1060
FAUDIOAPI float XNA_PlaySong(const char *name)
1061
{
1062
IMFAttributes *attributes = NULL;
1063
IMFMediaType *media_type = NULL;
1064
UINT32 channels, samplerate;
1065
INT64 duration;
1066
PROPVARIANT var;
1067
HRESULT hr;
1068
WCHAR filename_w[MAX_PATH];
1069
1070
LOG_FUNC_ENTER(songAudio);
1071
LOG_INFO(songAudio, "name %s\n", name);
1072
XNA_SongKill();
1073
1074
MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH);
1075
1076
hr = MFCreateAttributes(&attributes, 1);
1077
FAudio_assert(!FAILED(hr) && "Failed to create attributes!");
1078
hr = MFCreateSourceReaderFromURL(
1079
filename_w,
1080
attributes,
1081
&activeSong
1082
);
1083
FAudio_assert(!FAILED(hr) && "Failed to create source reader!");
1084
IMFAttributes_Release(attributes);
1085
1086
hr = MFCreateMediaType(&media_type);
1087
FAudio_assert(!FAILED(hr) && "Failed to create media type!");
1088
hr = IMFMediaType_SetGUID(
1089
media_type,
1090
&MF_MT_MAJOR_TYPE,
1091
&MFMediaType_Audio
1092
);
1093
FAudio_assert(!FAILED(hr) && "Failed to set major type!");
1094
hr = IMFMediaType_SetGUID(
1095
media_type,
1096
&MF_MT_SUBTYPE,
1097
&MFAudioFormat_Float
1098
);
1099
FAudio_assert(!FAILED(hr) && "Failed to set sub type!");
1100
hr = IMFSourceReader_SetCurrentMediaType(
1101
activeSong,
1102
MF_SOURCE_READER_FIRST_AUDIO_STREAM,
1103
NULL,
1104
media_type
1105
);
1106
FAudio_assert(!FAILED(hr) && "Failed to set source media type!");
1107
hr = IMFSourceReader_SetStreamSelection(
1108
activeSong,
1109
MF_SOURCE_READER_FIRST_AUDIO_STREAM,
1110
TRUE
1111
);
1112
FAudio_assert(!FAILED(hr) && "Failed to select source stream!");
1113
IMFMediaType_Release(media_type);
1114
1115
hr = IMFSourceReader_GetCurrentMediaType(
1116
activeSong,
1117
MF_SOURCE_READER_FIRST_AUDIO_STREAM,
1118
&media_type
1119
);
1120
FAudio_assert(!FAILED(hr) && "Failed to get current media type!");
1121
hr = IMFMediaType_GetUINT32(
1122
media_type,
1123
&MF_MT_AUDIO_NUM_CHANNELS,
1124
&channels
1125
);
1126
FAudio_assert(!FAILED(hr) && "Failed to get channel count!");
1127
hr = IMFMediaType_GetUINT32(
1128
media_type,
1129
&MF_MT_AUDIO_SAMPLES_PER_SECOND,
1130
&samplerate
1131
);
1132
FAudio_assert(!FAILED(hr) && "Failed to get sample rate!");
1133
IMFMediaType_Release(media_type);
1134
1135
hr = IMFSourceReader_GetPresentationAttribute(
1136
activeSong,
1137
MF_SOURCE_READER_MEDIASOURCE,
1138
&MF_PD_DURATION,
1139
&var
1140
);
1141
FAudio_assert(!FAILED(hr) && "Failed to get song duration!");
1142
hr = PropVariantToInt64(&var, &duration);
1143
FAudio_assert(!FAILED(hr) && "Failed to get song duration!");
1144
PropVariantClear(&var);
1145
1146
activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;
1147
activeSongFormat.nChannels = channels;
1148
activeSongFormat.nSamplesPerSec = samplerate;
1149
activeSongFormat.wBitsPerSample = sizeof(float) * 8;
1150
activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8;
1151
activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign;
1152
activeSongFormat.cbSize = 0;
1153
1154
/* Init voice */
1155
FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback));
1156
callbacks.OnBufferEnd = XNA_SongSubmitBuffer;
1157
FAudio_CreateSourceVoice(
1158
songAudio,
1159
&songVoice,
1160
&activeSongFormat,
1161
0,
1162
1.0f, /* No pitch shifting here! */
1163
&callbacks,
1164
NULL,
1165
NULL
1166
);
1167
FAudioVoice_SetVolume(songVoice, songVolume, 0);
1168
XNA_SongSubmitBuffer(NULL, NULL);
1169
1170
/* Finally. */
1171
FAudioSourceVoice_Start(songVoice, 0, 0);
1172
LOG_FUNC_EXIT(songAudio);
1173
return (float)(duration / 10000000.);
1174
}
1175
1176
FAUDIOAPI void XNA_PauseSong()
1177
{
1178
if (songVoice == NULL)
1179
{
1180
return;
1181
}
1182
FAudioSourceVoice_Stop(songVoice, 0, 0);
1183
}
1184
1185
FAUDIOAPI void XNA_ResumeSong()
1186
{
1187
if (songVoice == NULL)
1188
{
1189
return;
1190
}
1191
FAudioSourceVoice_Start(songVoice, 0, 0);
1192
}
1193
1194
FAUDIOAPI void XNA_StopSong()
1195
{
1196
XNA_SongKill();
1197
}
1198
1199
FAUDIOAPI void XNA_SetSongVolume(float volume)
1200
{
1201
songVolume = volume;
1202
if (songVoice != NULL)
1203
{
1204
FAudioVoice_SetVolume(songVoice, songVolume, 0);
1205
}
1206
}
1207
1208
FAUDIOAPI uint32_t XNA_GetSongEnded()
1209
{
1210
FAudioVoiceState state;
1211
if (songVoice == NULL || activeSong == NULL)
1212
{
1213
return 1;
1214
}
1215
FAudioSourceVoice_GetState(songVoice, &state, 0);
1216
return state.BuffersQueued == 0 && state.SamplesPlayed == 0;
1217
}
1218
1219
FAUDIOAPI void XNA_EnableVisualization(uint32_t enable)
1220
{
1221
/* TODO: Enable/Disable FAPO effect */
1222
}
1223
1224
FAUDIOAPI uint32_t XNA_VisualizationEnabled()
1225
{
1226
/* TODO: Query FAPO effect enabled */
1227
return 0;
1228
}
1229
1230
FAUDIOAPI void XNA_GetSongVisualizationData(
1231
float *frequencies,
1232
float *samples,
1233
uint32_t count
1234
) {
1235
/* TODO: Visualization FAPO that reads in Song samples, FFT analysis */
1236
}
1237
1238
#else
1239
1240
extern int this_tu_is_empty;
1241
1242
#endif /* FAUDIO_WIN32_PLATFORM */
1243
1244