- Fixed Crossfader to adjust for time spent actually adjusting the

output's volume

- Reduced lock contention in WasapiOut and DirectSoundOut by caching
  AddRef()'d copies of important interfaces in the ::Play() methods.
This commit is contained in:
casey langen 2016-12-26 15:22:49 -08:00
parent 6bffe8a54d
commit 4fd664b08a
3 changed files with 108 additions and 79 deletions

View File

@ -187,6 +187,8 @@ void DirectSoundOut::Stop() {
}
bool DirectSoundOut::Play(IBuffer *buffer, IBufferProvider *provider) {
IDirectSoundBuffer8 *outputBuffer = nullptr;
{
Lock lock(this->stateMutex);
@ -199,64 +201,70 @@ bool DirectSoundOut::Play(IBuffer *buffer, IBufferProvider *provider) {
return false;
}
unsigned char *dst1 = nullptr, *dst2 = nullptr;
DWORD size1 = 0, size2 = 0;
DWORD availableBytes = 0;
DWORD bufferBytes = buffer->Bytes();
do {
availableBytes = getAvailableBytes(
this->secondaryBuffer,
this->writeOffset,
this->bufferSize);
if (bufferBytes > availableBytes) {
int samples = (bufferBytes - availableBytes) / sizeof(float) / channels;
int sleepMs = ((long long)(samples * 1000) / rate) + 1;
Sleep(sleepMs);
}
} while (this->state == StatePlaying && availableBytes < bufferBytes);
if (this->state != StatePlaying) {
return false;
}
HRESULT result =
this->secondaryBuffer->Lock(
writeOffset,
bufferBytes,
(void **) &dst1, &size1,
(void **) &dst2, &size2,
0);
if (result == DSERR_BUFFERLOST) {
this->secondaryBuffer->Restore();
result = this->secondaryBuffer->Lock(
writeOffset,
bufferBytes,
(void **) &dst1, &size1,
(void **) &dst2, &size2,
0);
}
if (result != DS_OK) {
return false;
}
char* bufferPointer = (char *) buffer->BufferPointer();
memcpy(dst1, bufferPointer, size1);
if (size2 > 0) {
memcpy(dst2, bufferPointer + size1, size2);
}
writeOffset += bufferBytes;
writeOffset %= this->bufferSize;
this->secondaryBuffer->Unlock((void *)dst1, size1, (void *)dst2, size2);
/* reduce lock contention: cache a reference to the buffer here,
just in case someone comes along and release it */
outputBuffer = this->secondaryBuffer;
outputBuffer->AddRef();
}
unsigned char *dst1 = nullptr, *dst2 = nullptr;
DWORD size1 = 0, size2 = 0;
DWORD availableBytes = 0;
DWORD bufferBytes = buffer->Bytes();
do {
availableBytes = getAvailableBytes(
outputBuffer,
this->writeOffset,
this->bufferSize);
if (bufferBytes > availableBytes) {
int samples = (bufferBytes - availableBytes) / sizeof(float) / channels;
int sleepMs = ((long long)(samples * 1000) / rate) + 1;
Sleep(sleepMs);
}
} while (this->state == StatePlaying && availableBytes < bufferBytes);
if (this->state != StatePlaying) {
return false;
}
HRESULT result =
outputBuffer->Lock(
writeOffset,
bufferBytes,
(void **) &dst1, &size1,
(void **) &dst2, &size2,
0);
if (result == DSERR_BUFFERLOST) {
outputBuffer->Restore();
result = this->secondaryBuffer->Lock(
writeOffset,
bufferBytes,
(void **) &dst1, &size1,
(void **) &dst2, &size2,
0);
}
if (result != DS_OK) {
return false;
}
char* bufferPointer = (char *) buffer->BufferPointer();
memcpy(dst1, bufferPointer, size1);
if (size2 > 0) {
memcpy(dst2, bufferPointer + size1, size2);
}
writeOffset += bufferBytes;
writeOffset %= this->bufferSize;
outputBuffer->Unlock((void *)dst1, size1, (void *)dst2, size2);
outputBuffer->Release();
provider->OnBufferProcessed(buffer);
return true;

View File

@ -149,6 +149,12 @@ void WasapiOut::Drain() {
}
bool WasapiOut::Play(IBuffer *buffer, IBufferProvider *provider) {
IAudioRenderClient *renderClient = nullptr;
IAudioClient *audioClient = nullptr;
/* reduce lock contention by snagging the references to the
COM interfaces we care about and calling AddRef(), then operating
on the local copies. */
{
Lock lock(this->stateMutex);
@ -161,27 +167,31 @@ bool WasapiOut::Play(IBuffer *buffer, IBufferProvider *provider) {
return false;
}
UINT32 availableFrames = 0;
UINT32 frameOffset = 0;
UINT32 samples = (UINT32) buffer->Samples();
UINT32 framesToWrite = samples / (UINT32) buffer->Channels();
int channels = buffer->Channels();
renderClient = this->renderClient;
renderClient->AddRef();
do {
this->audioClient->GetCurrentPadding(&frameOffset);
availableFrames = (this->outputBufferFrames - frameOffset);
audioClient = this->audioClient;
audioClient->AddRef();
}
if (availableFrames < framesToWrite) {
UINT32 delta = framesToWrite - availableFrames;
REFERENCE_TIME sleepTime = (delta * 1000 * 1000 * 10) / buffer->SampleRate();
std::this_thread::sleep_for(std::chrono::microseconds(sleepTime));
}
} while (this->state == StatePlaying && availableFrames < framesToWrite);
UINT32 availableFrames = 0;
UINT32 frameOffset = 0;
UINT32 samples = (UINT32) buffer->Samples();
UINT32 framesToWrite = samples / (UINT32) buffer->Channels();
int channels = buffer->Channels();
if (state != StatePlaying) {
return false;
do {
audioClient->GetCurrentPadding(&frameOffset);
availableFrames = (this->outputBufferFrames - frameOffset);
if (availableFrames < framesToWrite) {
UINT32 delta = framesToWrite - availableFrames;
REFERENCE_TIME sleepTime = (delta * 1000 * 1000 * 10) / buffer->SampleRate();
std::this_thread::sleep_for(std::chrono::microseconds(sleepTime));
}
} while (this->state == StatePlaying && availableFrames < framesToWrite);
if (state == StatePlaying) {
if (availableFrames >= framesToWrite) {
BYTE *data = 0;
HRESULT result = this->renderClient->GetBuffer(framesToWrite, &data);
@ -191,10 +201,13 @@ bool WasapiOut::Play(IBuffer *buffer, IBufferProvider *provider) {
}
memcpy(data, buffer->BufferPointer(), sizeof(float) * samples);
this->renderClient->ReleaseBuffer(framesToWrite, 0);
renderClient->ReleaseBuffer(framesToWrite, 0);
}
}
renderClient->Release();
audioClient->Release();
provider->OnBufferProcessed(buffer);
return true;

View File

@ -38,10 +38,12 @@
#include <core/runtime/Message.h>
#include <algorithm>
#include <chrono>
using namespace musik::core::audio;
using namespace musik::core::sdk;
using namespace musik::core::runtime;
using namespace std::chrono;
#define TICKS_PER_SECOND 30
#define TICK_TIME_MILLIS (1000 / TICKS_PER_SECOND)
@ -51,6 +53,10 @@ using namespace musik::core::runtime;
this->messageQueue.Post(Message::Create( \
this, MESSAGE_TICK, 0, 0), TICK_TIME_MILLIS)
#define ENQUEUE_ADJUSTED_TICK(delay) \
this->messageQueue.Post(Message::Create( \
this, MESSAGE_TICK, 0, 0), delay > 0 ? delay : 0)
#define LOCK(x) \
std::unique_lock<std::recursive_mutex> lock(x);
@ -214,6 +220,8 @@ void Crossfader::ProcessMessage(IMessage &message) {
case MESSAGE_TICK: {
bool emptied = false;
auto start = system_clock::now().time_since_epoch();
{
LOCK(this->contextListLock);
@ -278,12 +286,7 @@ void Crossfader::ProcessMessage(IMessage &message) {
}
}
if (this->contextList.size()) {
ENQUEUE_TICK();
}
else {
emptied = true;
}
emptied = (this->contextList.size() == 0);
} /* end critical section */
/* notify outside of the critical section! */
@ -291,6 +294,11 @@ void Crossfader::ProcessMessage(IMessage &message) {
this->Emptied();
this->drainCondition.notify_all();
}
else {
auto end = system_clock::now().time_since_epoch();
int64 duration = duration_cast<milliseconds>(end - start).count();
ENQUEUE_ADJUSTED_TICK(TICK_TIME_MILLIS - duration);
}
}
break;
}