Modified IOutput interface to supply an optional latency. Updated

WasapiOut to send the proper latency.
This commit is contained in:
casey langen 2016-12-04 23:45:07 -08:00
parent a797d4db9d
commit 487502fd1f
9 changed files with 67 additions and 65 deletions

View File

@ -54,6 +54,7 @@ class AlsaOut : public musik::core::sdk::IOutput {
virtual void Resume();
virtual void SetVolume(double volume);
virtual void Stop();
virtual double Latency() { return 0.0; }
virtual bool Play(
musik::core::sdk::IBuffer *buffer,

View File

@ -59,6 +59,7 @@ class CoreAudioOut : public musik::core::sdk::IOutput {
virtual void Resume();
virtual void SetVolume(double volume);
virtual void Stop();
virtual double Latency() { return 0.0; }
virtual bool Play(
musik::core::sdk::IBuffer *buffer,

View File

@ -38,7 +38,10 @@
#include <chrono>
#include <thread>
#define MAX_BUFFERS_PER_OUTPUT 32
#define MAX_BUFFERS_PER_OUTPUT 16
#define BUFFER_TO_TIME(b) \
(UINT64) round(((UINT64) buffer->Samples() * 10000000LL) / (UINT64) buffer->SampleRate());
/* NOTE! device init and deinit logic was stolen and modified from
QMMP's WASABI output plugin! http://qmmp.ylsoftware.com/ */
@ -55,9 +58,11 @@ WasapiOut::WasapiOut()
, audioClient(nullptr)
, renderClient(nullptr)
, simpleAudioVolume(nullptr)
, outputBufferSize(0)
, state(Stopped) {
memset(&waveFormat, 0, sizeof(WAVEFORMATEXTENSIBLE));
, audioClock(nullptr)
, outputBufferFrames(0)
, state(StateStopped)
, latency(0) {
ZeroMemory(&waveFormat, sizeof(WAVEFORMATEXTENSIBLE));
}
WasapiOut::~WasapiOut() {
@ -69,19 +74,21 @@ void WasapiOut::Destroy() {
}
void WasapiOut::Pause() {
this->state = StatePaused;
Lock lock(this->stateMutex);
if (this->audioClient) {
this->state = Paused;
this->audioClient->Stop();
}
}
void WasapiOut::Resume() {
this->state = StatePlaying;
Lock lock(this->stateMutex);
if (this->audioClient) {
this->state = Playing;
this->audioClient->Start();
}
}
@ -91,31 +98,17 @@ void WasapiOut::SetVolume(double volume) {
if (this->simpleAudioVolume) {
simpleAudioVolume->SetMasterVolume((float) volume, 0);
simpleAudioVolume->SetMute(false, 0);
}
}
void WasapiOut::Stop() {
{
Lock lock(this->stateMutex);
Lock lock(this->stateMutex);
if (this->audioClient) {
this->audioClient->Stop();
this->audioClient->Reset();
this->audioClient->Start();
}
}
std::deque<std::shared_ptr<BufferContext>> toRelease;
{
Lock lock(this->bufferQueueMutex);
std::swap(this->pendingQueue, toRelease);
}
auto it = toRelease.begin();
while (it != toRelease.end()) {
(*it)->provider->OnBufferProcessed((*it)->buffer);
++it;
if (this->audioClient) {
this->audioClient->Stop();
this->audioClient->Reset();
this->audioClient->Start();
}
}
@ -128,26 +121,24 @@ bool WasapiOut::Play(IBuffer *buffer, IBufferProvider *provider) {
return false;
}
this->state = Playing;
UINT32 availableFrames;
UINT32 availableFrames = 0;
UINT32 frameOffset = 0;
UINT32 samples = (UINT32)buffer->Samples();
UINT32 framesToWrite = samples / (UINT32)buffer->Channels();
UINT32 samples = (UINT32) buffer->Samples();
UINT32 framesToWrite = samples / (UINT32) buffer->Channels();
int channels = buffer->Channels();
do {
this->audioClient->GetCurrentPadding(&frameOffset);
availableFrames = (this->outputBufferSize - frameOffset);
availableFrames = (this->outputBufferFrames - frameOffset);
if (availableFrames < framesToWrite) {
UINT32 delta = framesToWrite - availableFrames;
REFERENCE_TIME sleepTime = (delta * 1000 * 1000) / buffer->SampleRate();
REFERENCE_TIME sleepTime = (delta * 1000 * 1000 * 10) / buffer->SampleRate();
std::this_thread::sleep_for(std::chrono::microseconds(sleepTime));
}
} while (this->state == Playing && availableFrames < framesToWrite);
} while (this->state == StatePlaying && availableFrames < framesToWrite);
if (state != Playing) {
if (state != StatePlaying) {
return false;
}
@ -164,20 +155,7 @@ bool WasapiOut::Play(IBuffer *buffer, IBufferProvider *provider) {
}
}
{
Lock lock(this->bufferQueueMutex);
std::shared_ptr<BufferContext> context(new BufferContext());
context->buffer = buffer;
context->provider = provider;
pendingQueue.push_back(context);
if (pendingQueue.size() >= MAX_BUFFERS_PER_OUTPUT) {
context = pendingQueue.front();
pendingQueue.pop_front();
context->provider->OnBufferProcessed(context->buffer);
}
}
provider->OnBufferProcessed(buffer);
return true;
}
@ -208,6 +186,17 @@ void WasapiOut::Reset() {
this->simpleAudioVolume->Release();
this->simpleAudioVolume = nullptr;
}
if (this->audioClock) {
this->audioClock->Release();
this->audioClock = nullptr;
}
ZeroMemory(&waveFormat, sizeof(WAVEFORMATEXTENSIBLE));
}
double WasapiOut::Latency() {
return (double) latency / 1000;
}
bool WasapiOut::Configure(IBuffer *buffer) {
@ -284,7 +273,7 @@ bool WasapiOut::Configure(IBuffer *buffer) {
std::cerr << "WasapiOut: format is not supported, using converter\n";
}
long totalMillis = (long) round((buffer->Samples() * 1000) / buffer->SampleRate()) * MAX_BUFFERS_PER_OUTPUT;
long totalMillis = (long) round((buffer->Samples() / buffer->Channels() * 1000) / buffer->SampleRate()) * MAX_BUFFERS_PER_OUTPUT;
REFERENCE_TIME hundredNanos = totalMillis * 1000 * 10;
if ((result = this->audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, streamFlags, hundredNanos, 0, (WAVEFORMATEX *) &wf, NULL)) != S_OK) {
@ -292,11 +281,13 @@ bool WasapiOut::Configure(IBuffer *buffer) {
return false;
}
if ((result = this->audioClient->GetBufferSize(&this->outputBufferSize)) != S_OK) {
if ((result = this->audioClient->GetBufferSize(&this->outputBufferFrames)) != S_OK) {
std::cerr << "WasapiOut: IAudioClient::GetBufferSize failed, error code = " << result << "\n";
return false;
}
this->latency = (outputBufferFrames * 1000) / buffer->SampleRate();
if ((result = this->audioClient->GetService(__uuidof(IAudioRenderClient), (void**) &this->renderClient)) != S_OK) {
std::cerr << "WasapiOut: IAudioClient::GetService failed, error code = " << result << "\n";
return false;
@ -307,10 +298,17 @@ bool WasapiOut::Configure(IBuffer *buffer) {
return false;
}
if ((result = this->audioClient->GetService(__uuidof(IAudioClock), (void**) &this->audioClock)) != S_OK) {
std::cerr << "WasapiOut: IAudioClient::GetService failed, error code = " << result << "\n";
return false;
}
if ((result = this->audioClient->Start()) != S_OK) {
std::cerr << "WasapiOut: IAudioClient::Start failed, error code = " << result << "\n";
return false;
}
this->state = StatePlaying;
return true;
}

View File

@ -38,6 +38,7 @@
#include <deque>
#include <memory>
#include <mutex>
#include <atomic>
#include <mmdeviceapi.h>
#include <Audioclient.h>
@ -57,33 +58,30 @@ class WasapiOut : public IOutput {
virtual void SetVolume(double volume);
virtual void Stop();
virtual bool Play(IBuffer *buffer, IBufferProvider *provider);
virtual double Latency();
private:
enum State {
Stopped,
Playing,
Paused
};
struct BufferContext {
IBuffer *buffer;
IBufferProvider *provider;
StateStopped,
StatePlaying,
StatePaused
};
bool Configure(IBuffer *buffer);
void Reset();
void EventThread();
IMMDeviceEnumerator *enumerator;
IMMDevice *device;
IAudioClient *audioClient;
IAudioClock *audioClock;
IAudioRenderClient *renderClient;
ISimpleAudioVolume *simpleAudioVolume;
UINT32 outputBufferFrames;
std::atomic<State> state;
WAVEFORMATEXTENSIBLE waveFormat;
UINT32 outputBufferSize;
UINT64 totalSamplesWritten;
State state;
std::recursive_mutex bufferQueueMutex;
std::recursive_mutex stateMutex;
std::deque<std::shared_ptr<BufferContext>> pendingQueue;
INT64 latency;
};

View File

@ -97,6 +97,7 @@
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
<TargetMachine>MachineX86</TargetMachine>
<GenerateDebugInformation>false</GenerateDebugInformation>
<FullProgramDatabaseFile>false</FullProgramDatabaseFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View File

@ -55,6 +55,7 @@ class WaveOut : public IOutput {
virtual void SetVolume(double volume);
virtual void Stop();
virtual bool Play(IBuffer *buffer, IBufferProvider *provider);
virtual double Latency() { return 0.0; }
void OnBufferWrittenToOutput(WaveOutBuffer *buffer);

View File

@ -175,7 +175,7 @@ void Player::Destroy() {
double Player::Position() {
std::unique_lock<std::mutex> lock(this->positionMutex);
return this->currentPosition;
return std::max(0.0, round(this->currentPosition - this->output->Latency()));
}
void Player::SetPosition(double seconds) {

View File

@ -125,6 +125,7 @@ namespace musik { namespace core { namespace audio {
int state;
bool notifiedStarted;
float* spectrum;
uint64 samplesWritten;
FftContext* fftContext;
};

View File

@ -49,6 +49,7 @@ namespace musik { namespace core { namespace sdk {
virtual void SetVolume(double volume) = 0;
virtual void Stop() = 0;
virtual bool Play(IBuffer *buffer, IBufferProvider *provider) = 0;
virtual double Latency() = 0;
};
} } }