Path: blob/master/libs/faudio/src/FAudio_platform_win32.c
4389 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;345WAVEFORMATEX *closest;346IMMDevice *device = NULL;347HRESULT hr;348HANDLE audioEvent = NULL;349BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE);350#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64ec__) || defined(_M_ARM64EC)351BOOL has_neon = TRUE;352#elif defined(__arm__) || defined(_M_ARM)353BOOL has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);354#else355BOOL has_neon = FALSE;356#endif357FAudio_INTERNAL_InitSIMDFunctions(has_sse2, has_neon);358FAudio_resolve_SetThreadDescription();359360FAudio_PlatformAddRef();361362*platformDevice = NULL;363364args = FAudio_malloc(sizeof(*args));365FAudio_assert(!!args && "Failed to allocate FAudio thread args!");366367data = FAudio_malloc(sizeof(*data));368FAudio_assert(!!data && "Failed to allocate FAudio platform data!");369FAudio_zero(data, sizeof(*data));370371args->format.Format.wFormatTag = mixFormat->Format.wFormatTag;372args->format.Format.nChannels = mixFormat->Format.nChannels;373args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec;374args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec;375args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign;376args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample;377args->format.Format.cbSize = mixFormat->Format.cbSize;378379if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)380{381args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample;382args->format.dwChannelMask = mixFormat->dwChannelMask;383FAudio_memcpy(384&args->format.SubFormat,385&mixFormat->SubFormat,386sizeof(GUID)387);388}389390audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL);391FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!");392393data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);394FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!");395396hr = FAudio_OpenDevice(deviceIndex, &device);397FAudio_assert(!FAILED(hr) && "Failed to get audio device!");398399hr = IMMDevice_Activate(400device,401&IID_IAudioClient,402CLSCTX_ALL,403NULL,404(void **)&data->client405);406FAudio_assert(!FAILED(hr) && "Failed to create audio client!");407IMMDevice_Release(device);408409if (flags & FAUDIO_1024_QUANTUM) duration = 213333;410else duration = 100000;411412hr = IAudioClient_IsFormatSupported(413data->client,414AUDCLNT_SHAREMODE_SHARED,415&args->format.Format,416&closest417);418FAudio_assert(!FAILED(hr) && "Failed to find supported audio format!");419420if (closest)421{422if (closest->wFormatTag != WAVE_FORMAT_EXTENSIBLE) args->format.Format = *closest;423else args->format = *(WAVEFORMATEXTENSIBLE *)closest;424CoTaskMemFree(closest);425}426427hr = IAudioClient_Initialize(428data->client,429AUDCLNT_SHAREMODE_SHARED,430AUDCLNT_STREAMFLAGS_EVENTCALLBACK,431duration * 3,4320,433&args->format.Format,434&GUID_NULL435);436FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!");437438hr = IAudioClient_SetEventHandle(data->client, audioEvent);439FAudio_assert(!FAILED(hr) && "Failed to set audio client event!");440441mixFormat->Format.wFormatTag = args->format.Format.wFormatTag;442mixFormat->Format.nChannels = args->format.Format.nChannels;443mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec;444mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec;445mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign;446mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample;447448if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)449{450mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);451mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample;452mixFormat->dwChannelMask = args->format.dwChannelMask;453FAudio_memcpy(454&mixFormat->SubFormat,455&args->format.SubFormat,456sizeof(GUID)457);458}459else460{461mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx);462}463464args->client = data->client;465args->events[0] = audioEvent;466args->events[1] = data->stopEvent;467args->audio = audio;468if (flags & FAUDIO_1024_QUANTUM) args->updateSize = (UINT)(args->format.Format.nSamplesPerSec / (1000.0 / (64.0 / 3.0)));469else args->updateSize = args->format.Format.nSamplesPerSec / 100;470471data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL);472FAudio_assert(!!data->audioThread && "Failed to create audio client thread!");473474*updateSize = args->updateSize;475*platformDevice = data;476return;477}478479void FAudio_PlatformQuit(void* platformDevice)480{481struct FAudioWin32PlatformData *data = platformDevice;482483SetEvent(data->stopEvent);484WaitForSingleObject(data->audioThread, INFINITE);485if (data->client) IAudioClient_Release(data->client);486if (kernelbase)487{488my_SetThreadDescription = NULL;489FreeLibrary(kernelbase);490kernelbase = NULL;491}492FAudio_PlatformRelease();493}494495void FAudio_PlatformAddRef()496{497HRESULT hr;498EnterCriticalSection(&faudio_cs);499if (!device_enumerator)500{501init_hr = CoInitialize(NULL);502hr = CoCreateInstance(503&CLSID_MMDeviceEnumerator,504NULL,505CLSCTX_INPROC_SERVER,506&IID_IMMDeviceEnumerator,507(void**)&device_enumerator508);509FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!");510}511else IMMDeviceEnumerator_AddRef(device_enumerator);512LeaveCriticalSection(&faudio_cs);513}514515void FAudio_PlatformRelease()516{517EnterCriticalSection(&faudio_cs);518if (!IMMDeviceEnumerator_Release(device_enumerator))519{520device_enumerator = NULL;521if (SUCCEEDED(init_hr)) CoUninitialize();522}523LeaveCriticalSection(&faudio_cs);524}525526uint32_t FAudio_PlatformGetDeviceCount(void)527{528IMMDeviceCollection *device_collection;529uint32_t count;530HRESULT hr;531532FAudio_PlatformAddRef();533534hr = IMMDeviceEnumerator_EnumAudioEndpoints(535device_enumerator,536eRender,537DEVICE_STATE_ACTIVE,538&device_collection539);540if (FAILED(hr)) {541FAudio_PlatformRelease();542return 0;543}544545hr = IMMDeviceCollection_GetCount(device_collection, &count);546if (FAILED(hr)) {547IMMDeviceCollection_Release(device_collection);548FAudio_PlatformRelease();549return 0;550}551552IMMDeviceCollection_Release(device_collection);553554FAudio_PlatformRelease();555556return count;557}558559uint32_t FAudio_PlatformGetDeviceDetails(560uint32_t index,561FAudioDeviceDetails *details562) {563WAVEFORMATEX *format, *obtained;564WAVEFORMATEXTENSIBLE *ext;565IAudioClient *client;566IMMDevice *device;567IPropertyStore* properties;568PROPVARIANT deviceName;569uint32_t count = 0;570uint32_t ret = 0;571HRESULT hr;572WCHAR *str;573GUID sub;574575FAudio_memset(details, 0, sizeof(FAudioDeviceDetails));576577FAudio_PlatformAddRef();578579count = FAudio_PlatformGetDeviceCount();580if (index >= count)581{582FAudio_PlatformRelease();583return FAUDIO_E_INVALID_CALL;584}585586hr = FAudio_OpenDevice(index, &device);587FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint!");588589if (index == 0)590{591details->Role = FAudioGlobalDefaultDevice;592}593else594{595details->Role = FAudioNotDefaultDevice;596}597598/* Set the Device Display Name */599hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &properties);600FAudio_assert(!FAILED(hr) && "Failed to open device property store!");601hr = IPropertyStore_GetValue(properties, (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &deviceName);602FAudio_assert(!FAILED(hr) && "Failed to get audio device friendly name!");603lstrcpynW((LPWSTR)details->DisplayName, deviceName.pwszVal, ARRAYSIZE(details->DisplayName) - 1);604PropVariantClear(&deviceName);605IPropertyStore_Release(properties);606607/* Set the Device ID */608hr = IMMDevice_GetId(device, &str);609FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint id!");610lstrcpynW((LPWSTR)details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1);611CoTaskMemFree(str);612613hr = IMMDevice_Activate(614device,615&IID_IAudioClient,616CLSCTX_ALL,617NULL,618(void **)&client619);620FAudio_assert(!FAILED(hr) && "Failed to activate audio client!");621622hr = IAudioClient_GetMixFormat(client, &format);623FAudio_assert(!FAILED(hr) && "Failed to get audio client mix format!");624625if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)626{627ext = (WAVEFORMATEXTENSIBLE *)format;628sub = ext->SubFormat;629FAudio_memcpy(630&ext->SubFormat,631&DATAFORMAT_SUBTYPE_PCM,632sizeof(GUID)633);634635hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED, format, &obtained);636if (FAILED(hr))637{638ext->SubFormat = sub;639}640else if (obtained)641{642CoTaskMemFree(format);643format = obtained;644}645}646647details->OutputFormat.Format.wFormatTag = format->wFormatTag;648details->OutputFormat.Format.nChannels = format->nChannels;649details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec;650details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec;651details->OutputFormat.Format.nBlockAlign = format->nBlockAlign;652details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample;653details->OutputFormat.Format.cbSize = format->cbSize;654655if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)656{657ext = (WAVEFORMATEXTENSIBLE *)format;658details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample;659details->OutputFormat.dwChannelMask = ext->dwChannelMask;660FAudio_memcpy(661&details->OutputFormat.SubFormat,662&ext->SubFormat,663sizeof(GUID)664);665}666else667{668details->OutputFormat.dwChannelMask = GetMask(format->nChannels);669}670671CoTaskMemFree(format);672673IAudioClient_Release(client);674675IMMDevice_Release(device);676677FAudio_PlatformRelease();678679return ret;680}681682FAudioMutex FAudio_PlatformCreateMutex(void)683{684CRITICAL_SECTION *cs;685686cs = FAudio_malloc(sizeof(CRITICAL_SECTION));687if (!cs) return NULL;688689InitializeCriticalSection(cs);690691return cs;692}693694void FAudio_PlatformLockMutex(FAudioMutex mutex)695{696if (mutex) EnterCriticalSection(mutex);697}698699void FAudio_PlatformUnlockMutex(FAudioMutex mutex)700{701if (mutex) LeaveCriticalSection(mutex);702}703704void FAudio_PlatformDestroyMutex(FAudioMutex mutex)705{706if (mutex) DeleteCriticalSection(mutex);707FAudio_free(mutex);708}709710struct FAudioThreadArgs711{712FAudioThreadFunc func;713const char *name;714void* data;715};716717static DWORD WINAPI FaudioThreadWrapper(void *user)718{719struct FAudioThreadArgs *args = user;720DWORD ret;721722FAudio_set_thread_name(args->name);723ret = args->func(args->data);724725FAudio_free(args);726return ret;727}728729FAudioThread FAudio_PlatformCreateThread(730FAudioThreadFunc func,731const char *name,732void* data733) {734struct FAudioThreadArgs *args;735736if (!(args = FAudio_malloc(sizeof(*args)))) return NULL;737args->func = func;738args->name = name;739args->data = data;740741return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL);742}743744void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval)745{746WaitForSingleObject(thread, INFINITE);747if (retval != NULL) GetExitCodeThread(thread, (DWORD *)retval);748}749750void FAudio_PlatformThreadPriority(FAudioThreadPriority priority)751{752/* FIXME */753}754755uint64_t FAudio_PlatformGetThreadID(void)756{757return GetCurrentThreadId();758}759760void FAudio_sleep(uint32_t ms)761{762Sleep(ms);763}764765uint32_t FAudio_timems()766{767return GetTickCount();768}769770/* FAudio I/O */771772static size_t FAUDIOCALL FAudio_FILE_read(773void *data,774void *dst,775size_t size,776size_t count777) {778if (!data) return 0;779return fread(dst, size, count, data);780}781782static int64_t FAUDIOCALL FAudio_FILE_seek(783void *data,784int64_t offset,785int whence786) {787if (!data) return -1;788fseek(data, (long)offset, whence);789return ftell(data);790}791792static int FAUDIOCALL FAudio_FILE_close(void *data)793{794if (!data) return 0;795fclose(data);796return 0;797}798799FAudioIOStream* FAudio_fopen(const char *path)800{801FAudioIOStream *io;802errno_t err = -1;803804io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));805if (!io) return NULL;806807err = fopen_s((FILE**)&(io->data), path, "rb");808809if (err != 0 || io->data == NULL)810{811FAudio_free(io);812return NULL;813}814815io->read = FAudio_FILE_read;816io->seek = FAudio_FILE_seek;817io->close = FAudio_FILE_close;818io->lock = FAudio_PlatformCreateMutex();819820return io;821}822823struct FAudio_mem824{825char *mem;826int64_t len;827int64_t pos;828};829830static size_t FAUDIOCALL FAudio_mem_read(831void *data,832void *dst,833size_t size,834size_t count835) {836struct FAudio_mem *io = data;837size_t len = size * count;838839if (!data) return 0;840841while (len && len > (UINT)(io->len - io->pos)) len -= size;842FAudio_memcpy(dst, io->mem + io->pos, len);843io->pos += len;844845return len;846}847848static int64_t FAUDIOCALL FAudio_mem_seek(849void *data,850int64_t offset,851int whence852) {853struct FAudio_mem *io = data;854if (!data) return -1;855856if (whence == SEEK_SET)857{858if (io->len > offset) io->pos = offset;859else io->pos = io->len;860}861if (whence == SEEK_CUR)862{863if (io->len > io->pos + offset) io->pos += offset;864else io->pos = io->len;865}866if (whence == SEEK_END)867{868if (io->len > offset) io->pos = io->len - offset;869else io->pos = 0;870}871872return io->pos;873}874875static int FAUDIOCALL FAudio_mem_close(void *data)876{877if (!data) return 0;878FAudio_free(data);879return 0;880}881882FAudioIOStream* FAudio_memopen(void *mem, int len)883{884struct FAudio_mem *data;885FAudioIOStream *io;886887io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream));888if (!io) return NULL;889890data = FAudio_malloc(sizeof(struct FAudio_mem));891if (!data)892{893FAudio_free(io);894return NULL;895}896897data->mem = mem;898data->len = len;899data->pos = 0;900901io->data = data;902io->read = FAudio_mem_read;903io->seek = FAudio_mem_seek;904io->close = FAudio_mem_close;905io->lock = FAudio_PlatformCreateMutex();906return io;907}908909uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset)910{911struct FAudio_mem *memio = io->data;912return (uint8_t *)memio->mem + offset;913}914915void FAudio_close(FAudioIOStream *io)916{917io->close(io->data);918FAudio_PlatformDestroyMutex((FAudioMutex) io->lock);919FAudio_free(io);920}921922/* XNA Song implementation over Win32 MF */923924static FAudioWaveFormatEx activeSongFormat;925IMFSourceReader *activeSong;926static uint8_t *songBuffer;927static SIZE_T songBufferSize;928929static float songVolume = 1.0f;930static FAudio *songAudio = NULL;931static FAudioMasteringVoice *songMaster = NULL;932933static FAudioSourceVoice *songVoice = NULL;934static FAudioVoiceCallback callbacks;935936/* Internal Functions */937938static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext)939{940IMFMediaBuffer *media_buffer;941FAudioBuffer buffer;942IMFSample *sample;943HRESULT hr;944DWORD flags, buffer_size = 0;945BYTE *buffer_ptr;946947LOG_FUNC_ENTER(songAudio);948949FAudio_memset(&buffer, 0, sizeof(buffer));950951hr = IMFSourceReader_ReadSample(952activeSong,953MF_SOURCE_READER_FIRST_AUDIO_STREAM,9540,955NULL,956&flags,957NULL,958&sample959);960FAudio_assert(!FAILED(hr) && "Failed to read audio sample!");961962if (flags & MF_SOURCE_READERF_ENDOFSTREAM)963{964buffer.Flags = FAUDIO_END_OF_STREAM;965}966else967{968hr = IMFSample_ConvertToContiguousBuffer(969sample,970&media_buffer971);972FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!");973974hr = IMFMediaBuffer_Lock(975media_buffer,976&buffer_ptr,977NULL,978&buffer_size979);980FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!");981982if (songBufferSize < buffer_size)983{984songBufferSize = buffer_size;985songBuffer = FAudio_realloc(songBuffer, songBufferSize);986FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!");987}988FAudio_memcpy(songBuffer, buffer_ptr, buffer_size);989990hr = IMFMediaBuffer_Unlock(media_buffer);991FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!");992993IMFMediaBuffer_Release(media_buffer);994IMFSample_Release(sample);995}996997if (buffer_size > 0)998{999buffer.AudioBytes = buffer_size;1000buffer.pAudioData = songBuffer;1001buffer.PlayBegin = 0;1002buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign;1003buffer.LoopBegin = 0;1004buffer.LoopLength = 0;1005buffer.LoopCount = 0;1006buffer.pContext = NULL;1007FAudioSourceVoice_SubmitSourceBuffer(1008songVoice,1009&buffer,1010NULL1011);1012}10131014LOG_FUNC_EXIT(songAudio);1015}10161017static void XNA_SongKill()1018{1019if (songVoice != NULL)1020{1021FAudioSourceVoice_Stop(songVoice, 0, 0);1022FAudioVoice_DestroyVoice(songVoice);1023songVoice = NULL;1024}1025if (activeSong)1026{1027IMFSourceReader_Release(activeSong);1028activeSong = NULL;1029}1030FAudio_free(songBuffer);1031songBuffer = NULL;1032songBufferSize = 0;1033}10341035/* "Public" API */10361037FAUDIOAPI void XNA_SongInit()1038{1039HRESULT hr;10401041hr = MFStartup(MF_VERSION, MFSTARTUP_FULL);1042FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!");10431044FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR);1045FAudio_CreateMasteringVoice(1046songAudio,1047&songMaster,1048FAUDIO_DEFAULT_CHANNELS,1049FAUDIO_DEFAULT_SAMPLERATE,10500,10510,1052NULL1053);1054}10551056FAUDIOAPI void XNA_SongQuit()1057{1058XNA_SongKill();1059FAudioVoice_DestroyVoice(songMaster);1060FAudio_Release(songAudio);1061MFShutdown();1062}10631064FAUDIOAPI float XNA_PlaySong(const char *name)1065{1066IMFAttributes *attributes = NULL;1067IMFMediaType *media_type = NULL;1068UINT32 channels, samplerate;1069INT64 duration;1070PROPVARIANT var;1071HRESULT hr;1072WCHAR filename_w[MAX_PATH];10731074LOG_FUNC_ENTER(songAudio);1075LOG_INFO(songAudio, "name %s\n", name);1076XNA_SongKill();10771078MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH);10791080hr = MFCreateAttributes(&attributes, 1);1081FAudio_assert(!FAILED(hr) && "Failed to create attributes!");1082hr = MFCreateSourceReaderFromURL(1083filename_w,1084attributes,1085&activeSong1086);1087FAudio_assert(!FAILED(hr) && "Failed to create source reader!");1088IMFAttributes_Release(attributes);10891090hr = MFCreateMediaType(&media_type);1091FAudio_assert(!FAILED(hr) && "Failed to create media type!");1092hr = IMFMediaType_SetGUID(1093media_type,1094&MF_MT_MAJOR_TYPE,1095&MFMediaType_Audio1096);1097FAudio_assert(!FAILED(hr) && "Failed to set major type!");1098hr = IMFMediaType_SetGUID(1099media_type,1100&MF_MT_SUBTYPE,1101&MFAudioFormat_Float1102);1103FAudio_assert(!FAILED(hr) && "Failed to set sub type!");1104hr = IMFSourceReader_SetCurrentMediaType(1105activeSong,1106MF_SOURCE_READER_FIRST_AUDIO_STREAM,1107NULL,1108media_type1109);1110FAudio_assert(!FAILED(hr) && "Failed to set source media type!");1111hr = IMFSourceReader_SetStreamSelection(1112activeSong,1113MF_SOURCE_READER_FIRST_AUDIO_STREAM,1114TRUE1115);1116FAudio_assert(!FAILED(hr) && "Failed to select source stream!");1117IMFMediaType_Release(media_type);11181119hr = IMFSourceReader_GetCurrentMediaType(1120activeSong,1121MF_SOURCE_READER_FIRST_AUDIO_STREAM,1122&media_type1123);1124FAudio_assert(!FAILED(hr) && "Failed to get current media type!");1125hr = IMFMediaType_GetUINT32(1126media_type,1127&MF_MT_AUDIO_NUM_CHANNELS,1128&channels1129);1130FAudio_assert(!FAILED(hr) && "Failed to get channel count!");1131hr = IMFMediaType_GetUINT32(1132media_type,1133&MF_MT_AUDIO_SAMPLES_PER_SECOND,1134&samplerate1135);1136FAudio_assert(!FAILED(hr) && "Failed to get sample rate!");1137IMFMediaType_Release(media_type);11381139hr = IMFSourceReader_GetPresentationAttribute(1140activeSong,1141MF_SOURCE_READER_MEDIASOURCE,1142&MF_PD_DURATION,1143&var1144);1145FAudio_assert(!FAILED(hr) && "Failed to get song duration!");1146hr = PropVariantToInt64(&var, &duration);1147FAudio_assert(!FAILED(hr) && "Failed to get song duration!");1148PropVariantClear(&var);11491150activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;1151activeSongFormat.nChannels = channels;1152activeSongFormat.nSamplesPerSec = samplerate;1153activeSongFormat.wBitsPerSample = sizeof(float) * 8;1154activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8;1155activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign;1156activeSongFormat.cbSize = 0;11571158/* Init voice */1159FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback));1160callbacks.OnBufferEnd = XNA_SongSubmitBuffer;1161FAudio_CreateSourceVoice(1162songAudio,1163&songVoice,1164&activeSongFormat,11650,11661.0f, /* No pitch shifting here! */1167&callbacks,1168NULL,1169NULL1170);1171FAudioVoice_SetVolume(songVoice, songVolume, 0);1172XNA_SongSubmitBuffer(NULL, NULL);11731174/* Finally. */1175FAudioSourceVoice_Start(songVoice, 0, 0);1176LOG_FUNC_EXIT(songAudio);1177return (float)(duration / 10000000.);1178}11791180FAUDIOAPI void XNA_PauseSong()1181{1182if (songVoice == NULL)1183{1184return;1185}1186FAudioSourceVoice_Stop(songVoice, 0, 0);1187}11881189FAUDIOAPI void XNA_ResumeSong()1190{1191if (songVoice == NULL)1192{1193return;1194}1195FAudioSourceVoice_Start(songVoice, 0, 0);1196}11971198FAUDIOAPI void XNA_StopSong()1199{1200XNA_SongKill();1201}12021203FAUDIOAPI void XNA_SetSongVolume(float volume)1204{1205songVolume = volume;1206if (songVoice != NULL)1207{1208FAudioVoice_SetVolume(songVoice, songVolume, 0);1209}1210}12111212FAUDIOAPI uint32_t XNA_GetSongEnded()1213{1214FAudioVoiceState state;1215if (songVoice == NULL || activeSong == NULL)1216{1217return 1;1218}1219FAudioSourceVoice_GetState(songVoice, &state, 0);1220return state.BuffersQueued == 0 && state.SamplesPlayed == 0;1221}12221223FAUDIOAPI void XNA_EnableVisualization(uint32_t enable)1224{1225/* TODO: Enable/Disable FAPO effect */1226}12271228FAUDIOAPI uint32_t XNA_VisualizationEnabled()1229{1230/* TODO: Query FAPO effect enabled */1231return 0;1232}12331234FAUDIOAPI void XNA_GetSongVisualizationData(1235float *frequencies,1236float *samples,1237uint32_t count1238) {1239/* TODO: Visualization FAPO that reads in Song samples, FFT analysis */1240}12411242#else12431244extern int this_tu_is_empty;12451246#endif /* FAUDIO_WIN32_PLATFORM */124712481249