freedv-gui/src/audio/WASAPIAudioDevice.cpp

897 lines
29 KiB
C++

//=========================================================================
// Name: WASAPIAudioDevice.cpp
// Purpose: Defines the interface to a Windows audio device.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. This program is
// distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#include "WASAPIAudioDevice.h"
#include <sstream>
#include <chrono>
#include <thread>
#include <future>
#include <cmath>
#include <avrt.h>
#include <timeapi.h>
#include <inttypes.h>
#include "../util/logging/ulog.h"
#define BLOCK_TIME_NS (10000000)
// Nanoseconds per REFERENCE_TIME unit
#define NS_PER_REFTIME (100)
thread_local HANDLE WASAPIAudioDevice::HelperTask_ = nullptr;
WASAPIAudioDevice::WASAPIAudioDevice(ComPtr<IAudioClient3> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels)
: Win32COMObject("WASAPIDev")
, client_(client)
, device_(device)
, renderClient_(nullptr)
, captureClient_(nullptr)
, direction_(direction)
, sampleRate_(sampleRate)
, numChannels_(numChannels)
, bufferFrameCount_(0)
, initialized_(false)
, latencyFrames_(0)
, renderCaptureEvent_(nullptr)
, isRenderCaptureRunning_(false)
, semaphore_(nullptr)
, highResTimer_(nullptr)
, tmpBuf_(nullptr)
, waitOvershootHns_(0)
{
// empty
}
WASAPIAudioDevice::~WASAPIAudioDevice()
{
stop();
// Release IAudioClient here as it can normally be
// reused after stop().
auto prom = std::make_shared<std::promise<void> >();
auto fut = prom->get_future();
enqueue_([&]() {
renderClient_ = nullptr;
captureClient_ = nullptr;
client_ = nullptr;
device_ = nullptr;
prom->set_value();
});
fut.wait();
}
int WASAPIAudioDevice::getNumChannels() FREEDV_NONBLOCKING
{
return numChannels_;
}
int WASAPIAudioDevice::getSampleRate() const FREEDV_NONBLOCKING
{
return sampleRate_;
}
void WASAPIAudioDevice::start()
{
log_info("Starting device with direction %d, sample rate %d, num channels %d", direction_, sampleRate_, numChannels_);
auto prom = std::make_shared<std::promise<void> >();
auto fut = prom->get_future();
enqueue_([&]() {
WAVEFORMATEX* streamFormatPtr = nullptr;
WAVEFORMATEX streamFormat;
bool freeStreamFormat = false;
// Set AudioClientProperties for stream. Must be done prior to Initialize().
AudioClientProperties prop;
prop.cbSize = sizeof(AudioClientProperties);
prop.bIsOffload = FALSE;
prop.eCategory = AudioCategory_Other;
prop.Options = AUDCLNT_STREAMOPTIONS_RAW;
HRESULT hr = client_->SetClientProperties(&prop);
if (FAILED(hr))
{
// Non-critical error, can continue without setting properties.
std::stringstream ss;
ss << "Could not set AudioClient properties (hr = " << hr << ")";
log_warn(ss.str().c_str());
}
// Populate stream format based on requested sample
// rate/number of channels.
// NOTE: this should already have been determined valid
// by the audio engine!
hr = client_->GetMixFormat(&streamFormatPtr);
if (SUCCEEDED(hr))
{
freeStreamFormat = true;
streamFormatPtr->nChannels = numChannels_;
streamFormatPtr->nSamplesPerSec = sampleRate_;
streamFormatPtr->nBlockAlign = (numChannels_ * streamFormatPtr->wBitsPerSample) / 8;
streamFormatPtr->nAvgBytesPerSec = streamFormatPtr->nSamplesPerSec * streamFormatPtr->nBlockAlign;
}
else
{
streamFormatPtr = &streamFormat;
streamFormat.wFormatTag = WAVE_FORMAT_PCM;
streamFormat.wBitsPerSample = 16;
streamFormat.nChannels = numChannels_;
streamFormat.nSamplesPerSec = sampleRate_;
streamFormat.nBlockAlign = (numChannels_ * streamFormat.wBitsPerSample) / 8;
streamFormat.nAvgBytesPerSec = streamFormat.nSamplesPerSec * streamFormat.nBlockAlign;
streamFormat.cbSize = 0;
}
// Set up for conversion to mix format
if (streamFormatPtr->wFormatTag == WAVE_FORMAT_PCM)
{
containerBits_ = streamFormatPtr->wBitsPerSample;
validBits_ = streamFormatPtr->wBitsPerSample;
isFloatingPoint_ = false;
log_info("Mix format is integer (container bits: %d, valid bits: %d)", containerBits_, validBits_);
}
else if (streamFormatPtr->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
{
containerBits_ = streamFormatPtr->wBitsPerSample;
validBits_ = streamFormatPtr->wBitsPerSample;
isFloatingPoint_ = true;
log_info("Mix format is floating point (container bits: %d, valid bits: %d)", containerBits_, validBits_);
}
else if (streamFormatPtr->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
WAVEFORMATEXTENSIBLE* extFormat = (WAVEFORMATEXTENSIBLE*)streamFormatPtr;
containerBits_ = streamFormatPtr->wBitsPerSample;
validBits_ = extFormat->Samples.wValidBitsPerSample;
if (extFormat->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
{
isFloatingPoint_ = false;
log_info("Mix format is integer (container bits: %d, valid bits: %d)", containerBits_, validBits_);
}
else if (extFormat->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
{
isFloatingPoint_ = true;
log_info("Mix format is floating point (container bits: %d, valid bits: %d)", containerBits_, validBits_);
}
else
{
std::stringstream ss;
ss << "Unknown mix format found: " << GuidToString_(&extFormat->SubFormat);
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
if (freeStreamFormat)
{
CoTaskMemFree(streamFormatPtr);
}
prom->set_value();
return;
}
}
else
{
std::stringstream ss;
ss << "Unknown mix format found: " << streamFormatPtr->wFormatTag;
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
if (freeStreamFormat)
{
CoTaskMemFree(streamFormatPtr);
}
prom->set_value();
return;
}
if (!initialized_)
{
REFERENCE_TIME desiredRefTime = BLOCK_TIME_NS / NS_PER_REFTIME; // REFERENCE_TIME is in 100ns units
hr = client_->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
desiredRefTime,
0,
streamFormatPtr,
nullptr);
if (freeStreamFormat)
{
CoTaskMemFree(streamFormatPtr);
}
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not initialize AudioClient (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
prom->set_value();
return;
}
initialized_ = true;
}
else if (freeStreamFormat)
{
CoTaskMemFree(streamFormatPtr);
}
// Create render/capture event
renderCaptureEvent_ = CreateEvent(nullptr, false, false, nullptr);
if (renderCaptureEvent_ == nullptr)
{
std::stringstream ss;
ss << "Could not create event (hr = " << GetLastError() << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
prom->set_value();
return;
}
// Assign render/capture event
hr = client_->SetEventHandle(renderCaptureEvent_);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not assign event handle (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
CloseHandle(renderCaptureEvent_);
prom->set_value();
return;
}
// Get actual allocated buffer size
hr = client_->GetBufferSize(&bufferFrameCount_);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get buffer size (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
prom->set_value();
return;
}
log_info("Allocated %d frames for audio buffers", bufferFrameCount_);
// Get latency
latencyFrames_ = bufferFrameCount_;
REFERENCE_TIME latency = 0;
hr = client_->GetStreamLatency(&latency);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get latency (hr = " << hr << ")";
log_warn(ss.str().c_str());
}
else
{
latencyFrames_ += sampleRate_ * ((double)(NS_PER_REFTIME * latency) / 1e9);
}
// Get capture/render client
if (direction_ == IAudioEngine::AUDIO_ENGINE_IN)
{
hr = client_->GetService(
IID_IAudioCaptureClient,
(void**)captureClient_.GetAddressOf());
}
else
{
hr = client_->GetService(
IID_IAudioRenderClient,
(void**)renderClient_.GetAddressOf());
}
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get render/capture client (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
prom->set_value();
return;
}
// Allocate temporary buffer
tmpBuf_ = new short[sampleRate_];
assert(tmpBuf_ != nullptr);
memset(tmpBuf_, 0, sizeof(short) * sampleRate_);
if (direction_ == IAudioEngine::AUDIO_ENGINE_OUT)
{
// Perform initial population of audio buffer
BYTE* data = nullptr;
hr = renderClient_->GetBuffer(bufferFrameCount_, &data);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get render buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
renderClient_ = nullptr;
delete[] tmpBuf_;
tmpBuf_ = nullptr;
prom->set_value();
return;
}
if (onAudioDataFunction)
{
onAudioDataFunction(*this, tmpBuf_, bufferFrameCount_, onAudioDataState);
}
copyToWindowsBuffer_(data, bufferFrameCount_);
hr = renderClient_->ReleaseBuffer(bufferFrameCount_, 0);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not release render buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
renderClient_ = nullptr;
delete[] tmpBuf_;
tmpBuf_ = nullptr;
prom->set_value();
return;
}
}
// Create semaphore
semaphore_ = CreateSemaphore(nullptr, 0, 1, nullptr);
if (semaphore_ == nullptr)
{
std::stringstream ss;
ss << "Could not create semaphore (err = " << GetLastError() << ")";
log_warn(ss.str().c_str());
}
// Create a high-resolution waitable timer for the debt-compensated
// wait in stopRealTimeWork(), so that wait isn't limited to
// WaitForSingleObject's millisecond-granular timeout. The high-res
// flag requires Windows 10 1803+; fall back to a regular (still
// usable, just lower-resolution) waitable timer if unavailable.
highResTimer_ = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (highResTimer_ == nullptr)
{
highResTimer_ = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS);
}
if (highResTimer_ == nullptr)
{
std::stringstream ss;
ss << "Could not create waitable timer (err = " << GetLastError() << ")";
log_warn(ss.str().c_str());
}
// Reduce Windows timer resolution to 1ms to improve WaitForSingleObject
// precision in the audio loop.
timeBeginPeriod(1);
// Start render/capture
hr = client_->Start();
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not start audio device (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
renderClient_ = nullptr;
captureClient_ = nullptr;
delete[] tmpBuf_;
tmpBuf_ = nullptr;
prom->set_value();
return;
}
// Start render/capture thread.
isRenderCaptureRunning_ = true;
renderCaptureThread_ = std::thread([this]() {
log_info("Starting render/capture thread");
// Capture references for use by this thread.
ComPtr<IAudioRenderClient> renderClientRef = renderClient_;
ComPtr<IAudioCaptureClient> captureClientRef = captureClient_;
ComPtr<IAudioClient3> clientRef = client_;
HRESULT res = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(res))
{
log_warn("Could not initialize COM (res = %d)", res);
}
// Temporarily raise priority of task
setHelperRealTime();
while (isRenderCaptureRunning_)
{
WaitForSingleObject(renderCaptureEvent_, 100);
if (isRenderCaptureRunning_)
{
if (direction_ == IAudioEngine::AUDIO_ENGINE_OUT)
{
renderAudio_(renderClientRef);
}
else
{
captureAudio_(captureClientRef);
}
}
}
log_info("Exiting render/capture thread");
clearHelperRealTime();
CoUninitialize();
});
prom->set_value();
});
fut.wait();
}
void WASAPIAudioDevice::stop()
{
log_info("Stopping device with direction %d, sample rate %d, num channels %d", direction_, sampleRate_, numChannels_);
auto prom = std::make_shared<std::promise<void> >();
auto fut = prom->get_future();
enqueue_([&]() {
isRenderCaptureRunning_ = false;
if (renderCaptureThread_.joinable())
{
renderCaptureThread_.join();
}
if (renderClient_ || captureClient_)
{
HRESULT hr = client_->Stop();
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not stop audio device (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
}
}
renderClient_ = nullptr;
captureClient_ = nullptr;
timeEndPeriod(1);
if (renderCaptureEvent_ != nullptr)
{
CloseHandle(renderCaptureEvent_);
renderCaptureEvent_ = nullptr;
}
if (semaphore_ != nullptr)
{
// Set semaphore_ to nullptr first in case someone could be potentially
// using it. Then for those currently waiting, release the semaphore
// to get them unstuck. THEN we can close it. Otherwise, heap corruption
// occurs!
auto tmpSem = semaphore_;
semaphore_ = nullptr;
ReleaseSemaphore(tmpSem, 1, nullptr);
CloseHandle(tmpSem);
}
if (highResTimer_ != nullptr)
{
// Same ordering rationale as semaphore_ above: null out, signal
// any waiter unstuck, then close.
auto tmpTimer = highResTimer_;
highResTimer_ = nullptr;
LARGE_INTEGER dueTime;
dueTime.QuadPart = 0;
SetWaitableTimer(tmpTimer, &dueTime, 0, nullptr, nullptr, FALSE);
CloseHandle(tmpTimer);
}
if (tmpBuf_ != nullptr)
{
delete[] tmpBuf_;
tmpBuf_ = nullptr;
}
prom->set_value();
});
fut.wait();
}
bool WASAPIAudioDevice::isRunning()
{
return (renderClient_) || (captureClient_);
}
int64_t WASAPIAudioDevice::getLatencyInMicroseconds()
{
// Note: latencyFrames_ isn't expected to change, so we don't need to
// wrap this call in an enqueue_() like with the other public methods.
return (int64_t)1000000 * (int64_t)latencyFrames_ / sampleRate_;
}
void WASAPIAudioDevice::setHelperRealTime()
{
DWORD taskIndex = 0;
HelperTask_ = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
if (HelperTask_ == nullptr)
{
log_warn("Could not increase thread priority");
return;
}
// AvSetMmThreadCharacteristics() alone only enrolls the thread in the
// "Pro Audio" MMCSS class at that class's default (Normal) priority
// band. This thread's wait in stopRealTimeWork() is on the critical
// path for audio timing (it's what TxRxThread's wait/TX/RX stats
// measure), so bump it to the top of the band to cut down on how long
// it sits ready-but-not-running behind other MMCSS-scheduled threads
// after the semaphore/timer wakes it -- that scheduling delay is what
// shows up as wait jitter (stdev/max) rather than the wait target
// itself being wrong.
if (!AvSetMmThreadPriority(HelperTask_, AVRT_PRIORITY_CRITICAL))
{
log_warn("Could not raise MMCSS thread priority to critical (err = %lu)", GetLastError());
}
}
void WASAPIAudioDevice::startRealTimeWork()
{
startTime_ = std::chrono::steady_clock::now();
}
void WASAPIAudioDevice::stopRealTimeWork(bool fastMode)
{
if (semaphore_ == nullptr || highResTimer_ == nullptr)
{
// Fallback to base class behavior
IAudioDevice::stopRealTimeWork();
return;
}
// Nominal period in 100ns units -- matches what SetWaitableTimer expects,
// and lets the compensation below track sub-millisecond amounts instead
// of being rounded down to whole milliseconds.
int64_t nominalHns = ((10000000LL * bufferFrameCount_) / sampleRate_) >> (fastMode ? 1 : 0);
// Compensate for how much of the period THIS cycle's own processing
// already used, measured directly against startTime_ (set by
// startRealTimeWork() right before processing began) rather than against
// a debt figure copied from the *previous* cycle. The previous approach
// lagged by one cycle: it corrected this wait for last cycle's overrun
// instead of this cycle's own, which overcorrects/undercorrects whenever
// processing time varies cycle to cycle instead of holding steady.
// waitOvershootHns_ separately tracks only the wait itself running long
// (the one thing that genuinely can't be known until after it happens),
// so a systematic scheduling overshoot still can't accumulate into drift.
auto elapsedHns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - startTime_).count() / 100;
int64_t hns = nominalHns - elapsedHns - waitOvershootHns_;
if (hns <= 0)
{
waitOvershootHns_ = 0;
return;
}
// Arm a high-resolution one-shot timer for the compensated duration
// (negative = relative time, in 100ns units) and wait on it alongside the
// semaphore -- this is what lets the wait itself use a precision finer
// than WaitForSingleObject's millisecond-granular timeout would allow.
LARGE_INTEGER dueTime;
dueTime.QuadPart = -hns;
SetWaitableTimer(highResTimer_, &dueTime, 0, nullptr, nullptr, FALSE);
auto waitStartTime = std::chrono::steady_clock::now();
HANDLE waitHandles[2] = { semaphore_, highResTimer_ };
DWORD result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
auto actualWaitHns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - waitStartTime).count() / 100;
waitOvershootHns_ = std::max((int64_t)0, actualWaitHns - hns); // cap at >= 0; an early (semaphore) wake isn't overshoot.
if (result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 1)
{
// Fallback to a simple sleep.
IAudioDevice::stopRealTimeWork(fastMode);
}
}
void WASAPIAudioDevice::clearHelperRealTime()
{
if (HelperTask_ != nullptr)
{
AvRevertMmThreadCharacteristics(HelperTask_);
HelperTask_ = nullptr;
}
}
void WASAPIAudioDevice::renderAudio_(ComPtr<IAudioRenderClient> renderClient)
{
// If client is no longer available, abort
if (!renderClient)
{
return;
}
// Get available buffer space
UINT32 padding = 0;
UINT32 framesAvailable = 0;
BYTE* data = nullptr;
HRESULT hr = client_->GetCurrentPadding(&padding);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not get current padding (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
framesAvailable = bufferFrameCount_ - padding;
hr = renderClient->GetBuffer(framesAvailable, &data);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not get render buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
// Grab audio data from higher level code
if (framesAvailable > 0 && data != nullptr)
{
memset(tmpBuf_, 0, framesAvailable * numChannels_ * sizeof(short));
if (onAudioDataFunction)
{
onAudioDataFunction(*this, tmpBuf_, framesAvailable, onAudioDataState);
}
copyToWindowsBuffer_(data, framesAvailable);
}
// Release render buffer
hr = renderClient->ReleaseBuffer(framesAvailable, 0);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not release render buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
}
void WASAPIAudioDevice::captureAudio_(ComPtr<IAudioCaptureClient> captureClient)
{
// If client is no longer available, abort
if (!captureClient)
{
return;
}
// Get packet length
UINT32 packetLength = 0;
HRESULT hr = captureClient->GetNextPacketSize(&packetLength);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not get packet length (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
while(packetLength != 0)
{
BYTE* data = nullptr;
UINT32 numFramesAvailable = 0;
DWORD flags = 0;
hr = captureClient->GetBuffer(
&data,
&numFramesAvailable,
&flags,
nullptr,
nullptr);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not get capture buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
if (numFramesAvailable > 0 && data != nullptr)
{
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
memset(tmpBuf_, 0, numFramesAvailable * numChannels_ * sizeof(short));
}
else
{
copyFromWindowsBuffer_(data, numFramesAvailable);
}
if (onAudioDataFunction)
{
onAudioDataFunction(*this, tmpBuf_, numFramesAvailable, onAudioDataState);
}
}
// Release buffer
hr = captureClient->ReleaseBuffer(numFramesAvailable);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not release capture buffer (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
hr = captureClient->GetNextPacketSize(&packetLength);
if (FAILED(hr))
{
// Note: don't call to event handler to avoid annoying the user
// with popups.
std::stringstream ss;
ss << "Could not get packet length (hr = " << hr << ")";
log_error(ss.str().c_str());
return;
}
}
if (semaphore_ != nullptr)
{
// Notify worker threads
ReleaseSemaphore(semaphore_, 1, nullptr);
}
}
void WASAPIAudioDevice::copyFromWindowsBuffer_(void* buf, int numFrames)
{
if (isFloatingPoint_)
{
if (validBits_ == sizeof(float) * 8)
{
copyFloatToShort_<float>((float*)buf, numFrames);
}
else if (validBits_ == sizeof(double) * 8)
{
copyFloatToShort_<double>((double*)buf, numFrames);
}
}
else
{
if (containerBits_ == 8)
{
copyIntToShort_<char>((char*)buf, numFrames);
}
else if (containerBits_ == 16 && validBits_ == 16)
{
// Shortcut -- can just memcpy into tmpBuf_.
memcpy(tmpBuf_, buf, numFrames * numChannels_ * sizeof(short));
}
else if (containerBits_ == 16)
{
copyIntToShort_<short>((short*)buf, numFrames);
}
else if (containerBits_ == 32)
{
copyIntToShort_<int32_t>((int32_t*)buf, numFrames);
}
}
}
void WASAPIAudioDevice::copyToWindowsBuffer_(void* buf, int numFrames)
{
if (isFloatingPoint_)
{
if (validBits_ == sizeof(float) * 8)
{
copyShortToFloat_<float>((float*)buf, numFrames);
}
else if (validBits_ == sizeof(double) * 8)
{
copyShortToFloat_<double>((double*)buf, numFrames);
}
}
else
{
if (containerBits_ == 8)
{
copyShortToInt_<char>((char*)buf, numFrames);
}
else if (containerBits_ == 16 && validBits_ == 16)
{
// Shortcut -- can just memcpy from tmpBuf_.
memcpy(buf, tmpBuf_, numFrames * numChannels_ * sizeof(short));
}
else if (containerBits_ == 16)
{
copyShortToInt_<short>((short*)buf, numFrames);
}
else if (containerBits_ == 32)
{
copyShortToInt_<int32_t>((int32_t*)buf, numFrames);
}
}
}
std::string WASAPIAudioDevice::GuidToString_(GUID *guid)
{
char guid_string[37]; // 32 hex chars + 4 hyphens + null terminator
snprintf(
guid_string, sizeof(guid_string),
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
(unsigned int)guid->Data1, (unsigned int)guid->Data2, (unsigned int)guid->Data3,
guid->Data4[0], guid->Data4[1], guid->Data4[2],
guid->Data4[3], guid->Data4[4], guid->Data4[5],
guid->Data4[6], guid->Data4[7]);
return guid_string;
}