Path: blob/master/libs/faudio/src/FAudio_platform_win32.c
8726 views
/* FAudio - XAudio Reimplementation for FNA1*2* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team3*4* This software is provided 'as-is', without any express or implied warranty.5* In no event will the authors be held liable for any damages arising from6* the use of this software.7*8* Permission is granted to anyone to use this software for any purpose,9* including commercial applications, and to alter it and redistribute it10* freely, subject to the following restrictions:11*12* 1. The origin of this software must not be misrepresented; you must not13* claim that you wrote the original software. If you use this software in a14* product, an acknowledgment in the product documentation would be15* appreciated but is not required.16*17* 2. Altered source versions must be plainly marked as such, and must not be18* misrepresented as being the original software.19*20* 3. This notice may not be removed or altered from any source distribution.21*22* Ethan "flibitijibibo" Lee <[email protected]>23*24*/2526#ifdef FAUDIO_WIN32_PLATFORM2728#include <stddef.h>2930#define COBJMACROS31#include <windows.h>32#include <mfidl.h>33#include <mfapi.h>34#include <mfreadwrite.h>35#include <propvarutil.h>3637#include <initguid.h>38#include <audioclient.h>39#include <mmdeviceapi.h>40#include <devpkey.h>4142#define _SPEAKER_POSITIONS_ /* Defined by SDK. */43#include "FAudio_internal.h"4445static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 };46static IMMDeviceEnumerator *device_enumerator;47static HRESULT init_hr;4849struct FAudioWin32PlatformData50{51IAudioClient *client;52HANDLE audioThread;53HANDLE stopEvent;54};5556struct FAudioAudioClientThreadArgs57{58WAVEFORMATEXTENSIBLE format;59IAudioClient *client;60HANDLE events[2];61FAudio *audio;62UINT updateSize;63};6465void FAudio_Log(char const *msg)66{67OutputDebugStringA(msg);68}6970static HMODULE kernelbase = NULL;71static HRESULT (WINAPI *my_SetThreadDescription)(HANDLE, PCWSTR) = NULL;7273static void FAudio_resolve_SetThreadDescription(void)74{75kernelbase = LoadLibraryA("kernelbase.dll");76if (!kernelbase)77return;7879my_SetThreadDescription = (HRESULT (WINAPI *)(HANDLE, PCWSTR)) GetProcAddress(kernelbase, "SetThreadDescription");80if (!my_SetThreadDescription)81{82FreeLibrary(kernelbase);83kernelbase = NULL;84}85}8687static void FAudio_set_thread_name(char const *name)88{89int ret;90WCHAR *nameW;9192if (!my_SetThreadDescription)93return;9495ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);9697nameW = FAudio_malloc(ret * sizeof(WCHAR));98if (!nameW)99return;100101ret = MultiByteToWideChar(CP_UTF8, 0, name, -1, nameW, ret);102if (ret)103my_SetThreadDescription(GetCurrentThread(), nameW);104105FAudio_free(nameW);106}107108static HRESULT FAudio_FillAudioClientBuffer(109struct FAudioAudioClientThreadArgs *args,110IAudioRenderClient *client,111UINT frames,112UINT padding113) {114HRESULT hr = S_OK;115BYTE *buffer;116117while (padding + args->updateSize <= frames)118{119hr = IAudioRenderClient_GetBuffer(120client,121frames - padding,122&buffer123);124if (FAILED(hr)) return hr;125126FAudio_zero(127buffer,128args->updateSize * args->format.Format.nBlockAlign129);130131if (args->audio->active)132{133FAudio_INTERNAL_UpdateEngine(134args->audio,135(float*) buffer136);137}138139hr = IAudioRenderClient_ReleaseBuffer(140client,141args->updateSize,1420143);144if (FAILED(hr)) return hr;145146padding += args->updateSize;147}148149return hr;150}151152static DWORD WINAPI FAudio_AudioClientThread(void *user)153{154struct FAudioAudioClientThreadArgs *args = user;155IAudioRenderClient *render_client;156HRESULT hr = S_OK;157UINT frames, padding = 0;158159FAudio_set_thread_name(__func__);160161hr = IAudioClient_GetService(162args->client,163&IID_IAudioRenderClient,164(void **)&render_client165);166FAudio_assert(!FAILED(hr) && "Failed to get IAudioRenderClient service!");167168hr = IAudioClient_GetBufferSize(args->client, &frames);169FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient buffer size!");170171hr = FAudio_FillAudioClientBuffer(args, render_client, frames, 0);172FAudio_assert(!FAILED(hr) && "Failed to initialize IAudioClient buffer!");173174hr = IAudioClient_Start(args->client);175FAudio_assert(!FAILED(hr) && "Failed to start IAudioClient!");176177while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0)178{179hr = IAudioClient_GetCurrentPadding(args->client, &padding);180if (hr == AUDCLNT_E_DEVICE_INVALIDATED)181{182/* Device was removed, just exit */183break;184}185FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!");186187hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding);188FAudio_assert(!FAILED(hr) && "Failed to fill IAudioClient buffer!");189}190191hr = IAudioClient_Stop(args->client);192FAudio_assert(!FAILED(hr) && "Failed to stop IAudioClient!");193194IAudioRenderClient_Release(render_client);195FAudio_free(args);196return 0;197}198199/* Sets `defaultDeviceIndex` to the default audio device index in200* `deviceCollection`.201* On failure, `defaultDeviceIndex` is not modified and the latest error is202* returned. */203static HRESULT FAudio_DefaultDeviceIndex(204IMMDeviceCollection *deviceCollection,205uint32_t* defaultDeviceIndex206) {207IMMDevice *device;208HRESULT hr;209uint32_t i, count;210WCHAR *default_guid;211WCHAR *device_guid;212213/* Open the default device and get its GUID. */214hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(215device_enumerator,216eRender,217eConsole,218&device219);220if (FAILED(hr))221{222return hr;223}224hr = IMMDevice_GetId(device, &default_guid);225if (FAILED(hr))226{227IMMDevice_Release(device);228return hr;229}230231/* Free the default device. */232IMMDevice_Release(device);233234hr = IMMDeviceCollection_GetCount(deviceCollection, &count);235if (FAILED(hr))236{237CoTaskMemFree(default_guid);238return hr;239}240241for (i = 0; i < count; i += 1)242{243/* Open the device and get its GUID. */244hr = IMMDeviceCollection_Item(deviceCollection, i, &device);245if (FAILED(hr)) {246CoTaskMemFree(default_guid);247return hr;248}249hr = IMMDevice_GetId(device, &device_guid);250if (FAILED(hr))251{252CoTaskMemFree(default_guid);253IMMDevice_Release(device);254return hr;255}256257if (lstrcmpW(default_guid, device_guid) == 0)258{259/* Device found. */260CoTaskMemFree(default_guid);261CoTaskMemFree(device_guid);262IMMDevice_Release(device);263*defaultDeviceIndex = i;264return S_OK;265}266267CoTaskMemFree(device_guid);268IMMDevice_Release(device);269}270271/* This should probably never happen. Just in case, set272* `defaultDeviceIndex` to 0 and return S_OK. */273CoTaskMemFree(default_guid);274*defaultDeviceIndex = 0;275return S_OK;276}277278/* Open `device`, corresponding to `deviceIndex`. `deviceIndex` 0 always279* corresponds to the default device. XAudio reorders the devices so that the280* default device is always at index 0, so we mimick this behavior here by281* swapping the devices at indexes 0 and `defaultDeviceIndex`.282*/283static HRESULT FAudio_OpenDevice(uint32_t deviceIndex, IMMDevice **device)284{285IMMDeviceCollection *deviceCollection;286HRESULT hr;287uint32_t defaultDeviceIndex;288uint32_t actualIndex;289290*device = NULL;291292hr = IMMDeviceEnumerator_EnumAudioEndpoints(293device_enumerator,294eRender,295DEVICE_STATE_ACTIVE,296&deviceCollection297);298if (FAILED(hr))299{300return hr;301}302303/* Get the default device index. */304hr = FAudio_DefaultDeviceIndex(deviceCollection, &defaultDeviceIndex);305if (FAILED(hr))306{307IMMDeviceCollection_Release(deviceCollection);308return hr;309}310311if (deviceIndex == 0) {312/* Default device. */313actualIndex = defaultDeviceIndex;314} else if (deviceIndex == defaultDeviceIndex) {315/* Open the device at index 0 instead of the "correct" one. */316actualIndex = 0;317} else {318/* Otherwise, just open the device. */319actualIndex = deviceIndex;320321}322hr = IMMDeviceCollection_Item(deviceCollection, actualIndex, device);323if (FAILED(hr))324{325IMMDeviceCollection_Release(deviceCollection);326return hr;327}328329IMMDeviceCollection_Release(deviceCollection);330331return hr;332}333334void FAudio_PlatformInit(335FAudio *audio,336uint32_t flags,337uint32_t deviceIndex,338FAudioWaveFormatExtensible *mixFormat,339uint32_t *updateSize,340void** platformDevice341) {342struct FAudioAudioClientThreadArgs *args;343struct FAudioWin32PlatformData *data;344REFERENCE_TIME duration;345IMMDevice *device = NULL;346HRESULT hr;347HANDLE audioEvent = NULL;348BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE);349#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC)350BOOL has_neon = TRUE;351#elif defined(__arm__) || defined(_M_ARM)352BOOL has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);353#else354BOOL has_neon = FALSE;355#endif356FAudio_INTERNAL_InitSIMDFunctions(has_sse2, has_neon);357FAudio_resolve_SetThreadDescription();358359FAudio_PlatformAddRef();360361*platformDevice = NULL;362363args = FAudio_malloc(sizeof(*args));364FAudio_assert(!!args && "Failed to allocate FAudio thread args!");365366data = FAudio_malloc(sizeof(*data));367FAudio_assert(!!data && "Failed to allocate FAudio platform data!");368FAudio_zero(data, sizeof(*data));369370args->format.Format.wFormatTag = mixFormat->Format.wFormatTag;371args->format.Format.nChannels = mixFormat->Format.nChannels;372args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec;373args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec;374args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign;375args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample;376args->format.Format.cbSize = mixFormat->Format.cbSize;377378if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)379{380args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample;381args->format.dwChannelMask = mixFormat->dwChannelMask;382FAudio_memcpy(383&args->format.SubFormat,384&mixFormat->SubFormat,385sizeof(GUID)386);387}388389audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL);390FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!");391392data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);393FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!");394395hr = FAudio_OpenDevice(deviceIndex, &device);396FAudio_assert(!FAILED(hr) && "Failed to get audio device!");397398hr = IMMDevice_Activate(399device,400&IID_IAudioClient,401CLSCTX_ALL,402NULL,403(void **)&data->client404);405FAudio_assert(!FAILED(hr) && "Failed to create audio client!");406IMMDevice_Release(device);407408if (flags & FAUDIO_1024_QUANTUM) duration = 213333;409else duration = 100000;410411hr = IAudioClient_Initialize(412data->client,413AUDCLNT_SHAREMODE_SHARED,414AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,415duration * 3,4160,417&args->format.Format,418&GUID_NULL419);420FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!");421422hr = IAudioClient_SetEventHandle(data->client, audioEvent);423FAudio_assert(!FAILED(hr) && "Failed to set audio client event!");424425mixFormat->Format.wFormatTag = args->format.Format.wFormatTag;426mixFormat->Format.nChannels = args->format.Format.nChannels;427mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec;428mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec;429mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign;430mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample;431432if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)433{434mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);435mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample;436mixFormat->dwChannelMask = args->format.dwChannelMask;437FAudio_memcpy(438&mixFormat->SubFormat,439&args->format.SubFormat,440sizeof(GUID)441);442}443else444{445mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx);446}447448args->client = data->client;449args->events[0] = audioEvent;450args->events[1] = data->stopEvent;451args->audio = audio;452if (flags & FAUDIO_1024_QUANTUM) args->updateSize = (UINT)(args->format.Format.nSamplesPerSec / (1000.0 / (64.0 / 3.0)));453else args->updateSize = args->format.Format.nSamplesPerSec / 100;454455data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL);456FAudio_assert(!!data->audioThread && "Failed to create audio client thread!");457458*updateSize = args->updateSize;459*platformDevice = data;460return;461}462463void FAudio_PlatformQuit(void* platformDevice)464{465struct FAudioWin32PlatformData *data = platformDevice;466467SetEvent(data->stopEvent);468WaitForSingleObject(data->audioThread, INFINITE);469if (data->client) IAudioClient_Release(data->client);470if (kernelbase)471{472my_SetThreadDescription = NULL;473FreeLibrary(kernelbase);474kernelbase = NULL;475}476FAudio_PlatformRelease();477}478479void FAudio_PlatformAddRef()480{481HRESULT hr;482EnterCriticalSection(&faudio_cs);483if (!device_enumerator)484{485init_hr = CoInitialize(NULL);486hr = CoCreateInstance(487&CLSID_MMDeviceEnumerator,488NULL,489CLSCTX_INPROC_SERVER,490&IID_IMMDeviceEnumerator,491(void**)&device_enumerator492);493FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!");494}495else IMMDeviceEnumerator_AddRef(device_enumerator);496LeaveCriticalSection(&faudio_cs);497}498499void FAudio_PlatformRelease()500{501EnterCriticalSection(&faudio_cs);502if (!IMMDeviceEnumerator_Release(device_enumerator))503{504device_enumerator = NULL;505if (SUCCEEDED(init_hr)) CoUninitialize();506}507LeaveCriticalSection(&faudio_cs);508}509510uint32_t FAudio_PlatformGetDeviceCount(void)511{512IMMDeviceCollection *device_collection;513uint32_t count;514HRESULT hr;515516FAudio_PlatformAddRef();517518hr = IMMDeviceEnumerator_EnumAudioEndpoints(519device_enumerator,520eRender,521DEVICE_STATE_ACTIVE,522&device_collection523);524if (FAILED(hr)) {525FAudio_PlatformRelease();526return 0;527}528529hr = IMMDeviceCollection_GetCount(device_collection, &count);530if (FAILED(hr)) {531IMMDeviceCollection_Release(device_collection);532FAudio_PlatformRelease();533return 0;534}535536IMMDeviceCollection_Release(device_collection);537538FAudio_PlatformRelease();539540return count;541}542543uint32_t FAudio_PlatformGetDeviceDetails(544uint32_t index,545FAudioDeviceDetails *details546) {547WAVEFORMATEX *format, *obtained;548WAVEFORMATEXTENSIBLE *ext;549IAudioClient *client;550IMMDevice *device;551IPropertyStore* properties;552PROPVARIANT deviceName;553uint32_t count = 0;554uint32_t ret = 0;555HRESULT hr;556WCHAR *str;557GUID sub;558559FAudio_memset(details, 0, sizeof(FAudioDeviceDetails));560561FAudio_PlatformAddRef();562563count = FAudio_PlatformGetDeviceCount();564if (index >= count)565{566FAudio_PlatformRelease();567return FAUDIO_E_INVALID_CALL;568}569570if (FAILED(hr = FAudio_OpenDevice(index, &device)))571return hr;572573if (index == 0)574{575details->Role = FAudioGlobalDefaultDevice;576}577else578{579details->Role = FAudioNotDefaultDevice;580}581582/* Set the Device Display Name */583if (FAILED(hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &properties)))584{585IMMDevice_Release(device);586return hr;587}588if (FAILED(hr = IPropertyStore_GetValue(properties, (PROPERTYKEY *)&DEVPKEY_Device_FriendlyName, &deviceName)))589{590IPropertyStore_Release(properties);591IMMDevice_Release(device);592return hr;593}594lstrcpynW((LPWSTR)details->DisplayName, deviceName.pwszVal, ARRAYSIZE(details->DisplayName) - 1);595PropVariantClear(&deviceName);596IPropertyStore_Release(properties);597598/* Set the Device ID */599if (FAILED(hr = IMMDevice_GetId(device, &str)))600{601IMMDevice_Release(device);602return hr;603}604lstrcpynW((LPWSTR)details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1);605CoTaskMemFree(str);606607if (FAILED(hr = IMMDevice_Activate(device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&client)))608{609IMMDevice_Release(device);610return hr;611}612613if (FAILED(hr = IAudioClient_GetMixFormat(client, &format)))614{615IAudioClient_Release(client);616IMMDevice_Release(device);617return hr;618}619620if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)621{622ext = (WAVEFORMATEXTENSIBLE *)format;623sub = ext->SubFormat;624FAudio_memcpy(625&ext->SubFormat,626&DATAFORMAT_SUBTYPE_PCM,627sizeof(GUID)628);629630hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED, format, &obtained);631if (FAILED(hr))632{633ext->SubFormat = sub;634}635else if (obtained)636{637CoTaskMemFree(format);638format = obtained;639}640}641642details->OutputFormat.Format.wFormatTag = format->wFormatTag;643details->OutputFormat.Format.nChannels = format->nChannels;644details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec;645details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec;646details->OutputFormat.Format.nBlockAlign = format->nBlockAlign;647details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample;648details->OutputFormat.Format.cbSize = format->cbSize;649650if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)651{652ext = (WAVEFORMATEXTENSIBLE *)format;653details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample;654details->OutputFormat.dwChannelMask = ext->dwChannelMask;655FAudio_memcpy(656&details->OutputFormat.SubFormat,657&ext->SubFormat,658sizeof(GUID)659);660}661else662{663details->OutputFormat.dwChannelMask = GetMask(format->nChannels);664}665666CoTaskMemFree(format);667668IAudioClient_Release(client);669670IMMDevice_Release(device);671672FAudio_PlatformRelease();673674return ret;675}676677FAudioMutex FAudio_PlatformCreateMutex(void)678{679CRITICAL_SECTION *cs;680681cs = FAudio_malloc(sizeof(CRITICAL_SECTION));682if (!cs) return NULL;683684InitializeCriticalSection(cs);685686return cs;687}688689void FAudio_PlatformLockMutex(FAudioMutex mutex)690{691if (mutex) EnterCriticalSection(mutex);692}693694void FAudio_PlatformUnlockMutex(FAudioMutex mutex)695{696if (mutex) LeaveCriticalSection(mutex);697}698699void FAudio_PlatformDestroyMutex(FAudioMutex mutex)700{701if (mutex) DeleteCriticalSection(mutex);702FAudio_free(mutex);703}704705struct FAudioThreadArgs706{707FAudioThreadFunc func;708const char *name;709void* data;710};711712static DWORD WINAPI FaudioThreadWrapper(void *user)713{714struct FAudioThreadArgs *args = user;715DWORD ret;716717FAudio_set_thread_name(args->name);718ret = args->func(args->data);719720FAudio_free(args);721return ret;722}723724FAudioThread FAudio_PlatformCreateThread(725FAudioThreadFunc func,726const char *name,727void* data728) {729struct FAudioThreadArgs *args;730731if (!(args = FAudio_malloc(sizeof(*args)))) return NULL;732args->func = func;733args->name = name;734args->data = data;735736return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL);737}738739void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval)740{741WaitForSingleObject(thread, INFINITE);742if (retval != NULL) GetExitCodeThread(thread, (DWORD *)retval);743}744745void FAudio_PlatformThreadPriority(FAudioThreadPriority priority)746{747/* FIXME */748}749750uint64_t FAudio_PlatformGetThreadID(void)751{752return GetCurrentThreadId();753}754755void FAudio_sleep(uint32_t ms)756{757Sleep(ms);758}759760uint32_t FAudio_timems()761{762return GetTickCount();763}764765/* FAudio I/O */766767static size_t FAUDIOCALL FAudio_FILE_read(768void *data,769void *dst,770size_t size,771size_t count772) {773if (!data) return 0;774return fread(dst, size, count, data);775}776777static int64_t FAUDIOCALL FAudio_FILE_seek(778void *data,779int64_t offset,780int whence781) {782if (!data) return -1;783fseek(data, (long)offset, whence);784return ftell(data);785}786787static int FAUDIOCALL FAudio_FILE_close(void *data)788{789if (!data) return 0;790fclose(data);791return 0;792}793794FAudioIOStream* FAudio_fopen(const char *path)795{796FAudioIOStream *io;797errno_t err = -1;798799io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));800if (!io) return NULL;801802err = fopen_s((FILE**)&(io->data), path, "rb");803804if (err != 0 || io->data == NULL)805{806FAudio_free(io);807return NULL;808}809810io->read = FAudio_FILE_read;811io->seek = FAudio_FILE_seek;812io->close = FAudio_FILE_close;813io->lock = FAudio_PlatformCreateMutex();814815return io;816}817818struct FAudio_mem819{820char *mem;821int64_t len;822int64_t pos;823};824825static size_t FAUDIOCALL FAudio_mem_read(826void *data,827void *dst,828size_t size,829size_t count830) {831struct FAudio_mem *io = data;832size_t len = size * count;833834if (!data) return 0;835836while (len && len > (UINT)(io->len - io->pos)) len -= size;837FAudio_memcpy(dst, io->mem + io->pos, len);838io->pos += len;839840return len;841}842843static int64_t FAUDIOCALL FAudio_mem_seek(844void *data,845int64_t offset,846int whence847) {848struct FAudio_mem *io = data;849if (!data) return -1;850851if (whence == SEEK_SET)852{853if (io->len > offset) io->pos = offset;854else io->pos = io->len;855}856if (whence == SEEK_CUR)857{858if (io->len > io->pos + offset) io->pos += offset;859else io->pos = io->len;860}861if (whence == SEEK_END)862{863if (io->len > offset) io->pos = io->len - offset;864else io->pos = 0;865}866867return io->pos;868}869870static int FAUDIOCALL FAudio_mem_close(void *data)871{872if (!data) return 0;873FAudio_free(data);874return 0;875}876877FAudioIOStream* FAudio_memopen(void *mem, int len)878{879struct FAudio_mem *data;880FAudioIOStream *io;881882io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));883if (!io) return NULL;884885data = FAudio_malloc(sizeof(struct FAudio_mem));886if (!data)887{888FAudio_free(io);889return NULL;890}891892data->mem = mem;893data->len = len;894data->pos = 0;895896io->data = data;897io->read = FAudio_mem_read;898io->seek = FAudio_mem_seek;899io->close = FAudio_mem_close;900io->lock = FAudio_PlatformCreateMutex();901return io;902}903904uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset)905{906struct FAudio_mem *memio = io->data;907return (uint8_t *)memio->mem + offset;908}909910void FAudio_close(FAudioIOStream *io)911{912io->close(io->data);913FAudio_PlatformDestroyMutex((FAudioMutex) io->lock);914FAudio_free(io);915}916917/* XNA Song implementation over Win32 MF */918919static FAudioWaveFormatEx activeSongFormat;920IMFSourceReader *activeSong;921static uint8_t *songBuffer;922static SIZE_T songBufferSize;923924static float songVolume = 1.0f;925static FAudio *songAudio = NULL;926static FAudioMasteringVoice *songMaster = NULL;927928static FAudioSourceVoice *songVoice = NULL;929static FAudioVoiceCallback callbacks;930931/* Internal Functions */932933static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext)934{935IMFMediaBuffer *media_buffer;936FAudioBuffer buffer;937IMFSample *sample;938HRESULT hr;939DWORD flags, buffer_size = 0;940BYTE *buffer_ptr;941942LOG_FUNC_ENTER(songAudio);943944FAudio_memset(&buffer, 0, sizeof(buffer));945946hr = IMFSourceReader_ReadSample(947activeSong,948MF_SOURCE_READER_FIRST_AUDIO_STREAM,9490,950NULL,951&flags,952NULL,953&sample954);955FAudio_assert(!FAILED(hr) && "Failed to read audio sample!");956957if (flags & MF_SOURCE_READERF_ENDOFSTREAM)958{959buffer.Flags = FAUDIO_END_OF_STREAM;960}961else962{963hr = IMFSample_ConvertToContiguousBuffer(964sample,965&media_buffer966);967FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!");968969hr = IMFMediaBuffer_Lock(970media_buffer,971&buffer_ptr,972NULL,973&buffer_size974);975FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!");976977if (songBufferSize < buffer_size)978{979songBufferSize = buffer_size;980songBuffer = FAudio_realloc(songBuffer, songBufferSize);981FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!");982}983FAudio_memcpy(songBuffer, buffer_ptr, buffer_size);984985hr = IMFMediaBuffer_Unlock(media_buffer);986FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!");987988IMFMediaBuffer_Release(media_buffer);989IMFSample_Release(sample);990}991992if (buffer_size > 0)993{994buffer.AudioBytes = buffer_size;995buffer.pAudioData = songBuffer;996buffer.PlayBegin = 0;997buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign;998buffer.LoopBegin = 0;999buffer.LoopLength = 0;1000buffer.LoopCount = 0;1001buffer.pContext = NULL;1002FAudioSourceVoice_SubmitSourceBuffer(1003songVoice,1004&buffer,1005NULL1006);1007}10081009LOG_FUNC_EXIT(songAudio);1010}10111012static void XNA_SongKill()1013{1014if (songVoice != NULL)1015{1016FAudioSourceVoice_Stop(songVoice, 0, 0);1017FAudioVoice_DestroyVoice(songVoice);1018songVoice = NULL;1019}1020if (activeSong)1021{1022IMFSourceReader_Release(activeSong);1023activeSong = NULL;1024}1025FAudio_free(songBuffer);1026songBuffer = NULL;1027songBufferSize = 0;1028}10291030/* "Public" API */10311032FAUDIOAPI void XNA_SongInit()1033{1034HRESULT hr;10351036hr = MFStartup(MF_VERSION, MFSTARTUP_FULL);1037FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!");10381039FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR);1040FAudio_CreateMasteringVoice(1041songAudio,1042&songMaster,1043FAUDIO_DEFAULT_CHANNELS,1044FAUDIO_DEFAULT_SAMPLERATE,10450,10460,1047NULL1048);1049}10501051FAUDIOAPI void XNA_SongQuit()1052{1053XNA_SongKill();1054FAudioVoice_DestroyVoice(songMaster);1055FAudio_Release(songAudio);1056MFShutdown();1057}10581059FAUDIOAPI float XNA_PlaySong(const char *name)1060{1061IMFAttributes *attributes = NULL;1062IMFMediaType *media_type = NULL;1063UINT32 channels, samplerate;1064INT64 duration;1065PROPVARIANT var;1066HRESULT hr;1067WCHAR filename_w[MAX_PATH];10681069LOG_FUNC_ENTER(songAudio);1070LOG_INFO(songAudio, "name %s\n", name);1071XNA_SongKill();10721073MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH);10741075hr = MFCreateAttributes(&attributes, 1);1076FAudio_assert(!FAILED(hr) && "Failed to create attributes!");1077hr = MFCreateSourceReaderFromURL(1078filename_w,1079attributes,1080&activeSong1081);1082FAudio_assert(!FAILED(hr) && "Failed to create source reader!");1083IMFAttributes_Release(attributes);10841085hr = MFCreateMediaType(&media_type);1086FAudio_assert(!FAILED(hr) && "Failed to create media type!");1087hr = IMFMediaType_SetGUID(1088media_type,1089&MF_MT_MAJOR_TYPE,1090&MFMediaType_Audio1091);1092FAudio_assert(!FAILED(hr) && "Failed to set major type!");1093hr = IMFMediaType_SetGUID(1094media_type,1095&MF_MT_SUBTYPE,1096&MFAudioFormat_Float1097);1098FAudio_assert(!FAILED(hr) && "Failed to set sub type!");1099hr = IMFSourceReader_SetCurrentMediaType(1100activeSong,1101MF_SOURCE_READER_FIRST_AUDIO_STREAM,1102NULL,1103media_type1104);1105FAudio_assert(!FAILED(hr) && "Failed to set source media type!");1106hr = IMFSourceReader_SetStreamSelection(1107activeSong,1108MF_SOURCE_READER_FIRST_AUDIO_STREAM,1109TRUE1110);1111FAudio_assert(!FAILED(hr) && "Failed to select source stream!");1112IMFMediaType_Release(media_type);11131114hr = IMFSourceReader_GetCurrentMediaType(1115activeSong,1116MF_SOURCE_READER_FIRST_AUDIO_STREAM,1117&media_type1118);1119FAudio_assert(!FAILED(hr) && "Failed to get current media type!");1120hr = IMFMediaType_GetUINT32(1121media_type,1122&MF_MT_AUDIO_NUM_CHANNELS,1123&channels1124);1125FAudio_assert(!FAILED(hr) && "Failed to get channel count!");1126hr = IMFMediaType_GetUINT32(1127media_type,1128&MF_MT_AUDIO_SAMPLES_PER_SECOND,1129&samplerate1130);1131FAudio_assert(!FAILED(hr) && "Failed to get sample rate!");1132IMFMediaType_Release(media_type);11331134hr = IMFSourceReader_GetPresentationAttribute(1135activeSong,1136MF_SOURCE_READER_MEDIASOURCE,1137&MF_PD_DURATION,1138&var1139);1140FAudio_assert(!FAILED(hr) && "Failed to get song duration!");1141hr = PropVariantToInt64(&var, &duration);1142FAudio_assert(!FAILED(hr) && "Failed to get song duration!");1143PropVariantClear(&var);11441145activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;1146activeSongFormat.nChannels = channels;1147activeSongFormat.nSamplesPerSec = samplerate;1148activeSongFormat.wBitsPerSample = sizeof(float) * 8;1149activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8;1150activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign;1151activeSongFormat.cbSize = 0;11521153/* Init voice */1154FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback));1155callbacks.OnBufferEnd = XNA_SongSubmitBuffer;1156FAudio_CreateSourceVoice(1157songAudio,1158&songVoice,1159&activeSongFormat,11600,11611.0f, /* No pitch shifting here! */1162&callbacks,1163NULL,1164NULL1165);1166FAudioVoice_SetVolume(songVoice, songVolume, 0);1167XNA_SongSubmitBuffer(NULL, NULL);11681169/* Finally. */1170FAudioSourceVoice_Start(songVoice, 0, 0);1171LOG_FUNC_EXIT(songAudio);1172return (float)(duration / 10000000.);1173}11741175FAUDIOAPI void XNA_PauseSong()1176{1177if (songVoice == NULL)1178{1179return;1180}1181FAudioSourceVoice_Stop(songVoice, 0, 0);1182}11831184FAUDIOAPI void XNA_ResumeSong()1185{1186if (songVoice == NULL)1187{1188return;1189}1190FAudioSourceVoice_Start(songVoice, 0, 0);1191}11921193FAUDIOAPI void XNA_StopSong()1194{1195XNA_SongKill();1196}11971198FAUDIOAPI void XNA_SetSongVolume(float volume)1199{1200songVolume = volume;1201if (songVoice != NULL)1202{1203FAudioVoice_SetVolume(songVoice, songVolume, 0);1204}1205}12061207FAUDIOAPI uint32_t XNA_GetSongEnded()1208{1209FAudioVoiceState state;1210if (songVoice == NULL || activeSong == NULL)1211{1212return 1;1213}1214FAudioSourceVoice_GetState(songVoice, &state, 0);1215return state.BuffersQueued == 0 && state.SamplesPlayed == 0;1216}12171218FAUDIOAPI void XNA_EnableVisualization(uint32_t enable)1219{1220/* TODO: Enable/Disable FAPO effect */1221}12221223FAUDIOAPI uint32_t XNA_VisualizationEnabled()1224{1225/* TODO: Query FAPO effect enabled */1226return 0;1227}12281229FAUDIOAPI void XNA_GetSongVisualizationData(1230float *frequencies,1231float *samples,1232uint32_t count1233) {1234/* TODO: Visualization FAPO that reads in Song samples, FFT analysis */1235}12361237#else12381239extern int this_tu_is_empty;12401241#endif /* FAUDIO_WIN32_PLATFORM */124212431244