Update to v103r17 release.

byuu says:

Changelog:

  - tomoko: re-hid the video sync option¹
  - tomoko: removed " Settings" duplication on all the individual
    settings tab options
  - ruby/audio/wasapi: finished port to new syntax; adapted to an
    event-driven model; support 32-bit integral audio²
  - ruby/video/sdl: ported to new syntax; disabled driver on FreeBSD³

¹: still contemplating a synchronize submenu of {none, video, audio},
but ... the fact that video can't work on PAL, WonderSwan games is a
real limitation for it

²: this driver actually received a ton of work. There's also a new
ring-buffer queue, and I added special handling for when exclusive mode
fails because the latency requested is lower than the hardware can
support. It'll pick the closest latency to the minimum that is possible
in this case.

On my Audigy Rx, the results for non-exclusive mode are the same. For
exclusive mode, the framerate drops from 60fps to ~50fps for smaller
buffers, and ~55fps for larger buffers (no matter how big, it never hits
60fps.) This is a lot better than before where it was hitting ~15fps,
but unfortunately it's the best I can do.

The event system used by WASAPI is really stupid. It just uses SetEvent
at some arbitrary time, and you have to query to see how many samples
it's waiting on. This makes it unknowable how many samples we should
buffer before calling `WaitForSingleObject(INFINITE)`, and it's also
unclear how we should handle cases where there's more samples available
than our queue has: either we can fill it with zeroes, or we can write
less samples. The former should prevent audio looping effects when
running too slowly, whereas the latter could potentially be too
ambitious when the audio could've recovered from a minor stall.

It's shocking to me how there's as many ways to send audio to a sound
card as there are sound card APIs, when all that's needed is a simple
double buffer and a callback event from another thread to do it right.
It's also terrifying how unbelievably shitty nearly all sound card
drivers apparently are.

Also, I don't know if cards can output an actual 24-bit mode with three
byte audio samples, or if they always just take 32-bit samples and
ignore the lower 8-bits. Whatever, it's all nonsense for the final
output to be >16-bits anyway (hi, `double[]` input from ruby.)

³: unfortunately, this driver always crashes on FreeBSD (even before
the rewrite), so I'll need someone on Linux to test it and make sure it
actually works. I'll also need testing for a lot of the other drivers as
well, once they're ported over (I don't have X-video, PulseAudio, ALSA,
or udev.)

Note that I forgot to set `_ready=true` at the end of `initialize()`,
and `_ready=false` in `terminate()`, but it shouldn't actually matter
beyond showing you a false warning message on startup about it failing
to initialize.
This commit is contained in:
Tim Allen
2017-07-19 23:14:00 +10:00
parent f87c6b7ecb
commit 0b4e7fb5a5
7 changed files with 192 additions and 160 deletions

View File

