bsnes/ruby/audio/wasapi.cpp

274 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
};