diff --git a/Windows/WASAPIStream.cpp b/Windows/WASAPIStream.cpp index 3b04029bf8..d399781c46 100644 --- a/Windows/WASAPIStream.cpp +++ b/Windows/WASAPIStream.cpp @@ -35,8 +35,7 @@ public: } ~CMMNotificationClient() { - if (currentDevice_) - CoTaskMemFree(currentDevice_); + CoTaskMemFree(currentDevice_); currentDevice_ = nullptr; SAFE_RELEASE(_pEnumerator) } @@ -44,8 +43,8 @@ public: void SetCurrentDevice(IMMDevice *device) { std::lock_guard guard(lock_); - if (currentDevice_) - CoTaskMemFree(currentDevice_); + CoTaskMemFree(currentDevice_); + currentDevice_ = nullptr; if (!device || FAILED(device->GetId(¤tDevice_))) { currentDevice_ = nullptr; } @@ -196,6 +195,8 @@ private: bool InitAudioDevice(); void ShutdownAudioDevice(); bool DetectFormat(); + bool ValidateFormat(const WAVEFORMATEXTENSIBLE *fmt); + bool PrepareFormat(); std::atomic &threadData_; int &sampleRate_; @@ -252,6 +253,11 @@ bool WASAPIAudioThread::InitAudioDevice() { if (FAILED(hresult) || !deviceFormat_) return false; + if (!DetectFormat()) { + // Format unsupported - let's not even try to initialize. + return false; + } + hresult = audioInterface_->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsBufferDuration, 0, &deviceFormat_->Format, nullptr); if (FAILED(hresult)) return false; @@ -279,33 +285,84 @@ void WASAPIAudioThread::ShutdownAudioDevice() { } bool WASAPIAudioThread::DetectFormat() { + if (!ValidateFormat(deviceFormat_)) { + // Last chance, let's try to ask for one we support instead. + WAVEFORMATEXTENSIBLE fmt{}; + fmt.Format.cbSize = sizeof(fmt); + fmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + fmt.Format.nChannels = 2; + fmt.Format.nSamplesPerSec = 44100; + if (deviceFormat_->Format.nSamplesPerSec >= 22050 && deviceFormat_->Format.nSamplesPerSec <= 192000) + fmt.Format.nSamplesPerSec = deviceFormat_->Format.nSamplesPerSec; + fmt.Format.nBlockAlign = 2 * sizeof(float); + fmt.Format.nAvgBytesPerSec = fmt.Format.nSamplesPerSec * fmt.Format.nBlockAlign; + fmt.Format.wBitsPerSample = sizeof(float) * 8; + fmt.Samples.wReserved = 0; + fmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + fmt.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + WAVEFORMATEXTENSIBLE *closest = nullptr; + HRESULT hr = audioInterface_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &fmt.Format, (WAVEFORMATEX **)&closest); + if (hr == S_OK) { + // Okay, great. Let's just use ours. + CoTaskMemFree(closest); + CoTaskMemFree(deviceFormat_); + deviceFormat_ = (WAVEFORMATEXTENSIBLE *)CoTaskMemAlloc(sizeof(fmt)); + memcpy(deviceFormat_, &fmt, sizeof(fmt)); + + // In case something above gets out of date. + return ValidateFormat(deviceFormat_); + } else if (hr == S_FALSE && closest != nullptr) { + // This means check closest. We'll allow it only if it's less specific on channels. + if (ValidateFormat(closest)) { + CoTaskMemFree(deviceFormat_); + deviceFormat_ = closest; + } else { + ERROR_LOG_REPORT_ONCE(badfallbackclosest, SCEAUDIO, "WASAPI fallback and closest unsupported"); + CoTaskMemFree(closest); + return false; + } + } else { + CoTaskMemFree(closest); + ERROR_LOG_REPORT_ONCE(badfallback, SCEAUDIO, "WASAPI fallback format was unsupported"); + return false; + } + } + + return true; +} + +bool WASAPIAudioThread::ValidateFormat(const WAVEFORMATEXTENSIBLE *fmt) { // Don't know if PCM16 ever shows up here, the documentation only talks about float... but let's blindly // try to support it :P format_ = Format::UNKNOWN; - if (deviceFormat_->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - if (!memcmp(&deviceFormat_->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(deviceFormat_->SubFormat))) { - format_ = Format::IEEE_FLOAT; + if (fmt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + if (!memcmp(&fmt->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(fmt->SubFormat))) { + if (fmt->Format.nChannels >= 2) + format_ = Format::IEEE_FLOAT; } else { ERROR_LOG_REPORT_ONCE(unexpectedformat, SCEAUDIO, "Got unexpected WASAPI 0xFFFE stream format, expected float!"); - if (deviceFormat_->Format.wBitsPerSample == 16 && deviceFormat_->Format.nChannels == 2) { + if (fmt->Format.wBitsPerSample == 16 && fmt->Format.nChannels == 2) { format_ = Format::PCM16; } } - } else if (deviceFormat_->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { - format_ = Format::IEEE_FLOAT; + } else if (fmt->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { + if (fmt->Format.nChannels >= 2) + format_ = Format::IEEE_FLOAT; } else { ERROR_LOG_REPORT_ONCE(unexpectedformat2, SCEAUDIO, "Got unexpected non-extensible WASAPI stream format, expected extensible float!"); - if (deviceFormat_->Format.wBitsPerSample == 16 && deviceFormat_->Format.nChannels == 2) { + if (fmt->Format.wBitsPerSample == 16 && fmt->Format.nChannels == 2) { format_ = Format::PCM16; } } + return format_ != Format::UNKNOWN; +} + +bool WASAPIAudioThread::PrepareFormat() { delete [] shortBuf_; shortBuf_ = nullptr; - if (format_ == Format::UNKNOWN) { - return false; - } BYTE *pData = nullptr; HRESULT hresult = renderClient_->GetBuffer(numBufferFrames, &pData); @@ -356,7 +413,7 @@ void WASAPIAudioThread::Run() { ERROR_LOG(SCEAUDIO, "WASAPI: Could not init audio device"); return; } - if (!DetectFormat()) { + if (!PrepareFormat()) { ERROR_LOG(SCEAUDIO, "WASAPI: Could not find a suitable audio output format"); return; } @@ -384,14 +441,14 @@ void WASAPIAudioThread::Run() { if (FAILED(hresult) || pData == nullptr) { // What to do? } else if (pNumAvFrames) { + int chans = deviceFormat_->Format.nChannels; switch (format_) { case Format::IEEE_FLOAT: - callback_(shortBuf_, pNumAvFrames, 16, sampleRate_, 2); - if (deviceFormat_->Format.nChannels == 2) { - ConvertS16ToF32((float *)pData, shortBuf_, pNumAvFrames * deviceFormat_->Format.nChannels); - } else { + callback_(shortBuf_, pNumAvFrames, 16, sampleRate_, chans); + if (chans == 2) { + ConvertS16ToF32((float *)pData, shortBuf_, pNumAvFrames * chans); + } else if (chans > 2) { float *ptr = (float *)pData; - int chans = deviceFormat_->Format.nChannels; memset(ptr, 0, pNumAvFrames * chans * sizeof(float)); for (UINT32 i = 0; i < pNumAvFrames; i++) { ptr[i * chans + 0] = (float)shortBuf_[i * 2] * (1.0f / 32768.0f); @@ -400,7 +457,7 @@ void WASAPIAudioThread::Run() { } break; case Format::PCM16: - callback_((short *)pData, pNumAvFrames, 16, sampleRate_, 2); + callback_((short *)pData, pNumAvFrames, 16, sampleRate_, chans); break; } } @@ -430,7 +487,7 @@ void WASAPIAudioThread::Run() { ERROR_LOG(SCEAUDIO, "WASAPI: Could not init audio device"); return; } - if (!DetectFormat()) { + if (!PrepareFormat()) { ERROR_LOG(SCEAUDIO, "WASAPI: Could not find a suitable audio output format"); return; }