mirror of
https://github.com/holub/mame
synced 2025-06-04 20:06:28 +03:00
commit
6fd9e66cd0
1196
3rdparty/win81sdk/Include/um/xaudio2.h
vendored
Normal file
1196
3rdparty/win81sdk/Include/um/xaudio2.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10
makefile
10
makefile
@ -25,6 +25,8 @@
|
||||
# USE_BGFX = 1
|
||||
# NO_OPENGL = 1
|
||||
# USE_DISPATCH_GL = 0
|
||||
# MODERN_WIN_API = 0
|
||||
# USE_XAUDIO2 = 0
|
||||
# DIRECTINPUT = 7
|
||||
# USE_SDL = 1
|
||||
# SDL_INI_PATH = .;$HOME/.mame/;ini;
|
||||
@ -567,6 +569,14 @@ ifdef USE_QTDEBUG
|
||||
PARAMS += --USE_QTDEBUG='$(USE_QTDEBUG)'
|
||||
endif
|
||||
|
||||
ifdef MODERN_WIN_API
|
||||
PARAMS += --MODERN_WIN_API='$(MODERN_WIN_API)'
|
||||
endif
|
||||
|
||||
ifdef USE_XAUDIO2
|
||||
PARAMS += --USE_XAUDIO2='$(USE_XAUDIO2)'
|
||||
endif
|
||||
|
||||
ifdef DIRECTINPUT
|
||||
PARAMS += --DIRECTINPUT='$(DIRECTINPUT)'
|
||||
endif
|
||||
|
@ -67,6 +67,7 @@ function osdmodulesbuild()
|
||||
MAME_DIR .. "src/osd/modules/sound/direct_sound.cpp",
|
||||
MAME_DIR .. "src/osd/modules/sound/coreaudio_sound.cpp",
|
||||
MAME_DIR .. "src/osd/modules/sound/sdl_sound.cpp",
|
||||
MAME_DIR .. "src/osd/modules/sound/xaudio2_sound.cpp",
|
||||
MAME_DIR .. "src/osd/modules/sound/none.cpp",
|
||||
}
|
||||
|
||||
@ -397,6 +398,38 @@ if not _OPTIONS["NO_USE_MIDI"] then
|
||||
end
|
||||
end
|
||||
|
||||
newoption {
|
||||
trigger = "MODERN_WIN_API",
|
||||
description = "Use Modern Windows APIs",
|
||||
allowed = {
|
||||
{ "0", "Use classic Windows APIs - allows support for XP and later" },
|
||||
{ "1", "Use Modern Windows APIs - support for Windows 8.1 and later" },
|
||||
},
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "USE_XAUDIO2",
|
||||
description = "Use XAudio2 API for audio",
|
||||
allowed = {
|
||||
{ "0", "Disable XAudio2" },
|
||||
{ "1", "Enable XAudio2" },
|
||||
},
|
||||
}
|
||||
|
||||
if _OPTIONS["USE_XAUDIO2"]=="1" then
|
||||
_OPTIONS["MODERN_WIN_API"] = "1",
|
||||
defines {
|
||||
"USE_XAUDIO2=1",
|
||||
},
|
||||
includedirs {
|
||||
MAME_DIR .. "3rdparty/win81sdk/Include/um",
|
||||
}
|
||||
else
|
||||
defines {
|
||||
"USE_XAUDIO2=0",
|
||||
}
|
||||
end
|
||||
|
||||
newoption {
|
||||
trigger = "USE_QTDEBUG",
|
||||
description = "Use QT debugger",
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
defines {
|
||||
"OSD_WINDOWS",
|
||||
"_WIN32_WINNT=0x0501",
|
||||
}
|
||||
|
||||
configuration { "mingw*-gcc or vs*" }
|
||||
@ -25,6 +24,23 @@ configuration { "vs*" }
|
||||
|
||||
configuration { }
|
||||
|
||||
if not _OPTIONS["MODERN_WIN_API"] then
|
||||
_OPTIONS["MODERN_WIN_API"] = "0"
|
||||
end
|
||||
|
||||
if _OPTIONS["MODERN_WIN_API"]=="1" then
|
||||
defines {
|
||||
"WINVER=0x0602",
|
||||
"_WIN32_WINNT=0x0602",
|
||||
"NTDDI_VERSION=0x06030000",
|
||||
"MODERN_WIN_API",
|
||||
}
|
||||
else
|
||||
defines {
|
||||
"_WIN32_WINNT=0x0501",
|
||||
}
|
||||
end
|
||||
|
||||
if not _OPTIONS["DONT_USE_NETWORK"] then
|
||||
defines {
|
||||
"USE_NETWORK",
|
||||
|
@ -185,6 +185,7 @@ void osd_common_t::register_options()
|
||||
REGISTER_MODULE(m_mod_man, SOUND_COREAUDIO);
|
||||
REGISTER_MODULE(m_mod_man, SOUND_JS);
|
||||
REGISTER_MODULE(m_mod_man, SOUND_SDL);
|
||||
REGISTER_MODULE(m_mod_man, SOUND_XAUDIO2);
|
||||
REGISTER_MODULE(m_mod_man, SOUND_NONE);
|
||||
|
||||
#ifdef SDLMAME_MACOSX
|
||||
|
730
src/osd/modules/sound/xaudio2_sound.cpp
Normal file
730
src/osd/modules/sound/xaudio2_sound.cpp
Normal file
@ -0,0 +1,730 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Brad Hughes
|
||||
//====================================================================
|
||||
//
|
||||
// xaudio2_sound.cpp - XAudio2 implementation of MAME sound routines
|
||||
//
|
||||
//====================================================================
|
||||
|
||||
#include "sound_module.h"
|
||||
#include "modules/osdmodule.h"
|
||||
|
||||
#if (defined(OSD_WINDOWS) && USE_XAUDIO2)
|
||||
|
||||
// standard windows headers
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable: 4068 )
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wattributes"
|
||||
// XAudio2 include
|
||||
#include <xaudio2.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma warning( pop )
|
||||
|
||||
|
||||
#include <mmsystem.h>
|
||||
|
||||
// stdlib includes
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
|
||||
#undef interface
|
||||
|
||||
// MAME headers
|
||||
#include "emu.h"
|
||||
#include "osdepend.h"
|
||||
#include "emuopts.h"
|
||||
|
||||
//============================================================
|
||||
// Constants
|
||||
//============================================================
|
||||
|
||||
#define INITIAL_BUFFER_COUNT 4
|
||||
#define SUBMIT_FREQUENCY_TARGET_MS 20
|
||||
|
||||
//============================================================
|
||||
// Macros
|
||||
//============================================================
|
||||
|
||||
// Check HRESULT result and log if error, then take an optional action on failure
|
||||
#define HR_LOG( CALL, LOGFN, ONFAIL ) do { \
|
||||
result = CALL; \
|
||||
if (FAILED(result)) { \
|
||||
LOGFN(#CALL " failed with error 0x%X\n", (unsigned int)result); \
|
||||
ONFAIL; } \
|
||||
} while (0)
|
||||
|
||||
// Variant of HR_LOG to log using osd_printf_error
|
||||
#define HR_LOGE( CALL, ONFAIL ) HR_LOG(CALL, osd_printf_error, ONFAIL)
|
||||
|
||||
// Variant of HR_LOG to log using osd_printf_verbose
|
||||
#define HR_LOGV( CALL, ONFAIL ) HR_LOG(CALL, osd_printf_verbose, ONFAIL)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, goto a label called Error:
|
||||
#define HR_GOERR( CALL ) HR_LOGE( CALL, goto Error;)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, return the specified value
|
||||
#define HR_RET( CALL, ret ) HR_LOGE(CALL, return ret;)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, return nothing (void function)
|
||||
#define HR_RETV( CALL ) HR_RET(CALL,)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, return 0
|
||||
#define HR_RET0( CALL ) HR_RET(CALL, 0)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, return the HRESULT
|
||||
#define HR_RETHR( CALL ) HR_RET(CALL, result)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, return 1
|
||||
#define HR_RET1( CALL ) HR_RET(CALL, 1)
|
||||
|
||||
// Macro to check for a failed HRESULT and if failed, log verbose, and proceed as normal
|
||||
#define HR_IGNORE( CALL ) HR_LOGV(CALL,)
|
||||
|
||||
//============================================================
|
||||
// Structs and typedefs
|
||||
//============================================================
|
||||
|
||||
// A stucture to hold a pointer and the count of bytes of the data it points to
|
||||
struct xaudio2_buffer
|
||||
{
|
||||
std::unique_ptr<BYTE[]> AudioData;
|
||||
DWORD AudioSize;
|
||||
};
|
||||
|
||||
// Custom deleter with overloads to free smart pointer types used in the implementations
|
||||
struct xaudio2_custom_deleter
|
||||
{
|
||||
public:
|
||||
void operator()(IXAudio2* obj) const
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
obj->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(IXAudio2MasteringVoice* obj) const
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
obj->DestroyVoice();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(IXAudio2SourceVoice* obj) const
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
obj->Stop(0);
|
||||
obj->FlushSourceBuffers();
|
||||
obj->DestroyVoice();
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(osd_lock* obj) const
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
osd_lock_free(obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Typedefs for smart pointers used with customer deleters
|
||||
typedef std::unique_ptr<IXAudio2, xaudio2_custom_deleter> xaudio2_ptr;
|
||||
typedef std::unique_ptr<IXAudio2MasteringVoice, xaudio2_custom_deleter> mastering_voice_ptr;
|
||||
typedef std::unique_ptr<IXAudio2SourceVoice, xaudio2_custom_deleter> src_voice_ptr;
|
||||
typedef std::unique_ptr<osd_lock, xaudio2_custom_deleter> osd_lock_ptr;
|
||||
|
||||
// Typedef for pointer to XAudio2Create
|
||||
typedef HRESULT(__stdcall* PFN_XAUDIO2CREATE)(IXAudio2**, UINT32, XAUDIO2_PROCESSOR);
|
||||
|
||||
//============================================================
|
||||
// Helper classes
|
||||
//============================================================
|
||||
|
||||
// Helper for locking within a particular scope without having to manually release
|
||||
class osd_scoped_lock
|
||||
{
|
||||
private:
|
||||
osd_lock * m_lock;
|
||||
public:
|
||||
osd_scoped_lock(osd_lock* lock)
|
||||
{
|
||||
m_lock = lock;
|
||||
osd_lock_acquire(m_lock);
|
||||
}
|
||||
|
||||
~osd_scoped_lock()
|
||||
{
|
||||
if (m_lock != nullptr)
|
||||
{
|
||||
osd_lock_release(m_lock);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Provides a pool of buffers
|
||||
class bufferpool
|
||||
{
|
||||
private:
|
||||
int m_initial;
|
||||
int m_buffersize;
|
||||
std::queue<std::unique_ptr<BYTE[]>> m_queue;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
bufferpool(int capacity, int bufferSize) :
|
||||
m_initial(capacity),
|
||||
m_buffersize(bufferSize)
|
||||
{
|
||||
for (int i = 0; i < m_initial; i++)
|
||||
{
|
||||
auto newBuffer = std::make_unique<BYTE[]>(m_buffersize);
|
||||
memset(newBuffer.get(), 0, m_buffersize);
|
||||
m_queue.push(std::move(newBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
// get next buffer element from the pool
|
||||
BYTE* next()
|
||||
{
|
||||
BYTE* next_buffer;
|
||||
if (!m_queue.empty())
|
||||
{
|
||||
next_buffer = m_queue.front().release();
|
||||
m_queue.pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
next_buffer = new BYTE[m_buffersize];
|
||||
memset(next_buffer, 0, m_buffersize);
|
||||
}
|
||||
|
||||
return next_buffer;
|
||||
}
|
||||
|
||||
// release element, make it available back in the pool
|
||||
void return_to_pool(BYTE* buffer)
|
||||
{
|
||||
auto returned_buf = std::unique_ptr<BYTE[]>(buffer);
|
||||
memset(returned_buf.get(), 0, m_buffersize);
|
||||
m_queue.push(std::move(returned_buf));
|
||||
}
|
||||
};
|
||||
|
||||
//============================================================
|
||||
// sound_xaudio2 class
|
||||
//============================================================
|
||||
|
||||
// The main class for the XAudio2 sound module implementation
|
||||
class sound_xaudio2 : public osd_module, public sound_module, public IXAudio2VoiceCallback
|
||||
{
|
||||
private:
|
||||
xaudio2_ptr m_xAudio2;
|
||||
mastering_voice_ptr m_masterVoice;
|
||||
src_voice_ptr m_sourceVoice;
|
||||
DWORD m_sample_bytes;
|
||||
std::unique_ptr<BYTE[]> m_buffer;
|
||||
DWORD m_buffer_size;
|
||||
DWORD m_buffer_count;
|
||||
DWORD m_writepos;
|
||||
osd_lock_ptr m_buffer_lock;
|
||||
HANDLE m_hEventBufferCompleted;
|
||||
HANDLE m_hEventDataAvailable;
|
||||
HANDLE m_hEventExiting;
|
||||
std::thread m_audioThread;
|
||||
std::queue<xaudio2_buffer> m_queue;
|
||||
std::unique_ptr<bufferpool> m_buffer_pool;
|
||||
HMODULE m_xaudio2_module;
|
||||
PFN_XAUDIO2CREATE m_pfnxaudio2create;
|
||||
UINT32 m_overflows;
|
||||
UINT32 m_underflows;
|
||||
BOOL m_in_underflow;
|
||||
|
||||
public:
|
||||
sound_xaudio2() :
|
||||
osd_module(OSD_SOUND_PROVIDER, "xaudio2"),
|
||||
sound_module(),
|
||||
m_xAudio2(nullptr),
|
||||
m_masterVoice(nullptr),
|
||||
m_sourceVoice(nullptr),
|
||||
m_sample_bytes(0),
|
||||
m_buffer(nullptr),
|
||||
m_buffer_size(0),
|
||||
m_buffer_count(0),
|
||||
m_writepos(0),
|
||||
m_buffer_lock(osd_lock_alloc()),
|
||||
m_hEventBufferCompleted(NULL),
|
||||
m_hEventDataAvailable(NULL),
|
||||
m_hEventExiting(NULL),
|
||||
m_buffer_pool(nullptr),
|
||||
m_xaudio2_module(NULL),
|
||||
m_pfnxaudio2create(nullptr),
|
||||
m_overflows(0),
|
||||
m_underflows(0),
|
||||
m_in_underflow(FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
virtual int init(osd_options const &options) override;
|
||||
virtual void exit() override;
|
||||
|
||||
// sound_module
|
||||
virtual void update_audio_stream(bool is_throttled, INT16 const *buffer, int samples_this_frame) override;
|
||||
virtual void set_mastervolume(int attenuation) override;
|
||||
|
||||
// Xaudio callbacks
|
||||
void OnVoiceProcessingPassStart(UINT32 bytes_required) override;
|
||||
void OnVoiceProcessingPassEnd() override {}
|
||||
void OnStreamEnd() override {}
|
||||
void OnBufferStart(void* pBufferContext) override {}
|
||||
void OnLoopEnd(void* pBufferContext) override {}
|
||||
void OnVoiceError(void* pBufferContext, HRESULT error) override {}
|
||||
void OnBufferEnd(void *pBufferContext) override;
|
||||
|
||||
private:
|
||||
void create_buffers(const WAVEFORMATEX &format);
|
||||
HRESULT create_voices(const WAVEFORMATEX &format);
|
||||
void process_audio();
|
||||
void submit_buffer(std::unique_ptr<BYTE[]> audioData, DWORD audioLength);
|
||||
void submit_needed();
|
||||
HRESULT xaudio2_create(IXAudio2 ** xaudio2_interface);
|
||||
void roll_buffer();
|
||||
BOOL submit_next_queued();
|
||||
};
|
||||
|
||||
//============================================================
|
||||
// init
|
||||
//============================================================
|
||||
|
||||
int sound_xaudio2::init(osd_options const &options)
|
||||
{
|
||||
HRESULT result = S_OK;
|
||||
|
||||
HR_IGNORE(CoInitializeEx(NULL, COINITBASE_MULTITHREADED));
|
||||
|
||||
// Create the IXAudio2 object
|
||||
IXAudio2 *temp_xaudio2 = nullptr;
|
||||
HR_RET1(xaudio2_create(&temp_xaudio2));
|
||||
m_xAudio2 = xaudio2_ptr(temp_xaudio2);
|
||||
|
||||
// make a format description for what we want
|
||||
WAVEFORMATEX format = { 0 };
|
||||
format.wBitsPerSample = 16;
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = 2;
|
||||
format.nSamplesPerSec = sample_rate();
|
||||
format.nBlockAlign = format.wBitsPerSample * format.nChannels / 8;
|
||||
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
|
||||
|
||||
m_sample_bytes = format.nBlockAlign;
|
||||
|
||||
#if defined(_DEBUG)
|
||||
XAUDIO2_DEBUG_CONFIGURATION debugConfig = { 0 };
|
||||
debugConfig.TraceMask = XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_TIMING | XAUDIO2_LOG_STREAMING;
|
||||
debugConfig.LogFunctionName = TRUE;
|
||||
m_xAudio2->SetDebugConfiguration(&debugConfig);
|
||||
#endif
|
||||
|
||||
// Initialize our events
|
||||
m_hEventBufferCompleted = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
m_hEventDataAvailable = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
m_hEventExiting = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
// create the voices and start them
|
||||
HR_RET1(create_voices(format));
|
||||
HR_RET1(m_sourceVoice->Start());
|
||||
|
||||
// Start the thread listening
|
||||
m_audioThread = std::thread([](sound_xaudio2* self) { self->process_audio(); }, this);
|
||||
|
||||
osd_printf_verbose("Sound: XAudio2 initialized\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// exit
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::exit()
|
||||
{
|
||||
// Wait on processing thread to end
|
||||
SetEvent(m_hEventExiting);
|
||||
m_audioThread.join();
|
||||
|
||||
CloseHandle(m_hEventBufferCompleted);
|
||||
CloseHandle(m_hEventDataAvailable);
|
||||
CloseHandle(m_hEventExiting);
|
||||
|
||||
m_sourceVoice.reset();
|
||||
m_masterVoice.reset();
|
||||
m_xAudio2.reset();
|
||||
m_buffer.reset();
|
||||
m_buffer_pool.reset();
|
||||
|
||||
if (m_overflows != 0 || m_underflows != 0)
|
||||
osd_printf_verbose("Sound: overflows=%u, underflows=%u\n", m_overflows, m_underflows);
|
||||
|
||||
osd_printf_verbose("Sound: XAudio2 deinitialized\n");
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// update_audio_stream
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::update_audio_stream(
|
||||
bool is_throttled,
|
||||
INT16 const *buffer,
|
||||
int samples_this_frame)
|
||||
{
|
||||
if ((sample_rate() == 0) || !m_buffer)
|
||||
return;
|
||||
|
||||
UINT32 const bytes_this_frame = samples_this_frame * m_sample_bytes;
|
||||
|
||||
osd_scoped_lock scope_lock(m_buffer_lock.get());
|
||||
|
||||
UINT32 bytes_left = bytes_this_frame;
|
||||
|
||||
while (bytes_left > 0)
|
||||
{
|
||||
UINT32 chunk = MIN(m_buffer_size, bytes_left);
|
||||
|
||||
// Roll the buffer if needed
|
||||
if (m_writepos + chunk >= m_buffer_size)
|
||||
{
|
||||
roll_buffer();
|
||||
}
|
||||
|
||||
// Copy in the data
|
||||
memcpy(m_buffer.get() + m_writepos, buffer, chunk);
|
||||
m_writepos += chunk;
|
||||
bytes_left -= chunk;
|
||||
}
|
||||
|
||||
// Signal data available
|
||||
SetEvent(m_hEventDataAvailable);
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// set_mastervolume
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::set_mastervolume(int attenuation)
|
||||
{
|
||||
assert(m_sourceVoice);
|
||||
|
||||
HRESULT result;
|
||||
|
||||
// clamp the attenuation to 0-32 range
|
||||
attenuation = MAX(MIN(attenuation, 0), -32);
|
||||
|
||||
// Ranges from 1.0 to XAUDIO2_MAX_VOLUME_LEVEL indicate additional gain
|
||||
// Ranges from 0 to 1.0 indicate a reduced volume level
|
||||
// 0 indicates silence
|
||||
// We only support a reduction from 1.0, so we generate values in the range 0.0 to 1.0
|
||||
float scaledVolume = (32.0f + attenuation) / 32.0f;
|
||||
|
||||
// set the master volume
|
||||
HR_RETV(m_sourceVoice->SetVolume(scaledVolume));
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// IXAudio2VoiceCallback::OnBufferEnd
|
||||
//============================================================
|
||||
|
||||
// The XAudio2 voice callback triggered when a buffer finishes playing
|
||||
void sound_xaudio2::OnBufferEnd(void *pBufferContext)
|
||||
{
|
||||
BYTE* completed_buffer = (BYTE*)pBufferContext;
|
||||
if (completed_buffer != nullptr)
|
||||
{
|
||||
auto scoped_lock = osd_scoped_lock(m_buffer_lock.get());
|
||||
m_buffer_pool->return_to_pool(completed_buffer);
|
||||
}
|
||||
|
||||
SetEvent(m_hEventBufferCompleted);
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// IXAudio2VoiceCallback::OnVoiceProcessingPassStart
|
||||
//============================================================
|
||||
|
||||
// The XAudio2 voice callback triggered on every pass
|
||||
void sound_xaudio2::OnVoiceProcessingPassStart(UINT32 bytes_required)
|
||||
{
|
||||
if (bytes_required == 0)
|
||||
{
|
||||
// Reset underflow indicator if we're caught up
|
||||
if (m_in_underflow) m_in_underflow = FALSE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Since there are bytes required, we're going to be in underflow
|
||||
if (!m_in_underflow)
|
||||
{
|
||||
m_underflows++;
|
||||
m_in_underflow = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// xaudio2_create
|
||||
//============================================================
|
||||
|
||||
// Dynamically loads the XAudio2 DLL and calls the exported XAudio2Create()
|
||||
HRESULT sound_xaudio2::xaudio2_create(IXAudio2 ** ppxaudio2_interface)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
if (nullptr == m_pfnxaudio2create)
|
||||
{
|
||||
if (nullptr == m_xaudio2_module)
|
||||
{
|
||||
m_xaudio2_module = LoadLibrary(XAUDIO2_DLL);
|
||||
if (nullptr == m_xaudio2_module)
|
||||
{
|
||||
osd_printf_error("Failed to load module '%S', error: 0x%X\n", XAUDIO2_DLL, (unsigned int)GetLastError());
|
||||
HR_RETHR(E_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
m_pfnxaudio2create = (PFN_XAUDIO2CREATE)GetProcAddress(m_xaudio2_module, "XAudio2Create");
|
||||
if (nullptr == m_pfnxaudio2create)
|
||||
{
|
||||
osd_printf_error("Failed to get adddress of exported function XAudio2Create, error: 0x%X\n", (unsigned int)GetLastError());
|
||||
HR_RETHR(E_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
HR_RETHR(m_pfnxaudio2create(ppxaudio2_interface, 0, XAUDIO2_DEFAULT_PROCESSOR));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// create_buffers
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::create_buffers(const WAVEFORMATEX &format)
|
||||
{
|
||||
// Compute the buffer size
|
||||
// buffer size is equal to the bytes we need to hold in memory per X tenths of a second where X is audio_latency
|
||||
float audio_latency_in_seconds = m_audio_latency / 10.0f;
|
||||
UINT32 format_bytes_per_second = format.nSamplesPerSec * format.nBlockAlign;
|
||||
UINT32 total_buffer_size = format_bytes_per_second * audio_latency_in_seconds;
|
||||
|
||||
// We want to be able to submit buffers every X milliseconds
|
||||
// I want to divide these up into "packets" so figure out how many buffers we need
|
||||
m_buffer_count = (audio_latency_in_seconds * 1000.0f) / SUBMIT_FREQUENCY_TARGET_MS;
|
||||
|
||||
// Now record the size of the individual buffers
|
||||
m_buffer_size = MAX(1024, total_buffer_size / m_buffer_count);
|
||||
|
||||
// Make the buffer a multiple of the format size bytes (rounding up)
|
||||
UINT32 remainder = m_buffer_size % format.nBlockAlign;
|
||||
if (remainder != 0)
|
||||
m_buffer_size += format.nBlockAlign - remainder;
|
||||
|
||||
// get our initial buffer pool and our first buffer
|
||||
m_buffer_pool = std::make_unique<bufferpool>(m_buffer_count + 1, m_buffer_size);
|
||||
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
|
||||
|
||||
osd_printf_verbose(
|
||||
"Sound: XAudio2 created initial buffers. total size: %u, count %u, size each %u\n",
|
||||
(unsigned int)total_buffer_size,
|
||||
(unsigned int)m_buffer_count,
|
||||
(unsigned int)m_buffer_size);
|
||||
|
||||
// reset buffer states
|
||||
m_writepos = 0;
|
||||
m_overflows = 0;
|
||||
m_underflows = 0;
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// create_voices
|
||||
//============================================================
|
||||
|
||||
HRESULT sound_xaudio2::create_voices(const WAVEFORMATEX &format)
|
||||
{
|
||||
assert(m_xAudio2);
|
||||
assert(!m_masterVoice);
|
||||
HRESULT result;
|
||||
|
||||
IXAudio2MasteringVoice *temp_master_voice = nullptr;
|
||||
HR_RET1(
|
||||
m_xAudio2->CreateMasteringVoice(
|
||||
&temp_master_voice,
|
||||
format.nChannels,
|
||||
sample_rate()));
|
||||
|
||||
m_masterVoice = mastering_voice_ptr(temp_master_voice);
|
||||
|
||||
// create the source voice
|
||||
IXAudio2SourceVoice *temp_source_voice = nullptr;
|
||||
HR_RET1(m_xAudio2->CreateSourceVoice(
|
||||
&temp_source_voice,
|
||||
&format,
|
||||
XAUDIO2_VOICE_NOSRC | XAUDIO2_VOICE_NOPITCH,
|
||||
1.0,
|
||||
this));
|
||||
|
||||
m_sourceVoice = src_voice_ptr(temp_source_voice);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// process_audio
|
||||
//============================================================
|
||||
|
||||
// submits audio events on another thread in a loop
|
||||
void sound_xaudio2::process_audio()
|
||||
{
|
||||
BOOL exiting = FALSE;
|
||||
HANDLE hEvents[] = { m_hEventBufferCompleted, m_hEventDataAvailable, m_hEventExiting };
|
||||
while (!exiting)
|
||||
{
|
||||
DWORD wait_result = WaitForMultipleObjects(3, hEvents, FALSE, INFINITE);
|
||||
switch (wait_result)
|
||||
{
|
||||
// Buffer is complete or new data is available
|
||||
case 0:
|
||||
case 1:
|
||||
submit_needed();
|
||||
break;
|
||||
case 2:
|
||||
// exiting
|
||||
exiting = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// submit_needed
|
||||
//============================================================
|
||||
|
||||
// Submits any buffers that have currently been queued,
|
||||
// assuming they are needed based on current queue depth
|
||||
void sound_xaudio2::submit_needed()
|
||||
{
|
||||
XAUDIO2_VOICE_STATE state;
|
||||
m_sourceVoice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
|
||||
|
||||
// If we have a buffer on the queue, no reason to submit
|
||||
if (state.BuffersQueued >= 1)
|
||||
return;
|
||||
|
||||
osd_scoped_lock lock_scope(m_buffer_lock.get());
|
||||
|
||||
// Roll the buffer
|
||||
roll_buffer();
|
||||
|
||||
// Submit the next buffer
|
||||
submit_next_queued();
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// submit_buffer
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::submit_buffer(std::unique_ptr<BYTE[]> audioData, DWORD audioLength)
|
||||
{
|
||||
assert(audioLength != 0);
|
||||
|
||||
XAUDIO2_BUFFER buf = { 0 };
|
||||
buf.AudioBytes = audioLength;
|
||||
buf.pAudioData = audioData.get();
|
||||
buf.PlayBegin = 0;
|
||||
buf.PlayLength = audioLength / m_sample_bytes;
|
||||
buf.Flags = XAUDIO2_END_OF_STREAM;
|
||||
buf.pContext = audioData.get();
|
||||
|
||||
HRESULT result;
|
||||
if (FAILED(result = m_sourceVoice->SubmitSourceBuffer(&buf)))
|
||||
{
|
||||
osd_printf_verbose("Sound: XAudio2 failed to submit source buffer (non-fatal). Error: 0x%X\n", (unsigned int)result);
|
||||
m_buffer_pool->return_to_pool(audioData.release());
|
||||
return;
|
||||
}
|
||||
|
||||
// If we succeeded, relinquish the buffer allocation to the XAudio2 runtime
|
||||
// The buffer will be freed on the OnBufferCompleted callback
|
||||
audioData.release();
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// submit_next_queued
|
||||
//============================================================
|
||||
|
||||
BOOL sound_xaudio2::submit_next_queued()
|
||||
{
|
||||
if (!m_queue.empty())
|
||||
{
|
||||
// Get a reference to the buffer
|
||||
auto buf = &m_queue.front();
|
||||
|
||||
// submit the buffer data
|
||||
submit_buffer(std::move(buf->AudioData), buf->AudioSize);
|
||||
|
||||
// Remove it from the queue
|
||||
assert(buf->AudioSize > 0);
|
||||
m_queue.pop();
|
||||
|
||||
return !m_queue.empty();
|
||||
}
|
||||
|
||||
// queue was already empty
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// roll_buffer
|
||||
//============================================================
|
||||
|
||||
// Queues the current buffer, and gets a new write buffer
|
||||
void sound_xaudio2::roll_buffer()
|
||||
{
|
||||
// Don't queue a buffer if it is empty
|
||||
if (m_writepos == 0)
|
||||
return;
|
||||
|
||||
// Queue the current buffer
|
||||
xaudio2_buffer buf;
|
||||
buf.AudioData = std::move(m_buffer);
|
||||
buf.AudioSize = m_writepos;
|
||||
m_queue.push(std::move(buf));
|
||||
|
||||
// Get a new buffer
|
||||
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
|
||||
m_writepos = 0;
|
||||
|
||||
// We only want to keep a maximum number of buffers at any given time
|
||||
// so remove any from queue greater than MAX_QUEUED_BUFFERS
|
||||
if (m_queue.size() > m_buffer_count)
|
||||
{
|
||||
xaudio2_buffer *next_buffer = &m_queue.front();
|
||||
|
||||
// return the oldest buffer to the pool, and remove it from queue
|
||||
m_buffer_pool->return_to_pool(next_buffer->AudioData.release());
|
||||
m_queue.pop();
|
||||
|
||||
m_overflows++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2")
|
||||
#endif
|
||||
|
||||
MODULE_DEFINITION(SOUND_XAUDIO2, sound_xaudio2)
|
Loading…
Reference in New Issue
Block a user