spu2-x:windows: Add XAudio2 2.8 backend

On Windows Vista and 7, the XAudio2 2.7 backend will still be used.
Windows 8, 8.1 and 10 users will use XAudio2 2.8/2.9 (depending on OS).
This commit is contained in:
Jonathan Li 2016-02-28 21:41:04 +00:00
parent 5344c3f121
commit e7745d13f4
7 changed files with 437 additions and 1 deletions

View File

@ -160,6 +160,20 @@ EXPORT_C_(s32) SPU2test()
{
if( !CheckSSE() ) return -1;
#ifdef _WIN32
if (IsWindows8OrGreater())
{
for (int n = 0; mods[n] != nullptr; ++n)
{
if (mods[n] == XAudio2_27_Out)
{
mods[n] = XAudio2Out;
break;
}
}
}
#endif
ReadSettings();
if( SndBuffer::Test() != 0 )
{

View File

@ -671,6 +671,7 @@ public:
extern SndOutModule* WaveOut;
extern SndOutModule* DSoundOut;
extern SndOutModule* XAudio2_27_Out;
extern SndOutModule* XAudio2Out;
#endif
extern SndOutModule* PortaudioOut;
#if defined(SPU2X_SDL) || defined(SPU2X_SDL2)

View File

@ -225,6 +225,18 @@ EXPORT_C_(void) s2r_replay(HWND hwnd, HINSTANCE hinst, LPSTR filename, int nCmdS
conprintf("Playing %s file on %x...",filename,hwnd);
if (IsWindows8OrGreater())
{
for (int n = 0; mods[n] != nullptr; ++n)
{
if (mods[n] == XAudio2_27_Out)
{
mods[n] = XAudio2Out;
break;
}
}
}
#endif
// load file

View File

@ -127,7 +127,10 @@ void ReadSettings()
// let's use xaudio2 until this is sorted (rama)
// CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, PortaudioOut->GetIdent());
CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, XAudio2_27_Out->GetIdent());
if (IsWindows8OrGreater())
CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, XAudio2Out->GetIdent());
else
CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, XAudio2_27_Out->GetIdent());
// find the driver index of this module:
OutputModule = FindOutputModuleById( omodid );

View File

