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_system.h>
|
||||||
|
#include <xenia/apu/audio_driver.h>
|
||||||
|
|
||||||
#include <xenia/emulator.h>
|
#include <xenia/emulator.h>
|
||||||
#include <xenia/cpu/processor.h>
|
#include <xenia/cpu/processor.h>
|
||||||
|
@ -21,12 +22,19 @@ using namespace xe::cpu;
|
||||||
|
|
||||||
AudioSystem::AudioSystem(Emulator* emulator) :
|
AudioSystem::AudioSystem(Emulator* emulator) :
|
||||||
emulator_(emulator), memory_(emulator->memory()),
|
emulator_(emulator), memory_(emulator->memory()),
|
||||||
thread_(0), running_(false),
|
thread_(0), running_(false) {
|
||||||
client_({ 0 }) {
|
|
||||||
lock_ = xe_mutex_alloc();
|
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() {
|
AudioSystem::~AudioSystem() {
|
||||||
|
for (size_t i = 0; i < maximum_client_count_; ++i) {
|
||||||
|
CloseHandle(client_wait_handles_[i]);
|
||||||
|
}
|
||||||
xe_mutex_free(lock_);
|
xe_mutex_free(lock_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,27 +78,34 @@ void AudioSystem::ThreadStart() {
|
||||||
|
|
||||||
// Main run loop.
|
// Main run loop.
|
||||||
while (running_) {
|
while (running_) {
|
||||||
// Pump worker.
|
auto result = WaitForMultipleObjectsEx(maximum_client_count_, client_wait_handles_, FALSE, INFINITE, FALSE);
|
||||||
// This may block.
|
if (result == WAIT_FAILED) {
|
||||||
Pump();
|
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_);
|
xe_mutex_lock(lock_);
|
||||||
uint32_t client_callback = client_.callback;
|
uint32_t client_callback = clients_[index].callback;
|
||||||
uint32_t client_callback_arg = client_.wrapped_callback_arg;
|
uint32_t client_callback_arg = clients_[index].wrapped_callback_arg;
|
||||||
xe_mutex_unlock(lock_);
|
xe_mutex_unlock(lock_);
|
||||||
if (client_callback) {
|
if (client_callback) {
|
||||||
processor->Execute(
|
processor->Execute(thread_state_, client_callback, client_callback_arg, 0);
|
||||||
thread_state_, client_callback, client_callback_arg, 0);
|
}
|
||||||
} else {
|
pumped++;
|
||||||
Sleep(500);
|
index++;
|
||||||
|
} while (index < maximum_client_count_ && WaitForSingleObject(client_wait_handles_[index], 0) == WAIT_OBJECT_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!running_) {
|
if (!running_) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pump audio system.
|
if (!pumped) {
|
||||||
Pump();
|
Sleep(500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
|
@ -109,23 +124,53 @@ void AudioSystem::Shutdown() {
|
||||||
memory()->HeapFree(thread_block_, 0);
|
memory()->HeapFree(thread_block_, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSystem::RegisterClient(
|
X_STATUS AudioSystem::RegisterClient(
|
||||||
uint32_t callback, uint32_t callback_arg) {
|
uint32_t callback, uint32_t callback_arg, size_t* out_index) {
|
||||||
// Only support one client for now.
|
XEASSERTTRUE(unused_clients_.size());
|
||||||
XEASSERTZERO(client_.callback);
|
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);
|
uint32_t ptr = (uint32_t)memory()->HeapAlloc(0, 0x4, 0);
|
||||||
auto mem = memory()->membase();
|
auto mem = memory()->membase();
|
||||||
XESETUINT32BE(mem + ptr, callback_arg);
|
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_);
|
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_);
|
xe_mutex_unlock(lock_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSystem::UnregisterClient() {
|
void AudioSystem::UnregisterClient(size_t index) {
|
||||||
xe_mutex_lock(lock_);
|
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_);
|
xe_mutex_unlock(lock_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,12 @@
|
||||||
#include <xenia/core.h>
|
#include <xenia/core.h>
|
||||||
#include <xenia/xbox.h>
|
#include <xenia/xbox.h>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
XEDECLARECLASS1(xe, Emulator);
|
XEDECLARECLASS1(xe, Emulator);
|
||||||
XEDECLARECLASS2(xe, cpu, Processor);
|
XEDECLARECLASS2(xe, cpu, Processor);
|
||||||
XEDECLARECLASS2(xe, cpu, XenonThreadState);
|
XEDECLARECLASS2(xe, cpu, XenonThreadState);
|
||||||
|
XEDECLARECLASS2(xe, apu, AudioDriver);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace apu {
|
namespace apu {
|
||||||
|
@ -34,9 +35,12 @@ public:
|
||||||
virtual X_STATUS Setup();
|
virtual X_STATUS Setup();
|
||||||
virtual void Shutdown();
|
virtual void Shutdown();
|
||||||
|
|
||||||
void RegisterClient(uint32_t callback, uint32_t callback_arg);
|
X_STATUS RegisterClient(uint32_t callback, uint32_t callback_arg, size_t* out_index);
|
||||||
void UnregisterClient();
|
void UnregisterClient(size_t index);
|
||||||
virtual void SubmitFrame(uint32_t samples_ptr) = 0;
|
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);
|
bool HandlesRegister(uint64_t addr);
|
||||||
virtual uint64_t ReadRegister(uint64_t addr);
|
virtual uint64_t ReadRegister(uint64_t addr);
|
||||||
|
@ -44,7 +48,6 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Initialize();
|
virtual void Initialize();
|
||||||
virtual void Pump() = 0;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void ThreadStartThunk(AudioSystem* this_ptr) {
|
static void ThreadStartThunk(AudioSystem* this_ptr) {
|
||||||
|
@ -76,11 +79,17 @@ protected:
|
||||||
bool running_;
|
bool running_;
|
||||||
|
|
||||||
xe_mutex_t* lock_;
|
xe_mutex_t* lock_;
|
||||||
|
|
||||||
|
static const size_t maximum_client_count_ = 8;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
AudioDriver* driver;
|
||||||
uint32_t callback;
|
uint32_t callback;
|
||||||
uint32_t callback_arg;
|
uint32_t callback_arg;
|
||||||
uint32_t wrapped_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() {
|
NopAudioSystem::~NopAudioSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NopAudioSystem::Initialize() {
|
X_STATUS NopAudioSystem::CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver) {
|
||||||
AudioSystem::Initialize();
|
return X_STATUS_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NopAudioSystem::Pump() {
|
void NopAudioSystem::DestroyDriver(AudioDriver* driver) {
|
||||||
//
|
XEASSERTALWAYS();
|
||||||
}
|
|
||||||
|
|
||||||
void NopAudioSystem::SubmitFrame(uint32_t frame_ptr) {
|
|
||||||
// Process samples! They are big-endian floats.
|
|
||||||
}
|
|
||||||
|
|
||||||
void NopAudioSystem::Shutdown() {
|
|
||||||
AudioSystem::Shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,15 +26,8 @@ public:
|
||||||
NopAudioSystem(Emulator* emulator);
|
NopAudioSystem(Emulator* emulator);
|
||||||
virtual ~NopAudioSystem();
|
virtual ~NopAudioSystem();
|
||||||
|
|
||||||
virtual void Shutdown();
|
virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver);
|
||||||
|
virtual void DestroyDriver(AudioDriver* driver);
|
||||||
virtual void SubmitFrame(uint32_t frame_ptr);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void Initialize();
|
|
||||||
virtual void Pump();
|
|
||||||
|
|
||||||
private:
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
'apu-private.h',
|
'apu-private.h',
|
||||||
'apu.cc',
|
'apu.cc',
|
||||||
'apu.h',
|
'apu.h',
|
||||||
|
'audio_driver.cc',
|
||||||
|
'audio_driver.h',
|
||||||
'audio_system.cc',
|
'audio_system.cc',
|
||||||
'audio_system.h',
|
'audio_system.h',
|
||||||
],
|
],
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
'xaudio2_apu-private.h',
|
'xaudio2_apu-private.h',
|
||||||
'xaudio2_apu.cc',
|
'xaudio2_apu.cc',
|
||||||
'xaudio2_apu.h',
|
'xaudio2_apu.h',
|
||||||
|
'xaudio2_audio_driver.cc',
|
||||||
|
'xaudio2_audio_driver.h',
|
||||||
'xaudio2_audio_system.cc',
|
'xaudio2_audio_system.cc',
|
||||||
'xaudio2_audio_system.h',
|
'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_system.h>
|
||||||
|
#include <xenia/apu/xaudio2/xaudio2_audio_driver.h>
|
||||||
|
|
||||||
#include <xenia/apu/apu-private.h>
|
#include <xenia/apu/apu-private.h>
|
||||||
|
|
||||||
#include <xenia/emulator.h>
|
#include <xenia/emulator.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace xe;
|
using namespace xe;
|
||||||
using namespace xe::apu;
|
using namespace xe::apu;
|
||||||
using namespace xe::apu::xaudio2;
|
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) :
|
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() {
|
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() {
|
void XAudio2AudioSystem::Initialize() {
|
||||||
AudioSystem::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;
|
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE wait, AudioDriver** out_driver) {
|
||||||
|
XEASSERTNOTNULL(out_driver);
|
||||||
XAUDIO2_DEBUG_CONFIGURATION config;
|
auto driver = new XAudio2AudioDriver(emulator_, wait);
|
||||||
config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
|
driver->Initialize();
|
||||||
config.BreakMask = 0;
|
*out_driver = driver;
|
||||||
config.LogThreadID = FALSE;
|
return X_STATUS_SUCCESS;
|
||||||
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;
|
void XAudio2AudioSystem::DestroyDriver(AudioDriver* driver) {
|
||||||
|
XEASSERTNOTNULL(driver);
|
||||||
//waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
auto xdriver = static_cast<XAudio2AudioDriver*>(driver);
|
||||||
waveformat.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
xdriver->Shutdown();
|
||||||
waveformat.Format.nChannels = active_channels_;
|
XEASSERTNOTNULL(xdriver);
|
||||||
waveformat.Format.nSamplesPerSec = 48000;
|
delete xdriver;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,25 +28,11 @@ public:
|
||||||
XAudio2AudioSystem(Emulator* emulator);
|
XAudio2AudioSystem(Emulator* emulator);
|
||||||
virtual ~XAudio2AudioSystem();
|
virtual ~XAudio2AudioSystem();
|
||||||
|
|
||||||
virtual void Shutdown();
|
virtual X_RESULT CreateDriver(size_t index, HANDLE wait, AudioDriver** out_driver);
|
||||||
|
virtual void DestroyDriver(AudioDriver* driver);
|
||||||
virtual void SubmitFrame(uint32_t frame_ptr);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Initialize();
|
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);
|
callback_ptr, callback, callback_arg, driver_ptr);
|
||||||
|
|
||||||
auto audio_system = state->emulator()->audio_system();
|
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);
|
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,11 +136,10 @@ SHIM_CALL XAudioUnregisterRenderDriverClient_shim(
|
||||||
"XAudioUnregisterRenderDriverClient(%.8X)",
|
"XAudioUnregisterRenderDriverClient(%.8X)",
|
||||||
driver_ptr);
|
driver_ptr);
|
||||||
|
|
||||||
XEASSERT(driver_ptr == 0xAADD1100);
|
XEASSERT((driver_ptr & 0xFFFF0000) == 0x41550000);
|
||||||
|
|
||||||
auto audio_system = state->emulator()->audio_system();
|
auto audio_system = state->emulator()->audio_system();
|
||||||
audio_system->UnregisterClient();
|
audio_system->UnregisterClient(driver_ptr & 0x0000FFFF);
|
||||||
|
|
||||||
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,10 +153,10 @@ SHIM_CALL XAudioSubmitRenderDriverFrame_shim(
|
||||||
"XAudioSubmitRenderDriverFrame(%.8X, %.8X)",
|
"XAudioSubmitRenderDriverFrame(%.8X, %.8X)",
|
||||||
driver_ptr, samples_ptr);
|
driver_ptr, samples_ptr);
|
||||||
|
|
||||||
XEASSERT(driver_ptr == 0xAADD1100);
|
XEASSERT((driver_ptr & 0xFFFF0000) == 0x41550000);
|
||||||
|
|
||||||
auto audio_system = state->emulator()->audio_system();
|
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);
|
SHIM_SET_RETURN_32(X_ERROR_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue