diff --git a/client/client_settings.hpp b/client/client_settings.hpp index 0fca36eb..4331d95b 100644 --- a/client/client_settings.hpp +++ b/client/client_settings.hpp @@ -27,6 +27,14 @@ struct ClientSettings { +#ifdef HAS_WASAPI + enum class WasapiMode + { + SHARED, + EXCLUSIVE + }; +#endif + struct ServerSettings { std::string host{""}; @@ -39,6 +47,9 @@ struct ClientSettings int latency{0}; PcmDevice pcm_device; SampleFormat sample_format; +#ifdef HAS_WASAPI + WasapiMode wasapi_mode{ WasapiMode::SHARED }; +#endif }; struct LoggingSettings diff --git a/client/controller.cpp b/client/controller.cpp index 8b8e6535..d54192fd 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -145,7 +145,7 @@ void Controller::onMessageReceived(ClientConnection* /*connection*/, const msg:: #endif #ifdef HAS_WASAPI if (!player_ && (player_name.empty() || (player_name == "wasapi"))) - player_ = make_unique(pcm_device, stream_); + player_ = make_unique(pcm_device, stream_, settings_.player.wasapi_mode); #endif if (!player_) throw SnapException("No audio player support"); diff --git a/client/player/wasapi_player.cpp b/client/player/wasapi_player.cpp index 6d2dc1ca..d94e99a3 100644 --- a/client/player/wasapi_player.cpp +++ b/client/player/wasapi_player.cpp @@ -61,7 +61,7 @@ EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY PKEY_Device_FriendlyName = { { 0xa #define CHECK_HR(hres) if(FAILED(hres)){stringstream ss;ss<<"HRESULT fault status: "< stream) : Player(pcmDevice, stream) +WASAPIPlayer::WASAPIPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream, ClientSettings::WasapiMode mode) : Player(pcmDevice, stream), mode_(mode) { HRESULT hr = CoInitializeEx( NULL, @@ -92,7 +92,7 @@ inline PcmDevice convertToDevice(int idx, IMMDevicePtr& device) PropVariantInit(&deviceName); hr = properties->GetValue(PKEY_Device_FriendlyName, &deviceName); - CHECK_HR(hr); + CHECK_HR(hr); desc.idx = idx; desc.name = wstring_convert, wchar_t>().to_bytes(id); @@ -214,18 +214,20 @@ void WASAPIPlayer::worker() CHECK_HR(hr); PWAVEFORMATEX formatEx = (PWAVEFORMATEX)format.blob.pBlobData; - LOG(INFO, LOG_TAG) << "Device accepts format: " << formatEx->nSamplesPerSec << ":" << formatEx->wBitsPerSample << ":" << formatEx->nChannels << "\n"; - + LOG(INFO, LOG_TAG) << "Device accepts format: " << formatEx->nSamplesPerSec << ":" << formatEx->wBitsPerSample << ":" << formatEx->nChannels << "\n"; // Activate the device IAudioClientPtr audioClient = nullptr; hr = device->Activate(IID_IAudioClient, CLSCTX_SERVER, NULL, (void**)&audioClient); CHECK_HR(hr); - hr = audioClient->IsFormatSupported( - AUDCLNT_SHAREMODE_EXCLUSIVE, - &(waveformatExtended->Format), - NULL); - CHECK_HR(hr); + if(mode_ == ClientSettings::WasapiMode::EXCLUSIVE) + { + hr = audioClient->IsFormatSupported( + AUDCLNT_SHAREMODE_EXCLUSIVE, + &(waveformatExtended->Format), + NULL); + CHECK_HR(hr); + } IAudioSessionManagerPtr sessionManager = nullptr; // Get the session manager for the endpoint device. @@ -249,14 +251,22 @@ void WASAPIPlayer::worker() hr = audioClient->GetDevicePeriod(NULL, &hnsRequestedDuration); CHECK_HR(hr); + LOG(INFO, LOG_TAG) << "Initializing WASAPI in " << (mode_ == ClientSettings::WasapiMode::SHARED ? "shared" : "exclusive") << " mode\n"; + + _AUDCLNT_SHAREMODE share_mode = mode_ == ClientSettings::WasapiMode::SHARED ? AUDCLNT_SHAREMODE_SHARED : AUDCLNT_SHAREMODE_EXCLUSIVE; + DWORD stream_flags = mode_ == ClientSettings::WasapiMode::SHARED + ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY + : AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + // Initialize the client at minimum latency hr = audioClient->Initialize( - AUDCLNT_SHAREMODE_EXCLUSIVE, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + share_mode, + stream_flags, hnsRequestedDuration, hnsRequestedDuration, &(waveformatExtended->Format), NULL); + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { UINT32 alignedBufferSize; @@ -267,8 +277,8 @@ void WASAPIPlayer::worker() hr = device->Activate(IID_IAudioClient, CLSCTX_SERVER, NULL, (void**)&audioClient); CHECK_HR(hr); hr = audioClient->Initialize( - AUDCLNT_SHAREMODE_EXCLUSIVE, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + share_mode, + stream_flags, hnsRequestedDuration, hnsRequestedDuration, &(waveformatExtended->Format), @@ -350,20 +360,32 @@ void WASAPIPlayer::worker() volCorrection_ = audioEventListener_->getVolume(); clock->GetPosition(&position, NULL); + + UINT32 padding = 0; + if(mode_ == ClientSettings::WasapiMode::SHARED) + { + hr = audioClient->GetCurrentPadding(&padding); + CHECK_HR(hr); + } + + int available = bufferFrameCount - padding; if (stream_->getPlayerChunk(queueBuffer.get(), microseconds( ((bufferPosition * 1000000) / waveformat->nSamplesPerSec) - ((position * 1000000) / frequency)), - bufferFrameCount)) + available)) { - adjustVolume(queueBuffer.get(), bufferFrameCount); - hr = renderClient->GetBuffer(bufferFrameCount, &buffer); - CHECK_HR(hr); - memcpy(buffer, queueBuffer.get(), bufferSize); - hr = renderClient->ReleaseBuffer(bufferFrameCount, 0); - CHECK_HR(hr); + if (available > 0) + { + adjustVolume(queueBuffer.get(), available); + hr = renderClient->GetBuffer(available, &buffer); + CHECK_HR(hr); + memcpy(buffer, queueBuffer.get(), bufferSize); + hr = renderClient->ReleaseBuffer(available, 0); + CHECK_HR(hr); - bufferPosition += bufferFrameCount; + bufferPosition += available; + } } else { diff --git a/client/player/wasapi_player.h b/client/player/wasapi_player.h index 046f92f4..ecf3c445 100644 --- a/client/player/wasapi_player.h +++ b/client/player/wasapi_player.h @@ -18,9 +18,9 @@ #ifndef WASAPI_PLAYER_H #define WASAPI_PLAYER_H - #include "player.hpp" #include +#include "client_settings.hpp" class AudioSessionEventListener : public IAudioSessionEvents { @@ -93,7 +93,7 @@ public: class WASAPIPlayer : public Player { public: - WASAPIPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); + WASAPIPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream, ClientSettings::WasapiMode mode); virtual ~WASAPIPlayer(); static std::vector pcm_list(void); @@ -102,6 +102,7 @@ protected: private: AudioSessionEventListener* audioEventListener_; + ClientSettings::WasapiMode mode_; }; #endif diff --git a/client/snapclient.cpp b/client/snapclient.cpp index 81eadb06..dc5c99aa 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -115,6 +115,9 @@ int main(int argc, char** argv) #ifdef HAS_SOXR auto sample_format = op.add>("", "sampleformat", "resample audio stream to ::", ""); #endif +#ifdef HAS_WASAPI + auto wasapi_mode = op.add>("", "wasapimode", "WASAPI mode to use [shared/exclusive]", "shared"); +#endif try { @@ -225,10 +228,6 @@ int main(int argc, char** argv) } #endif -#if defined(HAS_WASAPI) - settings.player.player_name = "wasapi"; -#endif - #ifdef HAS_SOXR if (sample_format->is_set()) { @@ -241,6 +240,15 @@ int main(int argc, char** argv) } #endif +#ifdef HAS_WASAPI + settings.player.player_name = "wasapi"; + if (wasapi_mode->is_set()) + { + settings.player.wasapi_mode = (strcmp(wasapi_mode->value().c_str(), "exclusive") == 0) ? + ClientSettings::WasapiMode::EXCLUSIVE : ClientSettings::WasapiMode::SHARED; + } +#endif + bool active = true; std::shared_ptr controller; auto signal_handler = install_signal_handler({