Audio system overhaul for support of multiple audio drivers.
This commit is contained in:
parent
bbe50cfe26
commit
47c71d702a
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <xenia/apu/audio_driver.h>
|
||||
|
||||
#include <xenia/emulator.h>
|
||||
#include <xenia/cpu/processor.h>
|
||||
#include <xenia/cpu/xenon_thread_state.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::apu;
|
||||
using namespace xe::cpu;
|
||||
|
||||
|
||||
AudioDriver::AudioDriver(Emulator* emulator) :
|
||||
emulator_(emulator), memory_(emulator->memory()) {
|
||||
}
|
||||
|
||||
AudioDriver::~AudioDriver() {
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_APU_AUDIO_DRIVER_H_
|
||||
#define XENIA_APU_AUDIO_DRIVER_H_
|
||||
|
||||
#include <xenia/core.h>
|
||||
#include <xenia/xbox.h>
|
||||
|
||||
|
||||
XEDECLARECLASS1(xe, Emulator);
|
||||
XEDECLARECLASS2(xe, cpu, Processor);
|
||||
XEDECLARECLASS2(xe, cpu, XenonThreadState);
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
|
||||
|
||||
class AudioDriver {
|
||||
public:
|
||||
AudioDriver(Emulator* emulator);
|
||||
virtual ~AudioDriver();
|
||||
|
||||
virtual void SubmitFrame(uint32_t samples_ptr) = 0;
|
||||
|
||||
protected:
|
||||
Emulator* emulator_;
|
||||
Memory* memory_;
|
||||
cpu::Processor* processor_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace apu
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_APU_AUDIO_DRIVER_H_
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include <xenia/apu/audio_system.h>
|
||||
#include <xenia/apu/audio_driver.h>
|
||||
|
||||
#include <xenia/emulator.h>
|
||||
#include <xenia/cpu/processor.h>
|
||||
|
@ -21,12 +22,19 @@ using namespace xe::cpu;
|
|||
|
||||
AudioSystem::AudioSystem(Emulator* emulator) :
|
||||
emulator_(emulator), memory_(emulator->memory()),
|
||||
thread_(0), running_(false),
|
||||
client_({ 0 }) {
|
||||
thread_(0), running_(false) {
|
||||
lock_ = xe_mutex_alloc();
|
||||
memset(clients_, 0, sizeof(clients_));
|
||||
for (size_t i = 0; i < maximum_client_count_; ++i) {
|
||||
client_wait_handles_[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
unused_clients_.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
AudioSystem::~AudioSystem() {
|
||||
for (size_t i = 0; i < maximum_client_count_; ++i) {
|
||||
CloseHandle(client_wait_handles_[i]);
|
||||
}
|
||||
xe_mutex_free(lock_);
|
||||
}
|
||||
|
||||
|
@ -70,27 +78,34 @@ void AudioSystem::ThreadStart() {
|
|||
|
||||
// Main run loop.
|
||||
while (running_) {
|
||||
// Pump worker.
|
||||
// This may block.
|
||||
Pump();
|
||||
|
||||
xe_mutex_lock(lock_);
|
||||
uint32_t client_callback = client_.callback;
|
||||
uint32_t client_callback_arg = client_.wrapped_callback_arg;
|
||||
xe_mutex_unlock(lock_);
|
||||
if (client_callback) {
|
||||
processor->Execute(
|
||||
thread_state_, client_callback, client_callback_arg, 0);
|
||||
} else {
|
||||
Sleep(500);
|
||||
auto result = WaitForMultipleObjectsEx(maximum_client_count_, client_wait_handles_, FALSE, INFINITE, FALSE);
|
||||
if (result == WAIT_FAILED) {
|
||||
DWORD err = GetLastError();
|
||||
XEASSERTALWAYS();
|
||||
}
|
||||
size_t pumped = 0;
|
||||
if (result >= WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + (maximum_client_count_ - 1)) {
|
||||
size_t index = result - WAIT_OBJECT_0;
|
||||
do {
|
||||
xe_mutex_lock(lock_);
|
||||
uint32_t client_callback = clients_[index].callback;
|
||||
uint32_t client_callback_arg = clients_[index].wrapped_callback_arg;
|
||||
xe_mutex_unlock(lock_);
|
||||
if (client_callback) {
|
||||
processor->Execute(thread_state_, client_callback, client_callback_arg, 0);
|
||||
}
|
||||
pumped++;
|
||||
index++;
|
||||
} while (index < maximum_client_count_ && WaitForSingleObject(client_wait_handles_[index], 0) == WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Pump audio system.
|
||||
Pump();
|
||||
if (!pumped) {
|
||||
Sleep(500);
|
||||
}
|
||||
}
|
||||
running_ = false;
|
||||
|
||||
|
@ -109,23 +124,53 @@ void AudioSystem::Shutdown() {
|
|||
memory()->HeapFree(thread_block_, 0);
|
||||
}
|
||||
|
||||
void AudioSystem::RegisterClient(
|
||||
uint32_t callback, uint32_t callback_arg) {
|
||||
// Only support one client for now.
|
||||
XEASSERTZERO(client_.callback);
|
||||
X_STATUS AudioSystem::RegisterClient(
|
||||
uint32_t callback, uint32_t callback_arg, size_t* out_index) {
|
||||
XEASSERTTRUE(unused_clients_.size());
|
||||
xe_mutex_lock(lock_);
|
||||
|
||||
auto index = unused_clients_.front();
|
||||
|
||||
auto wait_handle = client_wait_handles_[index];
|
||||
ResetEvent(wait_handle);
|
||||
AudioDriver* driver;
|
||||
auto result = CreateDriver(index, wait_handle, &driver);
|
||||
if (XFAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
XEASSERTNOTNULL(driver != NULL);
|
||||
|
||||
unused_clients_.pop();
|
||||
|
||||
uint32_t ptr = (uint32_t)memory()->HeapAlloc(0, 0x4, 0);
|
||||
auto mem = memory()->membase();
|
||||
XESETUINT32BE(mem + ptr, callback_arg);
|
||||
|
||||
clients_[index] = { driver, callback, callback_arg, ptr };
|
||||
|
||||
if (out_index) {
|
||||
*out_index = index;
|
||||
}
|
||||
|
||||
xe_mutex_unlock(lock_);
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
|
||||
xe_mutex_lock(lock_);
|
||||
client_ = { callback, callback_arg, ptr };
|
||||
XEASSERTTRUE(index < maximum_client_count_);
|
||||
XEASSERTTRUE(clients_[index].driver != NULL);
|
||||
(clients_[index].driver)->SubmitFrame(samples_ptr);
|
||||
ResetEvent(client_wait_handles_[index]);
|
||||
xe_mutex_unlock(lock_);
|
||||
}
|
||||
|
||||
void AudioSystem::UnregisterClient() {
|
||||
void AudioSystem::UnregisterClient(size_t index) {
|
||||
xe_mutex_lock(lock_);
|
||||
client_ = { 0 };
|
||||
XEASSERTTRUE(index < maximum_client_count_);
|
||||
DestroyDriver(clients_[index].driver);
|
||||
clients_[index] = { 0 };
|
||||
unused_clients_.push(index);
|
||||
xe_mutex_unlock(lock_);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,12 @@
|
|||
#include <xenia/core.h>
|
||||
#include <xenia/xbox.h>
|
||||
|
||||
#include <queue>
|
||||
|
||||
XEDECLARECLASS1(xe, Emulator);
|
||||
XEDECLARECLASS2(xe, cpu, Processor);
|
||||
XEDECLARECLASS2(xe, cpu, XenonThreadState);
|
||||
|
||||
XEDECLARECLASS2(xe, apu, AudioDriver);
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
|
@ -34,9 +35,12 @@ public:
|
|||
virtual X_STATUS Setup();
|
||||
virtual void Shutdown();
|
||||
|
||||
void RegisterClient(uint32_t callback, uint32_t callback_arg);
|
||||
void UnregisterClient();
|
||||
virtual void SubmitFrame(uint32_t samples_ptr) = 0;
|
||||
X_STATUS RegisterClient(uint32_t callback, uint32_t callback_arg, size_t* out_index);
|
||||
void UnregisterClient(size_t index);
|
||||
void SubmitFrame(size_t index, uint32_t samples_ptr);
|
||||
|
||||
virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver) = 0;
|
||||
virtual void DestroyDriver(AudioDriver* driver) = 0;
|
||||
|
||||
bool HandlesRegister(uint64_t addr);
|
||||
virtual uint64_t ReadRegister(uint64_t addr);
|
||||
|
@ -44,7 +48,6 @@ public:
|
|||
|
||||
protected:
|
||||
virtual void Initialize();
|
||||
virtual void Pump() = 0;
|
||||
|
||||
private:
|
||||
static void ThreadStartThunk(AudioSystem* this_ptr) {
|
||||
|
@ -76,11 +79,17 @@ protected:
|
|||
bool running_;
|
||||
|
||||
xe_mutex_t* lock_;
|
||||
|
||||
static const size_t maximum_client_count_ = 8;
|
||||
|
||||
struct {
|
||||
AudioDriver* driver;
|
||||
uint32_t callback;
|
||||
uint32_t callback_arg;
|
||||
uint32_t wrapped_callback_arg;
|
||||
} client_;
|
||||
} clients_[maximum_client_count_];
|
||||
HANDLE client_wait_handles_[maximum_client_count_];
|
||||
std::queue<size_t> unused_clients_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -24,18 +24,10 @@ NopAudioSystem::NopAudioSystem(Emulator* emulator) :
|
|||
NopAudioSystem::~NopAudioSystem() {
|
||||
}
|
||||
|
||||
void NopAudioSystem::Initialize() {
|
||||
AudioSystem::Initialize();
|
||||
X_STATUS NopAudioSystem::CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver) {
|
||||
return X_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
void NopAudioSystem::Pump() {
|
||||
//
|
||||
}
|
||||
|
||||
void NopAudioSystem::SubmitFrame(uint32_t frame_ptr) {
|
||||
// Process samples! They are big-endian floats.
|
||||
}
|
||||
|
||||
void NopAudioSystem::Shutdown() {
|
||||
AudioSystem::Shutdown();
|
||||
void NopAudioSystem::DestroyDriver(AudioDriver* driver) {
|
||||
XEASSERTALWAYS();
|
||||
}
|
||||
|
|
|
@ -26,15 +26,8 @@ public:
|
|||
NopAudioSystem(Emulator* emulator);
|
||||
virtual ~NopAudioSystem();
|
||||
|
||||
virtual void Shutdown();
|
||||
|
||||
virtual void SubmitFrame(uint32_t frame_ptr);
|
||||
|
||||
protected:
|
||||
virtual void Initialize();
|
||||
virtual void Pump();
|
||||
|
||||
private:
|
||||
virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver);
|
||||
virtual void DestroyDriver(AudioDriver* driver);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
'apu-private.h',
|
||||
'apu.cc',
|
||||
'apu.h',
|
||||
'audio_driver.cc',
|
||||
'audio_driver.h',
|
||||
'audio_system.cc',
|
||||
'audio_system.h',
|
||||
],
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
'xaudio2_apu-private.h',
|
||||
'xaudio2_apu.cc',
|
||||
'xaudio2_apu.h',
|
||||
'xaudio2_audio_driver.cc',
|
||||
'xaudio2_audio_driver.h',
|
||||
'xaudio2_audio_system.cc',
|
||||
'xaudio2_audio_system.h',
|
||||
],
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <xenia/apu/xaudio2/xaudio2_audio_driver.h>
|
||||
|
||||
#include <xenia/apu/apu-private.h>
|
||||
|
||||
#include <xenia/emulator.h>
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::apu;
|
||||
using namespace xe::apu::xaudio2;
|
||||
|
||||
|
||||
class XAudio2AudioDriver::VoiceCallback : public IXAudio2VoiceCallback {
|
||||
public:
|
||||
VoiceCallback(HANDLE wait_handle) : wait_handle_(wait_handle) {}
|
||||
~VoiceCallback() {}
|
||||
|
||||
void OnStreamEnd() {}
|
||||
void OnVoiceProcessingPassEnd() {}
|
||||
void OnVoiceProcessingPassStart(uint32_t samples_required) {}
|
||||
void OnBufferEnd(void* context);
|
||||
void OnBufferStart(void* context) {}
|
||||
void OnLoopEnd(void* context) {}
|
||||
void OnVoiceError(void* context, HRESULT result) {}
|
||||
|
||||
private:
|
||||
HANDLE wait_handle_;
|
||||
};
|
||||
|
||||
void XAudio2AudioDriver::VoiceCallback::OnBufferEnd(void* context) {
|
||||
SetEvent(wait_handle_);
|
||||
}
|
||||
|
||||
XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE wait) :
|
||||
audio_(0), mastering_voice_(0), pcm_voice_(0),
|
||||
wait_handle_(wait), voice_callback_(0),
|
||||
AudioDriver(emulator) {
|
||||
}
|
||||
|
||||
XAudio2AudioDriver::~XAudio2AudioDriver() {
|
||||
}
|
||||
|
||||
const DWORD ChannelMasks[] =
|
||||
{
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY,
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
|
||||
0, // TODO: fixme
|
||||
};
|
||||
|
||||
void XAudio2AudioDriver::Initialize() {
|
||||
HRESULT hr;
|
||||
|
||||
voice_callback_ = new VoiceCallback(wait_handle_);
|
||||
|
||||
hr = XAudio2Create(&audio_, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("XAudio2Create failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
XAUDIO2_DEBUG_CONFIGURATION config;
|
||||
config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
|
||||
config.BreakMask = 0;
|
||||
config.LogThreadID = FALSE;
|
||||
config.LogTiming = TRUE;
|
||||
config.LogFunctionName = TRUE;
|
||||
config.LogFileline = TRUE;
|
||||
audio_->SetDebugConfiguration(&config);
|
||||
|
||||
hr = audio_->CreateMasteringVoice(&mastering_voice_, frame_channels_, 48000);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CreateMasteringVoice failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
WAVEFORMATIEEEFLOATEX waveformat;
|
||||
|
||||
waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
waveformat.Format.nChannels = frame_channels_;
|
||||
waveformat.Format.nSamplesPerSec = 48000;
|
||||
waveformat.Format.wBitsPerSample = 32;
|
||||
waveformat.Format.nBlockAlign = (waveformat.Format.nChannels * waveformat.Format.wBitsPerSample) / 8;
|
||||
waveformat.Format.nAvgBytesPerSec = waveformat.Format.nSamplesPerSec * waveformat.Format.nBlockAlign;
|
||||
waveformat.Format.cbSize = sizeof(waveformat) - sizeof(WAVEFORMATEX);
|
||||
|
||||
waveformat.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
waveformat.Samples.wValidBitsPerSample = waveformat.Format.wBitsPerSample;
|
||||
waveformat.dwChannelMask = ChannelMasks[waveformat.Format.nChannels];
|
||||
|
||||
hr = audio_->CreateSourceVoice(&pcm_voice_, &waveformat.Format,
|
||||
XAUDIO2_VOICE_NOSRC, 1.0f,
|
||||
voice_callback_);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CreateSourceVoice failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = pcm_voice_->Start();
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("Start failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
SetEvent(wait_handle_);
|
||||
}
|
||||
|
||||
void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
|
||||
// Process samples! They are big-endian floats.
|
||||
HRESULT hr;
|
||||
|
||||
auto input_frame = reinterpret_cast<float*>(emulator_->memory()->membase() + frame_ptr);
|
||||
auto output_frame = reinterpret_cast<float*>(frame_);
|
||||
auto interleave_channels = frame_channels_;
|
||||
|
||||
bool burp = false;
|
||||
// interleave the data
|
||||
for (int index = 0, o = 0; index < channel_samples_; ++index) {
|
||||
for (int channel = 0, table = 0; channel < interleave_channels; ++channel, table += channel_samples_) {
|
||||
output_frame[o++] = XESWAPF32BE(input_frame[table + index]);
|
||||
}
|
||||
}
|
||||
|
||||
XAUDIO2_BUFFER buffer;
|
||||
buffer.Flags = 0;
|
||||
buffer.pAudioData = reinterpret_cast<BYTE*>(&frame_[0]);
|
||||
buffer.AudioBytes = sizeof(frame_);
|
||||
buffer.PlayBegin = 0;
|
||||
buffer.PlayLength = channel_samples_;
|
||||
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
||||
buffer.LoopLength = 0;
|
||||
buffer.LoopCount = 0;
|
||||
buffer.pContext = 0;
|
||||
hr = pcm_voice_->SubmitSourceBuffer(&buffer);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("SubmitSourceBuffer failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2AudioDriver::Shutdown() {
|
||||
pcm_voice_->Stop();
|
||||
pcm_voice_->DestroyVoice();
|
||||
pcm_voice_ = NULL;
|
||||
|
||||
mastering_voice_->DestroyVoice();
|
||||
mastering_voice_ = NULL;
|
||||
|
||||
audio_->StopEngine();
|
||||
XESAFERELEASE(audio_);
|
||||
|
||||
delete voice_callback_;
|
||||
CloseHandle(wait_handle_);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||
|
||||
#include <xenia/core.h>
|
||||
|
||||
#include <xenia/apu/audio_driver.h>
|
||||
#include <xenia/apu/xaudio2/xaudio2_apu-private.h>
|
||||
|
||||
#include <xaudio2.h>
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
namespace xaudio2 {
|
||||
|
||||
|
||||
class XAudio2AudioDriver : public AudioDriver {
|
||||
public:
|
||||
XAudio2AudioDriver(Emulator* emulator, HANDLE wait);
|
||||
virtual ~XAudio2AudioDriver();
|
||||
|
||||
virtual void Initialize();
|
||||
virtual void SubmitFrame(uint32_t frame_ptr);
|
||||
virtual void Shutdown();
|
||||
|
||||
private:
|
||||
IXAudio2* audio_;
|
||||
IXAudio2MasteringVoice* mastering_voice_;
|
||||
IXAudio2SourceVoice* pcm_voice_;
|
||||
static const int frame_channels_ = 6;
|
||||
static const int channel_samples_ = 256;
|
||||
float frame_[frame_channels_ * channel_samples_];
|
||||
HANDLE wait_handle_;
|
||||
|
||||
class VoiceCallback;
|
||||
VoiceCallback* voice_callback_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace xaudio2
|
||||
} // namespace apu
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
|
@ -8,183 +8,40 @@
|
|||
*/
|
||||
|
||||
#include <xenia/apu/xaudio2/xaudio2_audio_system.h>
|
||||
#include <xenia/apu/xaudio2/xaudio2_audio_driver.h>
|
||||
|
||||
#include <xenia/apu/apu-private.h>
|
||||
|
||||
#include <xenia/emulator.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::apu;
|
||||
using namespace xe::apu::xaudio2;
|
||||
|
||||
|
||||
class XAudio2AudioSystem::VoiceCallback : public IXAudio2VoiceCallback {
|
||||
public:
|
||||
VoiceCallback(HANDLE wait_handle) : wait_handle_(wait_handle) {}
|
||||
~VoiceCallback() {}
|
||||
|
||||
void OnStreamEnd() {}
|
||||
void OnVoiceProcessingPassEnd() {}
|
||||
void OnVoiceProcessingPassStart(UINT32 SamplesRequired) {}
|
||||
void OnBufferEnd(void * pBufferContext) {
|
||||
SetEvent(wait_handle_);
|
||||
}
|
||||
void OnBufferStart(void * pBufferContext) {}
|
||||
void OnLoopEnd(void * pBufferContext) {}
|
||||
void OnVoiceError(void * pBufferContext, HRESULT Error) {}
|
||||
|
||||
private:
|
||||
HANDLE wait_handle_;
|
||||
};
|
||||
|
||||
|
||||
XAudio2AudioSystem::XAudio2AudioSystem(Emulator* emulator) :
|
||||
audio_(0), mastering_voice_(0), pcm_voice_(0),
|
||||
active_channels_(0),
|
||||
wait_handle_(NULL), voice_callback_(0),
|
||||
AudioSystem(emulator) {
|
||||
AudioSystem(emulator) {
|
||||
}
|
||||
|
||||
XAudio2AudioSystem::~XAudio2AudioSystem() {
|
||||
}
|
||||
|
||||
const DWORD ChannelMasks[] =
|
||||
{
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY,
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
0, // TODO: fixme
|
||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
|
||||
0, // TODO: fixme
|
||||
};
|
||||
|
||||
void XAudio2AudioSystem::Initialize() {
|
||||
AudioSystem::Initialize();
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
wait_handle_ = CreateEvent(NULL, TRUE, TRUE, NULL);
|
||||
|
||||
voice_callback_ = new VoiceCallback(wait_handle_);
|
||||
|
||||
hr = XAudio2Create(&audio_, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("XAudio2Create failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
active_channels_ = 6;
|
||||
|
||||
XAUDIO2_DEBUG_CONFIGURATION config;
|
||||
config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
|
||||
config.BreakMask = 0;
|
||||
config.LogThreadID = FALSE;
|
||||
config.LogTiming = TRUE;
|
||||
config.LogFunctionName = TRUE;
|
||||
config.LogFileline = TRUE;
|
||||
audio_->SetDebugConfiguration(&config);
|
||||
|
||||
hr = audio_->CreateMasteringVoice(&mastering_voice_, active_channels_, 48000);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CreateMasteringVoice failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
WAVEFORMATIEEEFLOATEX waveformat;
|
||||
|
||||
//waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
waveformat.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
waveformat.Format.nChannels = active_channels_;
|
||||
waveformat.Format.nSamplesPerSec = 48000;
|
||||
waveformat.Format.wBitsPerSample = 32;
|
||||
waveformat.Format.nBlockAlign = (waveformat.Format.nChannels * waveformat.Format.wBitsPerSample) / 8;
|
||||
waveformat.Format.nAvgBytesPerSec = waveformat.Format.nSamplesPerSec * waveformat.Format.nBlockAlign;
|
||||
waveformat.Format.cbSize = sizeof(waveformat)-sizeof(WAVEFORMATEX);
|
||||
|
||||
waveformat.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
waveformat.Samples.wValidBitsPerSample = waveformat.Format.wBitsPerSample;
|
||||
waveformat.dwChannelMask = ChannelMasks[waveformat.Format.nChannels];
|
||||
|
||||
hr = audio_->CreateSourceVoice(&pcm_voice_, &waveformat.Format,
|
||||
0,
|
||||
XAUDIO2_DEFAULT_FREQ_RATIO, voice_callback_);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CreateSourceVoice failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
|
||||
pcm_voice_->Start();
|
||||
}
|
||||
|
||||
void XAudio2AudioSystem::Pump() {
|
||||
XAUDIO2_VOICE_STATE state;
|
||||
pcm_voice_->GetState(&state);
|
||||
auto n = state.BuffersQueued;
|
||||
if (n > 0) {
|
||||
// Only allow one buffer to be queued at once. We only have one static
|
||||
// store of data, and if we called back the game audio driver it would
|
||||
// overwrite it.
|
||||
//ResetEvent(wait_handle_);
|
||||
}
|
||||
|
||||
WaitForSingleObject(wait_handle_, INFINITE);
|
||||
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE wait, AudioDriver** out_driver) {
|
||||
XEASSERTNOTNULL(out_driver);
|
||||
auto driver = new XAudio2AudioDriver(emulator_, wait);
|
||||
driver->Initialize();
|
||||
*out_driver = driver;
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void XAudio2AudioSystem::SubmitFrame(uint32_t frame_ptr) {
|
||||
ResetEvent(wait_handle_);
|
||||
|
||||
// Process samples! They are big-endian floats.
|
||||
HRESULT hr;
|
||||
|
||||
auto input_frame = reinterpret_cast<float*>(emulator_->memory()->membase() + frame_ptr);
|
||||
auto output_frame = reinterpret_cast<float*>(frame_);
|
||||
|
||||
// interleave the data
|
||||
for (int index = 0, o = 0; index < 256; ++index) {
|
||||
for (int channel = 0, table = 0;
|
||||
channel < 6 && channel < active_channels_;
|
||||
++channel, table += 256) {
|
||||
output_frame[o++] = XESWAPF32BE(input_frame[table + index]);
|
||||
}
|
||||
}
|
||||
|
||||
XAUDIO2_BUFFER buffer;
|
||||
buffer.Flags = 0;
|
||||
buffer.pAudioData = reinterpret_cast<BYTE*>(frame_);
|
||||
buffer.AudioBytes = sizeof(frame_);
|
||||
buffer.PlayBegin = 0;
|
||||
buffer.PlayLength = 256;
|
||||
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
||||
buffer.LoopLength = 0;
|
||||
buffer.LoopCount = 0;
|
||||
buffer.pContext = 0;
|
||||
hr = pcm_voice_->SubmitSourceBuffer(&buffer);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("SubmitSourceBuffer failed with %.8X", hr);
|
||||
XEASSERTALWAYS();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2AudioSystem::Shutdown() {
|
||||
pcm_voice_->Stop();
|
||||
pcm_voice_->DestroyVoice();
|
||||
pcm_voice_ = NULL;
|
||||
|
||||
mastering_voice_->DestroyVoice();
|
||||
mastering_voice_ = NULL;
|
||||
|
||||
audio_->StopEngine();
|
||||
XESAFERELEASE(audio_);
|
||||
|
||||
delete voice_callback_;
|
||||
CloseHandle(wait_handle_);
|
||||
|
||||
AudioSystem::Shutdown();
|
||||
void XAudio2AudioSystem::DestroyDriver(AudioDriver* driver) {
|
||||
XEASSERTNOTNULL(driver);
|
||||
auto xdriver = static_cast<XAudio2AudioDriver*>(driver);
|
||||
xdriver->Shutdown();
|
||||
XEASSERTNOTNULL(xdriver);
|
||||
delete xdriver;
|
||||
}
|
||||
|
|
|
@ -28,25 +28,11 @@ public:
|
|||
XAudio2AudioSystem(Emulator* emulator);
|
||||
virtual ~XAudio2AudioSystem();
|
||||
|
||||
virtual void Shutdown();
|
||||
|
||||
virtual void SubmitFrame(uint32_t frame_ptr);
|
||||
virtual X_RESULT CreateDriver(size_t index, HANDLE wait, AudioDriver** out_driver);
|
||||
virtual void DestroyDriver(AudioDriver* driver);
|
||||
|
||||
protected:
|
||||
virtual void Initialize();
|
||||
virtual void Pump();
|
||||
|
||||
private:
|
||||
IXAudio2* audio_;
|
||||
IXAudio2MasteringVoice* mastering_voice_;
|
||||
IXAudio2SourceVoice* pcm_voice_;
|
||||
int active_channels_;
|
||||
static const int frame_channels_ = 6;
|
||||
float frame_[frame_channels_ * 256];
|
||||
HANDLE wait_handle_;
|
||||
|
||||
class VoiceCallback;
|
||||
VoiceCallback* voice_callback_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -114,10 +114,16 @@ SHIM_CALL XAudioRegisterRenderDriverClient_shim(
|
|||
callback_ptr, callback, callback_arg, driver_ptr);
|
||||
|
||||
auto audio_system = state->emulator()->audio_system();
|
||||
audio_system->RegisterClient(callback, callback_arg);
|
||||
|
||||
SHIM_SET_MEM_32(driver_ptr, 0xAADD1100);
|
||||
size_t index;
|
||||
auto result = audio_system->RegisterClient(callback, callback_arg, &index);
|
||||
if (XFAILED(result)) {
|
||||
SHIM_SET_RETURN_32(result);
|
||||
return;
|
||||
}
|
||||
|
||||
XEASSERTTRUE(!(index & ~0x0000FFFF));
|
||||
SHIM_SET_MEM_32(driver_ptr, 0x41550000 | (index & 0x0000FFFF));
|
||||
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -130,11 +136,10 @@ SHIM_CALL XAudioUnregisterRenderDriverClient_shim(
|
|||
"XAudioUnregisterRenderDriverClient(%.8X)",
|
||||
driver_ptr);
|
||||
|
||||
XEASSERT(driver_ptr == 0xAADD1100);
|
||||
XEASSERT((driver_ptr & 0xFFFF0000) == 0x41550000);
|
||||
|
||||
auto audio_system = state->emulator()->audio_system();
|
||||
audio_system->UnregisterClient();
|
||||
|
||||
audio_system->UnregisterClient(driver_ptr & 0x0000FFFF);
|
||||
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -148,10 +153,10 @@ SHIM_CALL XAudioSubmitRenderDriverFrame_shim(
|
|||
"XAudioSubmitRenderDriverFrame(%.8X, %.8X)",
|
||||
driver_ptr, samples_ptr);
|
||||
|
||||
XEASSERT(driver_ptr == 0xAADD1100);
|
||||
XEASSERT((driver_ptr & 0xFFFF0000) == 0x41550000);
|
||||
|
||||
auto audio_system = state->emulator()->audio_system();
|
||||
audio_system->SubmitFrame(samples_ptr);
|
||||
audio_system->SubmitFrame(driver_ptr & 0x0000FFFF, samples_ptr);
|
||||
|
||||
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue