mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-04-02 10:42:14 -04:00
byuu says: This release fixes the XAudio 2.1 and WASAPI drivers on Windows, and extends XAudio to support device selection (eg headphones, speakers, monitor, etc.) It also adds DRC to XAudio, however it's not currently working. The code is courtesy of Talarubi, I just botched it somewhere upon porting it to the newer version of ruby.
273 lines
9.9 KiB
C++
273 lines
9.9 KiB
C++
#include <avrt.h>
|
|
#include <mmdeviceapi.h>
|
|
#include <audioclient.h>
|
|
#include <audiopolicy.h>
|
|
#include <devicetopology.h>
|
|
#include <endpointvolume.h>
|
|
#include <functiondiscoverykeys_devpkey.h>
|
|
|
|
struct AudioWASAPI : AudioDriver {
|
|
AudioWASAPI& self = *this;
|
|
AudioWASAPI(Audio& super) : AudioDriver(super) { construct(); }
|
|
~AudioWASAPI() { destruct(); }
|
|
|
|
auto create() -> bool override {
|
|
super.setExclusive(false);
|
|
super.setDevice(hasDevices().first());
|
|
super.setBlocking(false);
|
|
super.setChannels(2);
|
|
super.setFrequency(48000);
|
|
super.setLatency(40);
|
|
return initialize();
|
|
}
|
|
|
|
auto driver() -> string override { return "WASAPI"; }
|
|
auto ready() -> bool override { return self.isReady; }
|
|
|
|
auto hasExclusive() -> bool override { return true; }
|
|
auto hasBlocking() -> bool override { return true; }
|
|
|
|
auto hasDevices() -> vector<string> override {
|
|
vector<string> devices;
|
|
for(auto& device : self.devices) devices.append(device.name);
|
|
return devices;
|
|
}
|
|
|
|
auto hasChannels() -> vector<uint> override {
|
|
return {self.channels};
|
|
}
|
|
|
|
auto hasFrequencies() -> vector<uint> override {
|
|
return {self.frequency};
|
|
}
|
|
|
|
auto hasLatencies() -> vector<uint> override {
|
|
return {0, 20, 40, 60, 80, 100};
|
|
}
|
|
|
|
auto setExclusive(bool exclusive) -> bool override { return initialize(); }
|
|
auto setDevice(string device) -> bool override { return initialize(); }
|
|
auto setBlocking(bool blocking) -> bool override { return true; }
|
|
auto setFrequency(uint frequency) -> bool override { return initialize(); }
|
|
auto setLatency(uint latency) -> bool override { return initialize(); }
|
|
|
|
auto clear() -> void override {
|
|
self.queue.read = 0;
|
|
self.queue.write = 0;
|
|
self.queue.count = 0;
|
|
self.audioClient->Stop();
|
|
self.audioClient->Reset();
|
|
self.audioClient->Start();
|
|
}
|
|
|
|
auto output(const double samples[]) -> void override {
|
|
for(uint n : range(self.channels)) {
|
|
self.queue.samples[self.queue.write][n] = samples[n];
|
|
}
|
|
self.queue.write++;
|
|
self.queue.count++;
|
|
|
|
if(self.queue.count >= self.bufferSize) {
|
|
if(WaitForSingleObject(self.eventHandle, self.blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
|
write();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct Device {
|
|
string id;
|
|
string name;
|
|
};
|
|
vector<Device> devices;
|
|
|
|
auto construct() -> bool {
|
|
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&self.enumerator) != S_OK) return false;
|
|
|
|
IMMDevice* defaultDeviceContext = nullptr;
|
|
if(self.enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDeviceContext) != S_OK) return false;
|
|
|
|
Device defaultDevice;
|
|
LPWSTR defaultDeviceString = nullptr;
|
|
defaultDeviceContext->GetId(&defaultDeviceString);
|
|
defaultDevice.id = (const char*)utf8_t(defaultDeviceString);
|
|
CoTaskMemFree(defaultDeviceString);
|
|
|
|
IMMDeviceCollection* deviceCollection = nullptr;
|
|
if(self.enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
|
|
|
|
uint deviceCount = 0;
|
|
if(deviceCollection->GetCount(&deviceCount) != S_OK) return false;
|
|
|
|
for(uint deviceIndex : range(deviceCount)) {
|
|
IMMDevice* deviceContext = nullptr;
|
|
if(deviceCollection->Item(deviceIndex, &deviceContext) != S_OK) continue;
|
|
|
|
Device device;
|
|
LPWSTR deviceString = nullptr;
|
|
deviceContext->GetId(&deviceString);
|
|
device.id = (const char*)utf8_t(deviceString);
|
|
CoTaskMemFree(deviceString);
|
|
|
|
IPropertyStore* propertyStore = nullptr;
|
|
deviceContext->OpenPropertyStore(STGM_READ, &propertyStore);
|
|
PROPVARIANT propVariant;
|
|
propertyStore->GetValue(PKEY_Device_FriendlyName, &propVariant);
|
|
device.name = (const char*)utf8_t(propVariant.pwszVal);
|
|
propertyStore->Release();
|
|
|
|
if(device.id == defaultDevice.id) {
|
|
self.devices.prepend(device);
|
|
} else {
|
|
self.devices.append(device);
|
|
}
|
|
}
|
|
|
|
deviceCollection->Release();
|
|
return true;
|
|
}
|
|
|
|
auto destruct() -> void {
|
|
terminate();
|
|
|
|
if(self.enumerator) {
|
|
self.enumerator->Release();
|
|
self.enumerator = nullptr;
|
|
}
|
|
}
|
|
|
|
auto initialize() -> bool {
|
|
terminate();
|
|
|
|
string deviceID;
|
|
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
|
|
deviceID = self.devices[*index].id;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
utf16_t deviceString(deviceID);
|
|
if(self.enumerator->GetDevice(deviceString, &self.audioDevice) != S_OK) return false;
|
|
|
|
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
|
|
|
WAVEFORMATEXTENSIBLE waveFormat{};
|
|
if(self.exclusive) {
|
|
IPropertyStore* propertyStore = nullptr;
|
|
if(self.audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
|
PROPVARIANT propVariant;
|
|
if(propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &propVariant) != S_OK) return false;
|
|
waveFormat = *(WAVEFORMATEXTENSIBLE*)propVariant.blob.pBlobData;
|
|
propertyStore->Release();
|
|
if(self.audioClient->GetDevicePeriod(nullptr, &self.devicePeriod) != S_OK) return false;
|
|
auto latency = max(self.devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
|
auto result = self.audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
|
if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
|
if(self.audioClient->GetBufferSize(&self.bufferSize) != S_OK) return false;
|
|
self.audioClient->Release();
|
|
latency = (REFERENCE_TIME)(10'000 * 1'000 * self.bufferSize / waveFormat.Format.nSamplesPerSec);
|
|
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
|
result = self.audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
|
}
|
|
if(result != S_OK) return false;
|
|
DWORD taskIndex = 0;
|
|
self.taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
|
} else {
|
|
WAVEFORMATEX* waveFormatEx = nullptr;
|
|
if(self.audioClient->GetMixFormat(&waveFormatEx) != S_OK) return false;
|
|
waveFormat = *(WAVEFORMATEXTENSIBLE*)waveFormatEx;
|
|
CoTaskMemFree(waveFormatEx);
|
|
if(self.audioClient->GetDevicePeriod(&self.devicePeriod, nullptr)) return false;
|
|
auto latency = max(self.devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
|
if(self.audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, &waveFormat.Format, nullptr) != S_OK) return false;
|
|
}
|
|
|
|
self.eventHandle = CreateEvent(nullptr, false, false, nullptr);
|
|
if(self.audioClient->SetEventHandle(self.eventHandle) != S_OK) return false;
|
|
if(self.audioClient->GetService(IID_IAudioRenderClient, (void**)&self.renderClient) != S_OK) return false;
|
|
if(self.audioClient->GetBufferSize(&self.bufferSize) != S_OK) return false;
|
|
|
|
self.channels = waveFormat.Format.nChannels;
|
|
self.frequency = waveFormat.Format.nSamplesPerSec;
|
|
self.mode = waveFormat.SubFormat.Data1;
|
|
self.precision = waveFormat.Format.wBitsPerSample;
|
|
|
|
clear();
|
|
return self.isReady = true;
|
|
}
|
|
|
|
auto terminate() -> void {
|
|
self.isReady = false;
|
|
if(self.audioClient) self.audioClient->Stop();
|
|
if(self.renderClient) self.renderClient->Release(), self.renderClient = nullptr;
|
|
if(self.audioClient) self.audioClient->Release(), self.audioClient = nullptr;
|
|
if(self.audioDevice) self.audioDevice->Release(), self.audioDevice = nullptr;
|
|
if(self.eventHandle) CloseHandle(self.eventHandle), self.eventHandle = nullptr;
|
|
if(self.taskHandle) AvRevertMmThreadCharacteristics(self.taskHandle), self.taskHandle = nullptr;
|
|
}
|
|
|
|
auto write() -> void {
|
|
uint32_t available = self.bufferSize;
|
|
if(!self.exclusive) {
|
|
uint32_t padding = 0;
|
|
self.audioClient->GetCurrentPadding(&padding);
|
|
available = self.bufferSize - padding;
|
|
}
|
|
uint32_t length = min(available, self.queue.count);
|
|
|
|
uint8_t* buffer = nullptr;
|
|
if(self.renderClient->GetBuffer(length, &buffer) == S_OK) {
|
|
uint bufferFlags = 0;
|
|
for(uint _ : range(length)) {
|
|
double samples[8] = {};
|
|
if(self.queue.count) {
|
|
for(uint n : range(self.channels)) {
|
|
samples[n] = self.queue.samples[self.queue.read][n];
|
|
}
|
|
self.queue.read++;
|
|
self.queue.count--;
|
|
}
|
|
|
|
if(self.mode == 1 && self.precision == 16) {
|
|
auto output = (uint16_t*)buffer;
|
|
for(uint n : range(self.channels)) *output++ = (uint16_t)sclamp<16>(samples[n] * (32768.0 - 1.0));
|
|
buffer = (uint8_t*)output;
|
|
} else if(self.mode == 1 && self.precision == 32) {
|
|
auto output = (uint32_t*)buffer;
|
|
for(uint n : range(self.channels)) *output++ = (uint32_t)sclamp<32>(samples[n] * (65536.0 * 32768.0 - 1.0));
|
|
buffer = (uint8_t*)output;
|
|
} else if(self.mode == 3 && self.precision == 32) {
|
|
auto output = (float*)buffer;
|
|
for(uint n : range(self.channels)) *output++ = float(max(-1.0, min(+1.0, samples[n])));
|
|
buffer = (uint8_t*)output;
|
|
} else {
|
|
//output silence for unsupported sample formats
|
|
bufferFlags = AUDCLNT_BUFFERFLAGS_SILENT;
|
|
break;
|
|
}
|
|
}
|
|
self.renderClient->ReleaseBuffer(length, bufferFlags);
|
|
}
|
|
}
|
|
|
|
bool isReady = false;
|
|
|
|
uint mode = 0;
|
|
uint precision = 0;
|
|
|
|
struct Queue {
|
|
double samples[65536][8];
|
|
uint16_t read;
|
|
uint16_t write;
|
|
uint16_t count;
|
|
} queue;
|
|
|
|
IMMDeviceEnumerator* enumerator = nullptr;
|
|
IMMDevice* audioDevice = nullptr;
|
|
IAudioClient* audioClient = nullptr;
|
|
IAudioRenderClient* renderClient = nullptr;
|
|
HANDLE eventHandle = nullptr;
|
|
HANDLE taskHandle = nullptr;
|
|
REFERENCE_TIME devicePeriod = 0;
|
|
uint32_t bufferSize = 0; //in frames
|
|
};
|