@@ -15,7 +15,7 @@ struct AudioWASAPI : Audio {
Information information;
information.devices = {"Default"};
information.channels = {2};
information.frequencies = {};
information.frequencies = {(double)_frequency};
information.latencies = {20, 40, 60, 80, 100};
return information;
}
@@ -51,32 +51,31 @@ struct AudioWASAPI : Audio {
}
auto clear() -> void {
_queue.read = 0;
_queue.write = 0;
_queue.count = 0;
if(!_audioClient) return;
_audioClient->Stop();
_audioClient->Reset();
for(auto n : range(available())) write(0, 0);
_audioClient->Start();
}
auto output(const double samples[]) -> void {
_queuedFrames.append(int16_t(samples[0] * 32768.0) << 0 | int16_t(samples[1] * 32768.0) << 16);
if(!available() && _queuedFrames.size() >= _bufferSize) {
if(_blocking) {
while(!available()); //wait for free sample slot
} else {
_queuedFrames.takeLeft(); //drop sample (run ahead)
if(_queue.count < _bufferSize) {
for(uint n : range(_channels)) {
_queue.samples[_queue.write][n] = samples[n];
}
}
uint32_t cachedFrame = 0;
for(auto n : range(available())) {
if(_queuedFrames) cachedFrame = _queuedFrames.takeLeft();
write(cachedFrame >> 0, cachedFrame >> 16);
_queue.write++;
_queue.count++;
} else if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
write();
}
}
private:
auto initialize() -> bool {
terminate();
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false;
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
@@ -87,25 +86,35 @@ private:
_waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData;
if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false;
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, latency, latency, _waveFormat, nullptr) != S_OK) return false;
auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
_audioClient->Release();
latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / _waveFormat->nSamplesPerSec);
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
}
if(result != S_OK) return false;
DWORD taskIndex = 0;
_taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
} else {
if(_audioClient->GetMixFormat(&waveFormat) != S_OK) return false;
if(_audioClient->GetMixFormat(&_waveFormat) != S_OK) return false;
if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false;
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, latency, 0, _waveFormat, nullptr) != S_OK) return false;
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, _waveFormat, nullptr) != S_OK) return false;
}
_eventHandle = CreateEvent(nullptr, false, false, nullptr);
if(_audioClient->SetEventHandle(_eventHandle) != S_OK) return false;
if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false;
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
_channels = waveFormat->nChannels;
_frequency = waveFormat->nSamplesPerSec;
_channels = _waveFormat->nChannels;
_frequency = _waveFormat->nSamplesPerSec;
_mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1;
_precision = _waveFormat->wBitsPerSample;
_audioClient->Start();
clear();
return _ready = true;
}
@@ -115,33 +124,52 @@ private:
if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr;
if(_audioClient) _audioClient->Release(), _audioClient = nullptr;
if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr;
if(_eventHandle) CloseHandle(_eventHandle), _eventHandle = nullptr;
if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr;
}
auto available() -> uint {
auto write() -> void {
uint32_t padding = 0;
_audioClient->GetCurrentPadding(&padding);
return bufferSize - padding;
}
auto write(int16_t left, int16_t right) -> void {
if(_renderClient->GetBuffer(1, &_bufferData) != S_OK) return;
if(_channels >= 2 && _mode == 1 && _precision == 16) {
auto buffer = (int16_t*)_bufferData;
buffer[0] = left;
buffer[1] = right;
}
if(_channels >= 2 && _mode == 3 && _precision == 32) {
auto buffer = (float*)_bufferData;
buffer[0] = left / 32768.0;
buffer[1] = right / 32768.0;
}
_renderClient->ReleaseBuffer(1, 0);
uint32_t available = _bufferSize - padding;
uint8_t* buffer = nullptr;
if(_renderClient->GetBuffer(available, &buffer) == S_OK) {
uint bufferFlags = 0;
for(uint _ : range(available)) {
//if more samples are available than we have queued, fill remainder with silence
double samples[8] = {};
if(_queue.count) {
for(uint n : range(_channels)) {
samples[n] = _queue.samples[_queue.read][n];
}
_queue.read++;
_queue.count--;
}
if(_mode == 1 && _precision == 16) {
auto output = (int16_t*)buffer;
for(uint n : range(_channels)) *output++ = int16_t(samples[n] * 32768.0);
buffer = (uint8_t*)output;
} else if(_mode == 1 && _precision == 32) {
auto output = (int32_t*)buffer;
for(uint n : range(_channels)) *output++ = int32_t(samples[n] * 65536.0 * 32768.0);
buffer = (uint8_t*)output;
} else if(_mode == 3 && _precision == 32) {
auto output = (float*)buffer;
for(uint n : range(_channels)) *output++ = float(samples[n]);
buffer = (uint8_t*)output;
} else {
//output silence for unsupported sample formats
bufferFlags = AUDCLNT_BUFFERFLAGS_SILENT;
break;
}
}
_renderClient->ReleaseBuffer(available, bufferFlags);
}
}
bool _ready = false;
bool _exclusive = false;
bool _blocking = true;
uint _channels = 2;
@@ -151,6 +179,13 @@ private:
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;
IPropertyStore* _propertyStore = nullptr;
@@ -158,9 +193,8 @@ private:
IAudioRenderClient* _renderClient = nullptr;
WAVEFORMATEX* _waveFormat = nullptr;
PROPVARIANT _propVariant;
HANDLE _eventHandle = nullptr;
HANDLE _taskHandle = nullptr;
REFERENCE_TIME _devicePeriod = 0;
uint32_t _bufferSize = 0; //in frames
uint8_t* _bufferData = nullptr;
vector<uint32_t> _queuedFrames;
};