/* Copyright 2016, Ableton AG, Berlin. 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 as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 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 . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_Wasapi.hpp" #include #include // WARNING: This file provides an audio driver for Windows using WASAPI. This driver is // considered experimental and has problems with low-latency playback. Please consider // using the ASIO driver instead. namespace ableton { namespace linkaudio { // GUID identifiers used to when looking up COM enumerators and devices static const IID kMMDeviceEnumeratorId = __uuidof(MMDeviceEnumerator); static const IID kIMMDeviceEnumeratorId = __uuidof(IMMDeviceEnumerator); static const IID kAudioClientId = __uuidof(IAudioClient); static const IID kAudioRenderClientId = __uuidof(IAudioRenderClient); // Controls how large the driver's ring buffer will be, expressed in terms of // 100-nanosecond units. This value also influences the overall driver latency. static const REFERENCE_TIME kBufferDuration = 1000000; // How long to block the runloop while waiting for an event callback. static const DWORD kWaitTimeoutInMs = 2000; void fatalError(HRESULT result, LPCTSTR context) { if (result > 0) { _com_error error(result); LPCTSTR errorMessage = error.ErrorMessage(); std::cerr << context << ": " << errorMessage << std::endl; } else { std::cerr << context << std::endl; } std::terminate(); } DWORD renderAudioRunloop(LPVOID lpParam) { AudioPlatform* platform = static_cast(lpParam); return platform->audioRunloop(); } AudioPlatform::AudioPlatform(Link& link) : mEngine(link) , mSampleTime(0) , mDevice(nullptr) , mAudioClient(nullptr) , mRenderClient(nullptr) , mStreamFormat(nullptr) , mEventHandle(nullptr) , mAudioThreadHandle(nullptr) , mIsRunning(false) { initialize(); mEngine.setBufferSize(bufferSize()); mEngine.setSampleRate(mStreamFormat->nSamplesPerSec); start(); } AudioPlatform::~AudioPlatform() { // WARNING: Here be dragons! // The WASAPI driver is not thread-safe, and crashes may occur when shutting down due // to these fields being concurrently accessed in the audio thread. Introducing a mutex // in the audio thread is not an appropriate solution to fix this race condition; a more // robust solution needs to be considered instead. if (mDevice != nullptr) { mDevice->Release(); } if (mAudioClient != nullptr) { mAudioClient->Release(); } if (mRenderClient != nullptr) { mRenderClient->Release(); } CoTaskMemFree(mStreamFormat); } UINT32 AudioPlatform::bufferSize() { UINT32 bufferSize; HRESULT result = mAudioClient->GetBufferSize(&bufferSize); if (FAILED(result)) { fatalError(result, "Could not get buffer size"); return 0; // not reached } return bufferSize; } void AudioPlatform::initialize() { HRESULT result = CoInitialize(nullptr); if (FAILED(result)) { fatalError(result, "Could not initialize COM library"); } IMMDeviceEnumerator* enumerator = nullptr; result = CoCreateInstance(kMMDeviceEnumeratorId, nullptr, CLSCTX_ALL, kIMMDeviceEnumeratorId, (void**)&enumerator); if (FAILED(result)) { fatalError(result, "Could not create device instance"); } result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &(mDevice)); if (FAILED(result)) { fatalError(result, "Could not get default audio endpoint"); } else { enumerator->Release(); enumerator = nullptr; } result = mDevice->Activate(kAudioClientId, CLSCTX_ALL, nullptr, (void**)&(mAudioClient)); if (FAILED(result)) { fatalError(result, "Could not activate audio device"); } result = mAudioClient->GetMixFormat(&(mStreamFormat)); if (FAILED(result)) { fatalError(result, "Could not get mix format"); } if (mStreamFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { WAVEFORMATEXTENSIBLE* streamFormatEx = reinterpret_cast(mStreamFormat); if (streamFormatEx->SubFormat != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) { fatalError(0, "Sorry, only IEEE floating point streams are supported"); } } else { fatalError(0, "Sorry, only extensible wave streams are supported"); } result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, kBufferDuration, 0, mStreamFormat, nullptr); if (FAILED(result)) { fatalError(result, "Could not initialize audio device"); } mEventHandle = CreateEvent(nullptr, false, false, nullptr); if (mEventHandle == nullptr) { fatalError(result, "Could not create event handle"); } result = mAudioClient->GetService(kAudioRenderClientId, (void**)&(mRenderClient)); if (FAILED(result)) { fatalError(result, "Could not get audio render service"); } mIsRunning = true; LPTHREAD_START_ROUTINE threadEntryPoint = reinterpret_cast(renderAudioRunloop); mAudioThreadHandle = CreateThread(nullptr, 0, threadEntryPoint, this, 0, nullptr); if (mAudioThreadHandle == nullptr) { fatalError(GetLastError(), "Could not create audio thread"); } } void AudioPlatform::start() { UINT32 bufSize = bufferSize(); BYTE* buffer; HRESULT result = mRenderClient->GetBuffer(bufSize, &buffer); if (FAILED(result)) { fatalError(result, "Could not get render client buffer (in start audio engine)"); } result = mRenderClient->ReleaseBuffer(bufSize, 0); if (FAILED(result)) { fatalError(result, "Could not release buffer"); } result = mAudioClient->SetEventHandle(mEventHandle); if (FAILED(result)) { fatalError(result, "Could not set event handle to audio client"); } REFERENCE_TIME latency; result = mAudioClient->GetStreamLatency(&latency); if (FAILED(result)) { fatalError(result, "Could not get stream latency"); } result = mAudioClient->Start(); if (FAILED(result)) { fatalError(result, "Could not start audio client"); } } DWORD AudioPlatform::audioRunloop() { while (mIsRunning) { DWORD wait = WaitForSingleObject(mEventHandle, kWaitTimeoutInMs); if (wait != WAIT_OBJECT_0) { mIsRunning = false; mAudioClient->Stop(); return wait; } // Get the amount of padding, which basically is the amount of data in the driver's // ring buffer that is filled with unread data. Thus, subtracting this amount from // the buffer size gives the effective buffer size, which is the amount of frames // that can be safely written to the driver. UINT32 paddingFrames; HRESULT result = mAudioClient->GetCurrentPadding(&paddingFrames); if (FAILED(result)) { fatalError(result, "Could not get number of padding frames"); } const UINT32 numSamples = bufferSize() - paddingFrames; BYTE* buffer; result = mRenderClient->GetBuffer(numSamples, &buffer); if (FAILED(result)) { fatalError(result, "Could not get render client buffer (in callback)"); } const double sampleRate = static_cast(mStreamFormat->nSamplesPerSec); using namespace std::chrono; const auto bufferDuration = duration_cast(duration{numSamples / sampleRate}); const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime); mSampleTime += numSamples; const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency.load(); mEngine.audioCallback(bufferBeginAtOutput, numSamples); float* floatBuffer = reinterpret_cast(buffer); for (WORD i = 0; i < numSamples; ++i) { if (i >= mEngine.mBuffer.size()) { break; } for (WORD j = 0; j < mStreamFormat->nChannels; ++j) { floatBuffer[j + (i * mStreamFormat->nChannels)] = static_cast(mEngine.mBuffer[i]); } } // Write the buffer to the audio driver and subsequently free the buffer memory result = mRenderClient->ReleaseBuffer(numSamples, 0); if (FAILED(result)) { fatalError(result, "Error rendering data"); } } // end of runloop mIsRunning = false; return 0; } // void fillBuffer(MetronomeSynth& metronome, // const UINT32 startFrame, // const UINT32 numSamples, // const UINT32 numChannels, // BYTE* buffer) //{ // float* floatBuffer = reinterpret_cast(buffer); // UINT32 frame = startFrame; // while (frame < numSamples * numChannels) // { // const float sample = static_cast(metronome.getSample()); // for (UINT32 channel = 0; channel < numChannels; ++channel) // { // floatBuffer[frame++] = sample; // } // } //} } // namespace linkaudio } // namespace ableton