Merge pull request #12947 from unknownbrackets/wasapi-format

Windows: Avoid a WASAPI crash for a mono output format
This commit is contained in:
Henrik Rydgård 2020-05-22 12:33:45 +02:00 committed by GitHub
commit c861bc0d7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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<std::mutex> guard(lock_);
if (currentDevice_)
CoTaskMemFree(currentDevice_);
CoTaskMemFree(currentDevice_);
currentDevice_ = nullptr;
if (!device || FAILED(device->GetId(&currentDevice_))) {
currentDevice_ = nullptr;
}
@ -196,6 +195,8 @@ private:
bool InitAudioDevice();
void ShutdownAudioDevice();
bool DetectFormat();
bool ValidateFormat(const WAVEFORMATEXTENSIBLE *fmt);
bool PrepareFormat();
std::atomic<int> &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;
}