/* FAudio - XAudio Reimplementation for FNA * * Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * Ethan "flibitijibibo" Lee * */ #ifdef FAUDIO_WIN32_PLATFORM #include "FAudio_internal.h" #include #define COBJMACROS #include #include #include #include #include #include #include #include #include DEFINE_GUID(CLSID_CWMADecMediaObject, 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a); DEFINE_MEDIATYPE_GUID(MFAudioFormat_XMAudio2, FAUDIO_FORMAT_XMAUDIO2); static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 }; static IMMDeviceEnumerator *device_enumerator; static HRESULT init_hr; struct FAudioWin32PlatformData { IAudioClient *client; HANDLE audioThread; HANDLE stopEvent; }; struct FAudioAudioClientThreadArgs { WAVEFORMATEXTENSIBLE format; IAudioClient *client; HANDLE events[2]; FAudio *audio; UINT updateSize; }; void FAudio_Log(char const *msg) { OutputDebugStringA(msg); } static HRESULT FAudio_FillAudioClientBuffer( struct FAudioAudioClientThreadArgs *args, IAudioRenderClient *client, UINT frames, UINT padding ) { HRESULT hr = S_OK; BYTE *buffer; while (padding + args->updateSize <= frames) { hr = IAudioRenderClient_GetBuffer( client, frames - padding, &buffer ); if (FAILED(hr)) return hr; FAudio_zero( buffer, args->updateSize * args->format.Format.nBlockAlign ); if (args->audio->active) { FAudio_INTERNAL_UpdateEngine( args->audio, (float*) buffer ); } hr = IAudioRenderClient_ReleaseBuffer( client, args->updateSize, 0 ); if (FAILED(hr)) return hr; padding += args->updateSize; } return hr; } static DWORD WINAPI FAudio_AudioClientThread(void *user) { struct FAudioAudioClientThreadArgs *args = user; IAudioRenderClient *render_client; HRESULT hr = S_OK; UINT frames, padding = 0; hr = IAudioClient_GetService( args->client, &IID_IAudioRenderClient, (void **)&render_client ); FAudio_assert(!FAILED(hr) && "Failed to get IAudioRenderClient service!"); hr = IAudioClient_GetBufferSize(args->client, &frames); FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient buffer size!"); hr = FAudio_FillAudioClientBuffer(args, render_client, frames, 0); FAudio_assert(!FAILED(hr) && "Failed to initialize IAudioClient buffer!"); hr = IAudioClient_Start(args->client); FAudio_assert(!FAILED(hr) && "Failed to start IAudioClient!"); while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0) { hr = IAudioClient_GetCurrentPadding(args->client, &padding); FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!"); hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding); FAudio_assert(!FAILED(hr) && "Failed to fill IAudioClient buffer!"); } hr = IAudioClient_Stop(args->client); FAudio_assert(!FAILED(hr) && "Failed to stop IAudioClient!"); IAudioRenderClient_Release(render_client); FAudio_free(args); return 0; } void FAudio_PlatformInit( FAudio *audio, uint32_t flags, uint32_t deviceIndex, FAudioWaveFormatExtensible *mixFormat, uint32_t *updateSize, void** platformDevice ) { struct FAudioAudioClientThreadArgs *args; struct FAudioWin32PlatformData *data; REFERENCE_TIME duration; WAVEFORMATEX *closest; IMMDevice *device = NULL; HRESULT hr; HANDLE audioEvent = NULL; BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE); FAudio_INTERNAL_InitSIMDFunctions(has_sse2, FALSE); FAudio_PlatformAddRef(); *platformDevice = NULL; if (deviceIndex > 0) return; args = FAudio_malloc(sizeof(*args)); FAudio_assert(!!args && "Failed to allocate FAudio thread args!"); data = FAudio_malloc(sizeof(*data)); FAudio_assert(!!data && "Failed to allocate FAudio platform data!"); FAudio_zero(data, sizeof(*data)); args->format.Format.wFormatTag = mixFormat->Format.wFormatTag; args->format.Format.nChannels = mixFormat->Format.nChannels; args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec; args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec; args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign; args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample; args->format.Format.cbSize = mixFormat->Format.cbSize; if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample; args->format.dwChannelMask = mixFormat->dwChannelMask; FAudio_memcpy( &args->format.SubFormat, &mixFormat->SubFormat, sizeof(GUID) ); } audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL); FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!"); data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL); FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!"); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( device_enumerator, eRender, eConsole, &device ); FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); hr = IMMDevice_Activate( device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&data->client ); FAudio_assert(!FAILED(hr) && "Failed to create audio client!"); IMMDevice_Release(device); if (flags & FAUDIO_1024_QUANTUM) duration = 21330; else duration = 30000; hr = IAudioClient_IsFormatSupported( data->client, AUDCLNT_SHAREMODE_SHARED, &args->format.Format, &closest ); FAudio_assert(!FAILED(hr) && "Failed to find supported audio format!"); if (closest) { if (closest->wFormatTag != WAVE_FORMAT_EXTENSIBLE) args->format.Format = *closest; else args->format = *(WAVEFORMATEXTENSIBLE *)closest; CoTaskMemFree(closest); } hr = IAudioClient_Initialize( data->client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, duration, 0, &args->format.Format, &GUID_NULL ); FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!"); hr = IAudioClient_SetEventHandle(data->client, audioEvent); FAudio_assert(!FAILED(hr) && "Failed to set audio client event!"); mixFormat->Format.wFormatTag = args->format.Format.wFormatTag; mixFormat->Format.nChannels = args->format.Format.nChannels; mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec; mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec; mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign; mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample; if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample; mixFormat->dwChannelMask = args->format.dwChannelMask; FAudio_memcpy( &mixFormat->SubFormat, &args->format.SubFormat, sizeof(GUID) ); } else { mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx); } args->client = data->client; args->events[0] = audioEvent; args->events[1] = data->stopEvent; args->audio = audio; args->updateSize = args->format.Format.nSamplesPerSec / 100; data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL); FAudio_assert(!!data->audioThread && "Failed to create audio client thread!"); *updateSize = args->updateSize; *platformDevice = data; return; } void FAudio_PlatformQuit(void* platformDevice) { struct FAudioWin32PlatformData *data = platformDevice; SetEvent(data->stopEvent); WaitForSingleObject(data->audioThread, INFINITE); if (data->client) IAudioClient_Release(data->client); FAudio_PlatformRelease(); } void FAudio_PlatformAddRef() { HRESULT hr; EnterCriticalSection(&faudio_cs); if (!device_enumerator) { init_hr = CoInitialize(NULL); hr = CoCreateInstance( &CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&device_enumerator ); FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!"); } else IMMDeviceEnumerator_AddRef(device_enumerator); LeaveCriticalSection(&faudio_cs); } void FAudio_PlatformRelease() { EnterCriticalSection(&faudio_cs); if (!IMMDeviceEnumerator_Release(device_enumerator)) { device_enumerator = NULL; if (SUCCEEDED(init_hr)) CoUninitialize(); } LeaveCriticalSection(&faudio_cs); } uint32_t FAudio_PlatformGetDeviceCount(void) { IMMDevice *device; uint32_t count; HRESULT hr; FAudio_PlatformAddRef(); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( device_enumerator, eRender, eConsole, &device ); FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); IMMDevice_Release(device); FAudio_PlatformRelease(); return 1; } uint32_t FAudio_PlatformGetDeviceDetails( uint32_t index, FAudioDeviceDetails *details ) { WAVEFORMATEXTENSIBLE *ext; WAVEFORMATEX *format; IAudioClient *client; IMMDevice *device; uint32_t ret = 0; HRESULT hr; WCHAR *str; FAudio_memset(details, 0, sizeof(FAudioDeviceDetails)); if (index > 0) return FAUDIO_E_INVALID_CALL; FAudio_PlatformAddRef(); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( device_enumerator, eRender, eConsole, &device ); FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); details->Role = FAudioGlobalDefaultDevice; hr = IMMDevice_GetId(device, &str); FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint id!"); lstrcpynW(details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1); lstrcpynW(details->DisplayName, str, ARRAYSIZE(details->DisplayName) - 1); CoTaskMemFree(str); hr = IMMDevice_Activate( device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&client ); FAudio_assert(!FAILED(hr) && "Failed to activate audio client!"); hr = IAudioClient_GetMixFormat(client, &format); FAudio_assert(!FAILED(hr) && "Failed to get audio client mix format!"); details->OutputFormat.Format.wFormatTag = format->wFormatTag; details->OutputFormat.Format.nChannels = format->nChannels; details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec; details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec; details->OutputFormat.Format.nBlockAlign = format->nBlockAlign; details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample; details->OutputFormat.Format.cbSize = format->cbSize; if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { ext = (WAVEFORMATEXTENSIBLE *)format; details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample; details->OutputFormat.dwChannelMask = ext->dwChannelMask; FAudio_memcpy( &details->OutputFormat.SubFormat, &ext->SubFormat, sizeof(GUID) ); } IAudioClient_Release(client); IMMDevice_Release(device); FAudio_PlatformRelease(); return ret; } FAudioMutex FAudio_PlatformCreateMutex(void) { CRITICAL_SECTION *cs; cs = FAudio_malloc(sizeof(CRITICAL_SECTION)); if (!cs) return NULL; InitializeCriticalSection(cs); return cs; } void FAudio_PlatformLockMutex(FAudioMutex mutex) { if (mutex) EnterCriticalSection(mutex); } void FAudio_PlatformUnlockMutex(FAudioMutex mutex) { if (mutex) LeaveCriticalSection(mutex); } void FAudio_PlatformDestroyMutex(FAudioMutex mutex) { if (mutex) DeleteCriticalSection(mutex); FAudio_free(mutex); } struct FAudioThreadArgs { FAudioThreadFunc func; const char *name; void* data; }; static DWORD WINAPI FaudioThreadWrapper(void *user) { struct FAudioThreadArgs *args = user; DWORD ret; ret = args->func(args->data); FAudio_free(args); return ret; } FAudioThread FAudio_PlatformCreateThread( FAudioThreadFunc func, const char *name, void* data ) { struct FAudioThreadArgs *args; if (!(args = FAudio_malloc(sizeof(*args)))) return NULL; args->func = func; args->name = name; args->data = data; return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL); } void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) { WaitForSingleObject(thread, INFINITE); GetExitCodeThread(thread, (DWORD *)retval); } void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) { /* FIXME */ } uint64_t FAudio_PlatformGetThreadID(void) { return GetCurrentThreadId(); } void FAudio_sleep(uint32_t ms) { Sleep(ms); } uint32_t FAudio_timems() { return GetTickCount(); } /* FAudio I/O */ static size_t FAUDIOCALL FAudio_FILE_read( void *data, void *dst, size_t size, size_t count ) { if (!data) return 0; return fread(dst, size, count, data); } static int64_t FAUDIOCALL FAudio_FILE_seek( void *data, int64_t offset, int whence ) { if (!data) return -1; fseek(data, offset, whence); return ftell(data); } static int FAUDIOCALL FAudio_FILE_close(void *data) { if (!data) return 0; fclose(data); return 0; } FAudioIOStream* FAudio_fopen(const char *path) { FAudioIOStream *io; io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); if (!io) return NULL; io->data = fopen(path, "rb"); io->read = FAudio_FILE_read; io->seek = FAudio_FILE_seek; io->close = FAudio_FILE_close; io->lock = FAudio_PlatformCreateMutex(); return io; } struct FAudio_mem { char *mem; int64_t len; int64_t pos; }; static size_t FAUDIOCALL FAudio_mem_read( void *data, void *dst, size_t size, size_t count ) { struct FAudio_mem *io = data; size_t len = size * count; if (!data) return 0; while (len && len > (io->len - io->pos)) len -= size; FAudio_memcpy(dst, io->mem + io->pos, len); io->pos += len; return len; } static int64_t FAUDIOCALL FAudio_mem_seek( void *data, int64_t offset, int whence ) { struct FAudio_mem *io = data; if (!data) return -1; if (whence == SEEK_SET) { if (io->len > offset) io->pos = offset; else io->pos = io->len; } if (whence == SEEK_CUR) { if (io->len > io->pos + offset) io->pos += offset; else io->pos = io->len; } if (whence == SEEK_END) { if (io->len > offset) io->pos = io->len - offset; else io->pos = 0; } return io->pos; } static int FAUDIOCALL FAudio_mem_close(void *data) { if (!data) return 0; FAudio_free(data); } FAudioIOStream* FAudio_memopen(void *mem, int len) { struct FAudio_mem *data; FAudioIOStream *io; io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); if (!io) return NULL; data = FAudio_malloc(sizeof(struct FAudio_mem)); if (!data) { FAudio_free(io); return NULL; } data->mem = mem; data->len = len; data->pos = 0; io->data = data; io->read = FAudio_mem_read; io->seek = FAudio_mem_seek; io->close = FAudio_mem_close; io->lock = FAudio_PlatformCreateMutex(); return io; } uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) { struct FAudio_mem *memio = io->data; return memio->mem + offset; } void FAudio_close(FAudioIOStream *io) { io->close(io->data); FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); FAudio_free(io); } /* XNA Song implementation over Win32 MF */ static FAudioWaveFormatEx activeSongFormat; IMFSourceReader *activeSong; static uint8_t *songBuffer; static SIZE_T songBufferSize; static float songVolume = 1.0f; static FAudio *songAudio = NULL; static FAudioMasteringVoice *songMaster = NULL; static FAudioSourceVoice *songVoice = NULL; static FAudioVoiceCallback callbacks; /* Internal Functions */ static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext) { IMFMediaBuffer *media_buffer; FAudioBuffer buffer; IMFSample *sample; HRESULT hr; DWORD flags, buffer_size = 0; BYTE *buffer_ptr; LOG_FUNC_ENTER(songAudio); FAudio_memset(&buffer, 0, sizeof(buffer)); hr = IMFSourceReader_ReadSample( activeSong, MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &flags, NULL, &sample ); FAudio_assert(!FAILED(hr) && "Failed to read audio sample!"); if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { buffer.Flags = FAUDIO_END_OF_STREAM; } else { hr = IMFSample_ConvertToContiguousBuffer( sample, &media_buffer ); FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); hr = IMFMediaBuffer_Lock( media_buffer, &buffer_ptr, NULL, &buffer_size ); FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); if (songBufferSize < buffer_size) { songBufferSize = buffer_size; songBuffer = FAudio_realloc(songBuffer, songBufferSize); FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!"); } FAudio_memcpy(songBuffer, buffer_ptr, buffer_size); hr = IMFMediaBuffer_Unlock(media_buffer); FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); IMFMediaBuffer_Release(media_buffer); IMFSample_Release(sample); } if (buffer_size > 0) { buffer.AudioBytes = buffer_size; buffer.pAudioData = songBuffer; buffer.PlayBegin = 0; buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign; buffer.LoopBegin = 0; buffer.LoopLength = 0; buffer.LoopCount = 0; buffer.pContext = NULL; FAudioSourceVoice_SubmitSourceBuffer( songVoice, &buffer, NULL ); } LOG_FUNC_EXIT(songAudio); } static void XNA_SongKill() { if (songVoice != NULL) { FAudioSourceVoice_Stop(songVoice, 0, 0); FAudioVoice_DestroyVoice(songVoice); songVoice = NULL; } if (activeSong) { IMFSourceReader_Release(activeSong); activeSong = NULL; } FAudio_free(songBuffer); songBuffer = NULL; songBufferSize = 0; } /* "Public" API */ FAUDIOAPI void XNA_SongInit() { HRESULT hr; hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!"); FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR); FAudio_CreateMasteringVoice( songAudio, &songMaster, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, NULL ); } FAUDIOAPI void XNA_SongQuit() { XNA_SongKill(); FAudioVoice_DestroyVoice(songMaster); FAudio_Release(songAudio); MFShutdown(); } FAUDIOAPI float XNA_PlaySong(const char *name) { IMFAttributes *attributes = NULL; IMFMediaType *media_type = NULL; UINT32 channels, samplerate; UINT64 duration; PROPVARIANT var; HRESULT hr; WCHAR filename_w[MAX_PATH]; LOG_FUNC_ENTER(songAudio); LOG_INFO(songAudio, "name %s\n", name); XNA_SongKill(); MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH); hr = MFCreateAttributes(&attributes, 1); FAudio_assert(!FAILED(hr) && "Failed to create attributes!"); hr = MFCreateSourceReaderFromURL( filename_w, attributes, &activeSong ); FAudio_assert(!FAILED(hr) && "Failed to create source reader!"); IMFAttributes_Release(attributes); hr = MFCreateMediaType(&media_type); FAudio_assert(!FAILED(hr) && "Failed to create media type!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio ); FAudio_assert(!FAILED(hr) && "Failed to set major type!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_SUBTYPE, &MFAudioFormat_Float ); FAudio_assert(!FAILED(hr) && "Failed to set sub type!"); hr = IMFSourceReader_SetCurrentMediaType( activeSong, MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, media_type ); FAudio_assert(!FAILED(hr) && "Failed to set source media type!"); hr = IMFSourceReader_SetStreamSelection( activeSong, MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE ); FAudio_assert(!FAILED(hr) && "Failed to select source stream!"); IMFMediaType_Release(media_type); hr = IMFSourceReader_GetCurrentMediaType( activeSong, MF_SOURCE_READER_FIRST_AUDIO_STREAM, &media_type ); FAudio_assert(!FAILED(hr) && "Failed to get current media type!"); hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_NUM_CHANNELS, &channels ); FAudio_assert(!FAILED(hr) && "Failed to get channel count!"); hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &samplerate ); FAudio_assert(!FAILED(hr) && "Failed to get sample rate!"); IMFMediaType_Release(media_type); hr = IMFSourceReader_GetPresentationAttribute( activeSong, MF_SOURCE_READER_MEDIASOURCE, &MF_PD_DURATION, &var ); FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); hr = PropVariantToInt64(&var, &duration); FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); PropVariantClear(&var); activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; activeSongFormat.nChannels = channels; activeSongFormat.nSamplesPerSec = samplerate; activeSongFormat.wBitsPerSample = sizeof(float) * 8; activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8; activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign; activeSongFormat.cbSize = 0; /* Init voice */ FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback)); callbacks.OnBufferEnd = XNA_SongSubmitBuffer; FAudio_CreateSourceVoice( songAudio, &songVoice, &activeSongFormat, 0, 1.0f, /* No pitch shifting here! */ &callbacks, NULL, NULL ); FAudioVoice_SetVolume(songVoice, songVolume, 0); XNA_SongSubmitBuffer(NULL, NULL); /* Finally. */ FAudioSourceVoice_Start(songVoice, 0, 0); LOG_FUNC_EXIT(songAudio); return duration / 10000000.; } FAUDIOAPI void XNA_PauseSong() { if (songVoice == NULL) { return; } FAudioSourceVoice_Stop(songVoice, 0, 0); } FAUDIOAPI void XNA_ResumeSong() { if (songVoice == NULL) { return; } FAudioSourceVoice_Start(songVoice, 0, 0); } FAUDIOAPI void XNA_StopSong() { XNA_SongKill(); } FAUDIOAPI void XNA_SetSongVolume(float volume) { songVolume = volume; if (songVoice != NULL) { FAudioVoice_SetVolume(songVoice, songVolume, 0); } } FAUDIOAPI uint32_t XNA_GetSongEnded() { FAudioVoiceState state; if (songVoice == NULL || activeSong == NULL) { return 1; } FAudioSourceVoice_GetState(songVoice, &state, 0); return state.BuffersQueued == 0; } FAUDIOAPI void XNA_EnableVisualization(uint32_t enable) { /* TODO: Enable/Disable FAPO effect */ } FAUDIOAPI uint32_t XNA_VisualizationEnabled() { /* TODO: Query FAPO effect enabled */ return 0; } FAUDIOAPI void XNA_GetSongVisualizationData( float *frequencies, float *samples, uint32_t count ) { /* TODO: Visualization FAPO that reads in Song samples, FFT analysis */ } /* FAudio WMADEC implementation over Win32 MF */ struct FAudioWMADEC { IMFTransform *decoder; IMFSample *output_sample; char *output_buf; size_t output_pos; size_t output_size; size_t input_pos; size_t input_size; }; static HRESULT FAudio_WMAMF_ProcessInput( FAudioVoice *voice, FAudioBuffer *buffer ) { struct FAudioWMADEC *impl = voice->src.wmadec; IMFMediaBuffer *media_buffer; IMFSample *sample; DWORD copy_size; BYTE *copy_buf; HRESULT hr; copy_size = min(buffer->AudioBytes - impl->input_pos, impl->input_size); if (!copy_size) return S_FALSE; LOG_INFO(voice->audio, "pushing %x bytes at %x", copy_size, impl->input_pos); hr = MFCreateSample(&sample); FAudio_assert(!FAILED(hr) && "Failed to create sample!"); hr = MFCreateMemoryBuffer(copy_size, &media_buffer); FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); hr = IMFMediaBuffer_SetCurrentLength(media_buffer, copy_size); FAudio_assert(!FAILED(hr) && "Failed to set buffer length!"); hr = IMFMediaBuffer_Lock( media_buffer, ©_buf, NULL, ©_size ); FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); FAudio_memcpy(copy_buf, buffer->pAudioData + impl->input_pos, copy_size); hr = IMFMediaBuffer_Unlock(media_buffer); FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); hr = IMFSample_AddBuffer(sample, media_buffer); FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); IMFMediaBuffer_Release(media_buffer); hr = IMFTransform_ProcessInput(impl->decoder, 0, sample, 0); IMFSample_Release(sample); if (hr == MF_E_NOTACCEPTING) return S_OK; if (FAILED(hr)) { LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); return hr; } impl->input_pos += copy_size; return S_OK; }; static HRESULT FAudio_WMAMF_ProcessOutput( FAudioVoice *voice, FAudioBuffer *buffer ) { struct FAudioWMADEC *impl = voice->src.wmadec; MFT_OUTPUT_DATA_BUFFER output; IMFMediaBuffer *media_buffer; DWORD status, copy_size; BYTE *copy_buf; HRESULT hr; while (1) { FAudio_memset(&output, 0, sizeof(output)); output.pSample = impl->output_sample; hr = IMFTransform_ProcessOutput(impl->decoder, 0, 1, &output, &status); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) return S_FALSE; if (FAILED(hr)) { LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); return hr; } if (output.dwStatus & MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) continue; hr = IMFSample_ConvertToContiguousBuffer( output.pSample, &media_buffer ); FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); hr = IMFMediaBuffer_Lock( media_buffer, ©_buf, NULL, ©_size ); FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); if (impl->output_pos + copy_size > impl->output_size) { impl->output_size = max( impl->output_pos + copy_size, impl->output_size * 3 / 2 ); impl->output_buf = voice->audio->pRealloc( impl->output_buf, impl->output_size ); FAudio_assert(impl->output_buf && "Failed to resize output buffer!"); } FAudio_memcpy(impl->output_buf + impl->output_pos, copy_buf, copy_size); impl->output_pos += copy_size; LOG_INFO(voice->audio, "pulled %x bytes at %x", copy_size, impl->output_pos); hr = IMFMediaBuffer_Unlock(media_buffer); FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); IMFMediaBuffer_Release(media_buffer); if (!impl->output_sample) IMFSample_Release(output.pSample); } return S_OK; }; static void FAudio_INTERNAL_DecodeWMAMF( FAudioVoice *voice, FAudioBuffer *buffer, float *decodeCache, uint32_t samples ) { const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; struct FAudioWMADEC *impl = voice->src.wmadec; size_t samples_pos, samples_size, copy_size; HRESULT hr; LOG_FUNC_ENTER(voice->audio) if (!impl->output_pos) { if (wfx->Format.wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) { const FAudioBufferWMA *wma = &voice->src.bufferList->bufferWMA; const UINT32 *output_sizes = wma->pDecodedPacketCumulativeBytes; impl->input_size = wfx->Format.nBlockAlign; impl->output_size = max( impl->output_size, output_sizes[wma->PacketCount - 1] ); } else { const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; impl->input_size = xwf->dwBytesPerBlock; impl->output_size = max( impl->output_size, (size_t) xwf->dwSamplesEncoded * voice->src.format->nChannels * (voice->src.format->wBitsPerSample / 8) ); } impl->output_buf = voice->audio->pRealloc( impl->output_buf, impl->output_size ); FAudio_assert(impl->output_buf && "Failed to allocate output buffer!"); LOG_INFO(voice->audio, "sending BOS to %p", impl->decoder); hr = IMFTransform_ProcessMessage( impl->decoder, MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); FAudio_assert(!FAILED(hr) && "Failed to notify decoder stream start!"); FAudio_WMAMF_ProcessInput(voice, buffer); } samples_pos = voice->src.curBufferOffset * voice->src.format->nChannels * sizeof(float); samples_size = samples * voice->src.format->nChannels * sizeof(float); while (impl->output_pos < samples_pos + samples_size) { hr = FAudio_WMAMF_ProcessOutput(voice, buffer); if (FAILED(hr)) goto error; if (hr == S_OK) continue; hr = FAudio_WMAMF_ProcessInput(voice, buffer); if (FAILED(hr)) goto error; if (hr == S_OK) continue; if (!impl->input_size) break; LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); hr = IMFTransform_ProcessMessage( impl->decoder, MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); impl->input_size = 0; } copy_size = FAudio_clamp(impl->output_pos - samples_pos, 0, samples_size); FAudio_memcpy(decodeCache, impl->output_buf + samples_pos, copy_size); LOG_INFO( voice->audio, "decoded %x / %x bytes, copied %x / %x bytes", impl->output_pos, impl->output_size, copy_size, samples_size ); LOG_FUNC_EXIT(voice->audio) return; error: FAudio_zero(decodeCache, samples * voice->src.format->nChannels * sizeof(float)); LOG_FUNC_EXIT(voice->audio) } uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) { static const uint8_t fake_codec_data[16] = {0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; struct FAudioWMADEC *impl; MFT_OUTPUT_STREAM_INFO info = {0}; IMFMediaBuffer *media_buffer; IMFMediaType *media_type; IMFTransform *decoder; HRESULT hr; UINT32 i, value; GUID guid; LOG_FUNC_ENTER(voice->audio) if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return -1; FAudio_memset(impl, 0, sizeof(*impl)); hr = CoCreateInstance( &CLSID_CWMADecMediaObject, 0, CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&decoder ); if (FAILED(hr)) { voice->audio->pFree(impl->output_buf); return -2; } hr = MFCreateMediaType(&media_type); FAudio_assert(!FAILED(hr) && "Failed create media type!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio ); FAudio_assert(!FAILED(hr) && "Failed set media major type!"); switch (type) { case FAUDIO_FORMAT_WMAUDIO2: hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, (void *)fake_codec_data, sizeof(fake_codec_data) ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_SUBTYPE, &MFAudioFormat_WMAudioV8 ); FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, wfx->Format.nBlockAlign ); FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; case FAUDIO_FORMAT_WMAUDIO3: hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, (void *)&wfx->Samples, wfx->Format.cbSize ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_SUBTYPE, &MFAudioFormat_WMAudioV9 ); FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, wfx->Format.nBlockAlign ); FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; case FAUDIO_FORMAT_WMAUDIO_LOSSLESS: hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, (void *)&wfx->Samples, wfx->Format.cbSize ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_SUBTYPE, &MFAudioFormat_WMAudio_Lossless ); FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, wfx->Format.nBlockAlign ); FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; case FAUDIO_FORMAT_XMAUDIO2: { const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, (void *)&wfx->Samples, wfx->Format.cbSize ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( media_type, &MF_MT_SUBTYPE, &MFAudioFormat_XMAudio2 ); FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, xwf->dwBytesPerBlock ); FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; } default: FAudio_assert(0 && "Unsupported type!"); break; } hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, wfx->Format.wBitsPerSample ); FAudio_assert(!FAILED(hr) && "Failed set input bits per sample!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, wfx->Format.nAvgBytesPerSec ); FAudio_assert(!FAILED(hr) && "Failed set input bytes per sample!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_NUM_CHANNELS, wfx->Format.nChannels ); FAudio_assert(!FAILED(hr) && "Failed set input channel count!"); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, wfx->Format.nSamplesPerSec ); FAudio_assert(!FAILED(hr) && "Failed set input sample rate!"); hr = IMFTransform_SetInputType( decoder, 0, media_type, 0 ); FAudio_assert(!FAILED(hr) && "Failed set decoder input type!"); IMFMediaType_Release(media_type); i = 0; while (SUCCEEDED(hr)) { hr = IMFTransform_GetOutputAvailableType( decoder, 0, i++, &media_type ); FAudio_assert(!FAILED(hr) && "Failed get output media type!"); hr = IMFMediaType_GetGUID( media_type, &MF_MT_MAJOR_TYPE, &guid ); FAudio_assert(!FAILED(hr) && "Failed get media major type!"); if (!IsEqualGUID(&MFMediaType_Audio, &guid)) goto next; hr = IMFMediaType_GetGUID( media_type, &MF_MT_SUBTYPE, &guid ); FAudio_assert(!FAILED(hr) && "Failed get media major type!"); if (!IsEqualGUID(&MFAudioFormat_Float, &guid)) goto next; hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, &value ); if (FAILED(hr)) { value = 32; hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, value ); } FAudio_assert(!FAILED(hr) && "Failed get bits per sample!"); if (value != 32) goto next; hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_NUM_CHANNELS, &value ); if (FAILED(hr)) { value = wfx->Format.nChannels; hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_NUM_CHANNELS, value ); } FAudio_assert(!FAILED(hr) && "Failed get channel count!"); if (value != wfx->Format.nChannels) goto next; hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &value ); if (FAILED(hr)) { value = wfx->Format.nSamplesPerSec; hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, value ); } FAudio_assert(!FAILED(hr) && "Failed get sample rate!"); if (value != wfx->Format.nSamplesPerSec) goto next; hr = IMFMediaType_GetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, &value ); if (FAILED(hr)) { value = wfx->Format.nChannels * sizeof(float); hr = IMFMediaType_SetUINT32( media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, value ); } FAudio_assert(!FAILED(hr) && "Failed get block align!"); if (value == wfx->Format.nChannels * sizeof(float)) break; next: IMFMediaType_Release(media_type); } FAudio_assert(!FAILED(hr) && "Failed to find output media type!"); hr = IMFTransform_SetOutputType(decoder, 0, media_type, 0); FAudio_assert(!FAILED(hr) && "Failed set decoder output type!"); IMFMediaType_Release(media_type); hr = IMFTransform_GetOutputStreamInfo(decoder, 0, &info); FAudio_assert(!FAILED(hr) && "Failed to get output stream info!"); impl->decoder = decoder; if (!(info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) { hr = MFCreateSample(&impl->output_sample); FAudio_assert(!FAILED(hr) && "Failed to create sample!"); hr = MFCreateMemoryBuffer(info.cbSize, &media_buffer); FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); hr = IMFSample_AddBuffer(impl->output_sample, media_buffer); FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); IMFMediaBuffer_Release(media_buffer); } hr = IMFTransform_ProcessMessage( decoder, MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); FAudio_assert(!FAILED(hr) && "Failed to start decoder stream!"); voice->src.wmadec = impl; voice->src.decode = FAudio_INTERNAL_DecodeWMAMF; LOG_FUNC_EXIT(voice->audio); return 0; } void FAudio_WMADEC_free(FAudioSourceVoice *voice) { struct FAudioWMADEC *impl = voice->src.wmadec; HRESULT hr; LOG_FUNC_ENTER(voice->audio) FAudio_PlatformLockMutex(voice->audio->sourceLock); LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) if (impl->input_size) { LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); hr = IMFTransform_ProcessMessage( impl->decoder, MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); impl->input_size = 0; } if (impl->output_pos) { LOG_INFO(voice->audio, "sending DRAIN to %p", impl->decoder); hr = IMFTransform_ProcessMessage( impl->decoder, MFT_MESSAGE_COMMAND_DRAIN, 0 ); FAudio_assert(!FAILED(hr) && "Failed to send DRAIN!"); impl->output_pos = 0; } if (impl->output_sample) IMFSample_Release(impl->output_sample); IMFTransform_Release(impl->decoder); voice->audio->pFree(impl->output_buf); voice->audio->pFree(voice->src.wmadec); voice->src.wmadec = NULL; voice->src.decode = NULL; FAudio_PlatformUnlockMutex(voice->audio->sourceLock); LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) LOG_FUNC_EXIT(voice->audio) } void FAudio_WMADEC_end_buffer(FAudioSourceVoice *voice) { struct FAudioWMADEC *impl = voice->src.wmadec; HRESULT hr; LOG_FUNC_ENTER(voice->audio) if (impl->input_size) { LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); hr = IMFTransform_ProcessMessage( impl->decoder, MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); impl->input_size = 0; } impl->output_pos = 0; impl->input_pos = 0; LOG_FUNC_EXIT(voice->audio) } #else extern int this_tu_is_empty; #endif /* FAUDIO_WIN32_PLATFORM */