@ -0,0 +1,402 @@
/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
* Developed and maintained by the Pcsx2 Development Team.
*
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
*
* SPU2-X is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Global.h"
#include "Dialogs.h"
#include <atlcomcli.h>
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0602
#include <xaudio2.h>
namespace Exception
{
class XAudio2Error : public std::runtime_error
{
protected:
static const char* SomeKindaErrorString(HRESULT hr)
{
switch (hr) {
case XAUDIO2_E_INVALID_CALL:
return "Invalid call for the XA2 object state.";
case XAUDIO2_E_DEVICE_INVALIDATED:
return "Device is unavailable, unplugged, unsupported, or has been consumed by The Nothing.";
}
return "Unknown error code!";
}
public:
const HRESULT ErrorCode;
std::string m_Message;
const char* CMessage() const
{
return m_Message.c_str();
}
virtual ~XAudio2Error() throw() {}
XAudio2Error(const HRESULT result, const std::string& msg) :
runtime_error(msg),
ErrorCode(result),
m_Message()
{
char omg[1024];
sprintf_s(omg, "%s (code 0x%x)\n\n%s", what(), ErrorCode, SomeKindaErrorString(ErrorCode));
m_Message = omg;
}
};
}
static const double SndOutNormalizer = (double)(1UL << (SndOutVolumeShift + 16));
class XAudio2Mod : public SndOutModule
{
private:
static const int PacketsPerBuffer = 8;
static const int MAX_BUFFER_COUNT = 3;
class BaseStreamingVoice : public IXAudio2VoiceCallback
{
protected:
IXAudio2SourceVoice* pSourceVoice;
s16* qbuffer;
const uint m_nBuffers;
const uint m_nChannels;
const uint m_BufferSize;
const uint m_BufferSizeBytes;
CRITICAL_SECTION cs;
public:
int GetEmptySampleCount()
{
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState(&state);
return state.SamplesPlayed & (m_BufferSize - 1);
}
virtual ~BaseStreamingVoice()
{
}
BaseStreamingVoice(uint numChannels) :
m_nBuffers(Config_XAudio2.NumBuffers),
m_nChannels(numChannels),
m_BufferSize(SndOutPacketSize * m_nChannels * PacketsPerBuffer),
m_BufferSizeBytes(m_BufferSize * sizeof(s16))
{
}
virtual void Init(IXAudio2* pXAudio2) = 0;
protected:
// Several things must be initialized separate of the constructor, due to the fact that
// virtual calls can't be made from the constructor's context.
void _init(IXAudio2* pXAudio2, uint chanConfig)
{
WAVEFORMATEXTENSIBLE wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEXTENSIBLE));
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfx.Format.nSamplesPerSec = SampleRate;
wfx.Format.nChannels = m_nChannels;
wfx.Format.wBitsPerSample = 16;
wfx.Format.nBlockAlign = wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8;
wfx.Format.nAvgBytesPerSec = SampleRate * wfx.Format.nBlockAlign;
wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfx.Samples.wValidBitsPerSample = 16;
wfx.dwChannelMask = chanConfig;
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//
// Create an XAudio2 voice to stream this wave
//
HRESULT hr;
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx,
XAUDIO2_VOICE_NOSRC, 1.0f, this))) {
throw Exception::XAudio2Error(hr, "XAudio2 CreateSourceVoice failure.");
}
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
pSourceVoice->FlushSourceBuffers();
pSourceVoice->Start(0, 0);
qbuffer = new s16[m_nBuffers * m_BufferSize];
ZeroMemory(qbuffer, m_BufferSizeBytes * m_nBuffers);
// Start some buffers.
for (uint i = 0; i < m_nBuffers; i++) {
XAUDIO2_BUFFER buf = { 0 };
buf.AudioBytes = m_BufferSizeBytes;
buf.pContext = &qbuffer[i*m_BufferSize];
buf.pAudioData = (BYTE*)buf.pContext;
pSourceVoice->SubmitSourceBuffer(&buf);
}
LeaveCriticalSection(&cs);
}
STDMETHOD_(void, OnVoiceProcessingPassStart) () {}
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
STDMETHOD_(void, OnStreamEnd) () {}
STDMETHOD_(void, OnBufferStart) (void*) {}
STDMETHOD_(void, OnLoopEnd) (void*) {}
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) {}
};
template< typename T >
class StreamingVoice : public BaseStreamingVoice
{
public:
StreamingVoice(IXAudio2* pXAudio2) :
BaseStreamingVoice(sizeof(T) / sizeof(s16))
{
}
virtual ~StreamingVoice()
{
IXAudio2SourceVoice* killMe = pSourceVoice;
pSourceVoice = NULL;
killMe->FlushSourceBuffers();
killMe->DestroyVoice();
EnterCriticalSection(&cs);
safe_delete_array(qbuffer);
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
}
void Init(IXAudio2* pXAudio2)
{
int chanMask = 0;
switch (m_nChannels) {
case 1: chanMask |= SPEAKER_FRONT_CENTER; break;
case 2: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break;
case 3: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY; break;
case 4: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
case 5: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
case 6: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY; break;
case 8: chanMask |= SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | SPEAKER_LOW_FREQUENCY; break;
}
_init(pXAudio2, chanMask);
}
protected:
STDMETHOD_(void, OnBufferEnd) (void* context)
{
EnterCriticalSection(&cs);
// All of these checks are necessary because XAudio2 is wonky shizat.
if (pSourceVoice == NULL || context == NULL) {
LeaveCriticalSection(&cs);
return;
}
T* qb = (T*)context;
for (int p = 0; p < PacketsPerBuffer; p++, qb += SndOutPacketSize)
SndBuffer::ReadSamples(qb);
XAUDIO2_BUFFER buf = { 0 };
buf.AudioBytes = m_BufferSizeBytes;
buf.pAudioData = (BYTE*)context;
buf.pContext = context;
pSourceVoice->SubmitSourceBuffer(&buf);
LeaveCriticalSection(&cs);
}
};
HMODULE xAudio2DLL;
decltype(&XAudio2Create) pXAudio2Create;
CComPtr<IXAudio2> pXAudio2;
IXAudio2MasteringVoice* pMasteringVoice;
BaseStreamingVoice* voiceContext;
public:
s32 Init()
{
HRESULT hr;
jASSUME(pXAudio2 == NULL);
xAudio2DLL = LoadLibraryEx(XAUDIO2_DLL, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (xAudio2DLL == nullptr)
throw std::runtime_error("Could not load " XAUDIO2_DLL_A ". Error code:" + std::to_string(GetLastError()));
pXAudio2Create = reinterpret_cast<decltype(&XAudio2Create)>(GetProcAddress(xAudio2DLL, "XAudio2Create"));
if (pXAudio2Create == nullptr)
throw std::runtime_error("XAudio2Create not found. Error code: " + std::to_string(GetLastError()));
//
// Initialize XAudio2
//
CoInitializeEx(NULL, COINIT_MULTITHREADED);
try {
if (FAILED(hr = pXAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR)))
throw Exception::XAudio2Error(hr, "Failed to init XAudio2 engine. Error Details:");
// Stereo Expansion was planned to grab the currently configured number of
// Speakers from Windows's audio config.
// This doesn't always work though, so let it be a user configurable option.
int speakers;
switch (numSpeakers) // speakers = (numSpeakers + 1) *2; ?
{
case 0: speakers = 2; break; // Stereo
case 1: speakers = 4; break; // Quadrafonic
case 2: speakers = 6; break; // Surround 5.1
case 3: speakers = 8; break; // Surround 7.1
default: speakers = 2;
}
//
// Create a mastering voice
//
if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice, speakers, SampleRate))) {
SysMessage("Failed creating mastering voice: %#X\n", hr);
CoUninitialize();
return -1;
}
switch (speakers) {
case 2:
ConLog("* SPU2 > Using normal 2 speaker stereo output.\n");
voiceContext = new StreamingVoice<StereoOut16>(pXAudio2);
break;
case 3:
ConLog("* SPU2 > 2.1 speaker expansion enabled.\n");
voiceContext = new StreamingVoice<Stereo21Out16>(pXAudio2);
break;
case 4:
ConLog("* SPU2 > 4 speaker expansion enabled [quadraphenia]\n");
voiceContext = new StreamingVoice<Stereo40Out16>(pXAudio2);
break;
case 5:
ConLog("* SPU2 > 4.1 speaker expansion enabled.\n");
voiceContext = new StreamingVoice<Stereo41Out16>(pXAudio2);
break;
case 6:
case 7:
switch (dplLevel) {
case 0:
ConLog("* SPU2 > 5.1 speaker expansion enabled.\n");
voiceContext = new StreamingVoice<Stereo51Out16>(pXAudio2); //"normal" stereo upmix
break;
case 1:
ConLog("* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.\n");
voiceContext = new StreamingVoice<Stereo51Out16Dpl>(pXAudio2); // basic Dpl decoder without rear stereo balancing
break;
case 2:
ConLog("* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.\n");
voiceContext = new StreamingVoice<Stereo51Out16DplII>(pXAudio2); //gigas PLII
break;
}
break;
default: // anything 8 or more gets the 7.1 treatment!
ConLog("* SPU2 > 7.1 speaker expansion enabled.\n");
voiceContext = new StreamingVoice<Stereo51Out16>(pXAudio2);
break;
}
voiceContext->Init(pXAudio2);
} catch (Exception::XAudio2Error& ex) {
SysMessage(ex.CMessage());
pXAudio2.Release();
CoUninitialize();
return -1;
}
return 0;
}
void Close()
{
safe_delete(voiceContext);
voiceContext = NULL;
if (pMasteringVoice != NULL)
pMasteringVoice->DestroyVoice();
pMasteringVoice = NULL;
pXAudio2.Release();
CoUninitialize();
if (xAudio2DLL) {
FreeLibrary(xAudio2DLL);
xAudio2DLL = nullptr;
pXAudio2Create = nullptr;
}
}
virtual void Configure(uptr parent)
{
}
s32 Test() const
{
return 0;
}
int GetEmptySampleCount()
{
if (voiceContext == NULL) return 0;
return voiceContext->GetEmptySampleCount();
}
const wchar_t* GetIdent() const
{
return L"xaudio2";
}
const wchar_t* GetLongName() const
{
return L"XAudio 2 (Recommended)";
}
void ReadSettings()
{
}
void SetApiSettings(wxString api)
{
}
void WriteSettings() const
{
}
} static XA2;
SndOutModule *XAudio2Out = &XA2;

View File

@ -342,6 +342,7 @@
<ClCompile Include="..\Timestretcher.cpp" />
<ClCompile Include="SndOut_DSound.cpp" />
<ClCompile Include="SndOut_waveOut.cpp" />
<ClCompile Include="SndOut_XAudio2.cpp" />
<ClCompile Include="SndOut_XAudio2_27.cpp">
<AdditionalIncludeDirectories>$(DXSDK_DIR)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>

View File

@ -213,6 +213,9 @@
<ClCompile Include="..\DplIIdecoder.cpp">
<Filter>Source Files\Sound Output</Filter>
</ClCompile>
<ClCompile Include="SndOut_XAudio2.cpp">
<Filter>Source Files\Sound Output\Windows</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LGPL.txt">