1 // Copyright 2005-2019 The Mumble Developers. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file at the root of the 4 // Mumble source tree or at <https://www.mumble.info/LICENSE>. 5 6 #include "mumble_pch.hpp" 7 8 #include "WASAPI.h" 9 #include "WASAPINotificationClient.h" 10 11 #include "MainWindow.h" 12 13 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include. 14 #include "Global.h" 15 16 // Now that Win7 is published, which includes public versions of these 17 // interfaces, we simply inherit from those but use the "old" IIDs. 18 19 DEFINE_GUID(IID_IVistaAudioSessionControl2, 0x33969B1DL, 0xD06F, 0x4281, 0xB8, 0x37, 0x7E, 0xAA, 0xFD, 0x21, 0xA9, 0xC0); 20 MIDL_INTERFACE("33969B1D-D06F-4281-B837-7EAAFD21A9C0") 21 IVistaAudioSessionControl2 : 22 public IAudioSessionControl2 { 23 }; 24 25 DEFINE_GUID(IID_IAudioSessionQuery, 0x94BE9D30L, 0x53AC, 0x4802, 0x82, 0x9C, 0xF1, 0x3E, 0x5A, 0xD3, 0x47, 0x75); 26 MIDL_INTERFACE("94BE9D30-53AC-4802-829C-F13E5AD34775") 27 IAudioSessionQuery : 28 public IUnknown { 29 virtual HRESULT STDMETHODCALLTYPE GetQueryInterface(IAudioSessionEnumerator **) = 0; 30 }; 31 32 /// Convert the configured 'wasapi/role' to an ERole. 33 static ERole WASAPIRoleFromSettings() { 34 QString role = g.s.qsWASAPIRole.toLower().trimmed(); 35 36 if (role == QLatin1String("console")) { 37 return eConsole; 38 } else if (role == QLatin1String("multimedia")) { 39 return eMultimedia; 40 } 41 42 return eCommunications; 43 } 44 45 class WASAPIInputRegistrar : public AudioInputRegistrar { 46 public: 47 WASAPIInputRegistrar(); 48 virtual AudioInput *create(); 49 virtual const QList<audioDevice> getDeviceChoices(); 50 virtual void setDeviceChoice(const QVariant &, Settings &); 51 virtual bool canEcho(const QString &) const; 52 virtual bool canExclusive() const; 53 }; 54 55 class WASAPIOutputRegistrar : public AudioOutputRegistrar { 56 public: 57 WASAPIOutputRegistrar(); 58 virtual AudioOutput *create(); 59 virtual const QList<audioDevice> getDeviceChoices(); 60 virtual void setDeviceChoice(const QVariant &, Settings &); 61 bool canMuteOthers() const; 62 virtual bool canExclusive() const; 63 }; 64 65 class WASAPIInit : public DeferInit { 66 WASAPIInputRegistrar *wirReg; 67 WASAPIOutputRegistrar *worReg; 68 public: 69 WASAPIInit() : wirReg(NULL), worReg(NULL) { } 70 void initialize(); 71 void destroy(); 72 }; 73 74 static WASAPIInit wasapiinit; 75 76 extern bool bIsWin7, bIsVistaSP1; 77 78 void WASAPIInit::initialize() { 79 wirReg = NULL; 80 worReg = NULL; 81 82 if (! bIsVistaSP1) { 83 qWarning("WASAPIInit: Requires Vista SP1"); 84 return; 85 } 86 87 HMODULE hLib = LoadLibrary(L"AVRT.DLL"); 88 if (hLib == NULL) { 89 qWarning("WASAPIInit: Failed to load avrt.dll"); 90 return; 91 } 92 FreeLibrary(hLib); 93 94 wirReg = new WASAPIInputRegistrar(); 95 worReg = new WASAPIOutputRegistrar(); 96 } 97 98 void WASAPIInit::destroy() { 99 delete wirReg; 100 delete worReg; 101 } 102 103 104 WASAPIInputRegistrar::WASAPIInputRegistrar() : AudioInputRegistrar(QLatin1String("WASAPI"), 10) { 105 } 106 107 108 /// Calls getMixFormat on given IAudioClient and checks whether it is compatible. 109 /// At the moment this means the format is either 32bit float or 16bit PCM. 110 /// 111 /// @param sourceName Name to prepend to log in case of error 112 /// @param deviceName Device name to refer to in case of error 113 /// @param audioClient IAudioClient to get and check mix format for 114 /// @param waveFormatEx WAVEFORMATEX structure to store getMixFormat result in 115 /// @param waveFormatExtensible If waveFormatEx is of type WAVEFORMATEXTENSIBLE receives a cast pointer. 116 /// @param sampleFormat Receives either SampleFloat or SampleShort as valid format 117 /// @return True if mix format is ok. False if incompatible or another error occured. 118 119 template <typename SAMPLEFORMAT> // Template on SampleFormat enum as AudioOutput and AudioInput each define their own 120 bool getAndCheckMixFormat(const char* sourceName, 121 const char* deviceName, 122 IAudioClient* audioClient, 123 WAVEFORMATEX **waveFormatEx, 124 WAVEFORMATEXTENSIBLE **waveFormatExtensible, 125 SAMPLEFORMAT *sampleFormat) { 126 127 *waveFormatEx = NULL; 128 *waveFormatExtensible = NULL; 129 130 HRESULT hr = audioClient->GetMixFormat(waveFormatEx); 131 if (FAILED(hr)) { 132 qWarning("%s: %s GetMixFormat failed: hr=0x%08lx", sourceName, deviceName, hr); 133 return false; 134 } 135 136 if ((*waveFormatEx)->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 137 (*waveFormatExtensible) = reinterpret_cast<WAVEFORMATEXTENSIBLE *>((*waveFormatEx)); 138 if ((*waveFormatExtensible)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) { 139 *sampleFormat = SAMPLEFORMAT::SampleFloat; 140 } else if ((*waveFormatExtensible)->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) { 141 *sampleFormat = SAMPLEFORMAT::SampleShort; 142 } else { 143 qWarning() << sourceName << ":" << deviceName << "Subformat is not IEEE Float or PCM but:" << (*waveFormatExtensible)->SubFormat; 144 return false; 145 } 146 } else { 147 if ((*waveFormatEx)->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { 148 *sampleFormat = SAMPLEFORMAT::SampleFloat; 149 } else if ((*waveFormatEx)->wFormatTag != WAVE_FORMAT_PCM) { 150 *sampleFormat = SAMPLEFORMAT::SampleShort; 151 } else { 152 qWarning() << sourceName << ":" << deviceName << "format tag is not IEEE Float or PCM but:" << (*waveFormatEx)->wFormatTag; 153 return false; 154 } 155 } 156 157 if (*sampleFormat == SAMPLEFORMAT::SampleFloat) { 158 if ((*waveFormatEx)->wBitsPerSample != (sizeof(float) * 8)) { 159 qWarning() << sourceName << ":" << deviceName << "unexpected number of bits per sample for IEEE Float:" << (*waveFormatEx)->wBitsPerSample; 160 return false; 161 } 162 } else if (*sampleFormat == SAMPLEFORMAT::SampleShort) { 163 if ((*waveFormatEx)->wBitsPerSample != (sizeof(short) * 8)) { 164 qWarning() << sourceName << ":" << deviceName << "unexpected number of bits per sample for PCM:" << (*waveFormatEx)->wBitsPerSample; 165 return false; 166 } 167 } else { 168 qFatal("%s: %s unexpected sample format %lu", sourceName, deviceName, static_cast<unsigned long>(*sampleFormat)); 169 return false; 170 } 171 172 return true; 173 } 174 175 176 AudioInput *WASAPIInputRegistrar::create() { 177 return new WASAPIInput(); 178 } 179 180 const QList<audioDevice> WASAPIInputRegistrar::getDeviceChoices() { 181 return WASAPISystem::mapToDevice(WASAPISystem::getInputDevices(), g.s.qsWASAPIInput); 182 } 183 184 void WASAPIInputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) { 185 s.qsWASAPIInput = choice.toString(); 186 } 187 188 bool WASAPIInputRegistrar::canEcho(const QString &outputsys) const { 189 return (outputsys == name); 190 } 191 192 bool WASAPIInputRegistrar::canExclusive() const { 193 return true; 194 } 195 196 WASAPIOutputRegistrar::WASAPIOutputRegistrar() : AudioOutputRegistrar(QLatin1String("WASAPI"), 10) { 197 } 198 199 AudioOutput *WASAPIOutputRegistrar::create() { 200 return new WASAPIOutput(); 201 } 202 203 const QList<audioDevice> WASAPIOutputRegistrar::getDeviceChoices() { 204 return WASAPISystem::mapToDevice(WASAPISystem::getOutputDevices(), g.s.qsWASAPIOutput); 205 } 206 207 void WASAPIOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) { 208 s.qsWASAPIOutput = choice.toString(); 209 } 210 211 bool WASAPIOutputRegistrar::canMuteOthers() const { 212 return true; 213 } 214 215 bool WASAPIOutputRegistrar::canExclusive() const { 216 return true; 217 } 218 219 const QHash<QString, QString> WASAPISystem::getInputDevices() { 220 return getDevices(eCapture); 221 } 222 223 const QHash<QString, QString> WASAPISystem::getOutputDevices() { 224 return getDevices(eRender); 225 } 226 227 const QHash<QString, QString> WASAPISystem::getDevices(EDataFlow dataflow) { 228 QHash<QString, QString> devices; 229 230 HRESULT hr; 231 232 IMMDeviceEnumerator *pEnumerator = NULL; 233 IMMDeviceCollection *pCollection = NULL; 234 235 hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void **>(&pEnumerator)); 236 237 if (! pEnumerator || FAILED(hr)) { 238 qWarning("WASAPI: Failed to instatiate enumerator: hr=0x%08lx", hr); 239 } else { 240 hr = pEnumerator->EnumAudioEndpoints(dataflow, DEVICE_STATE_ACTIVE, &pCollection); 241 if (! pCollection || FAILED(hr)) { 242 qWarning("WASAPI: Failed to enumerate: hr=0x%08lx", hr); 243 } else { 244 devices.insert(QString(), tr("Default Device")); 245 246 UINT ndev = 0; 247 pCollection->GetCount(&ndev); 248 for (unsigned int idx=0;idx<ndev;++idx) { 249 IMMDevice *pDevice = NULL; 250 IPropertyStore *pStore = NULL; 251 252 pCollection->Item(idx, &pDevice); 253 pDevice->OpenPropertyStore(STGM_READ, &pStore); 254 255 LPWSTR strid = NULL; 256 pDevice->GetId(&strid); 257 258 PROPVARIANT varName; 259 PropVariantInit(&varName); 260 261 pStore->GetValue(PKEY_Device_FriendlyName, &varName); 262 263 devices.insert(QString::fromWCharArray(strid), QString::fromWCharArray(varName.pwszVal)); 264 265 PropVariantClear(&varName); 266 CoTaskMemFree(strid); 267 268 pStore->Release(); 269 pDevice->Release(); 270 } 271 pCollection->Release(); 272 } 273 pEnumerator->Release(); 274 } 275 276 return devices; 277 } 278 279 const QList<audioDevice> WASAPISystem::mapToDevice(const QHash<QString, QString>& devs, const QString& match) { 280 QList<audioDevice> qlReturn; 281 282 QStringList qlDevices = devs.keys(); 283 qSort(qlDevices); 284 285 if (qlDevices.contains(match)) { 286 qlDevices.removeAll(match); 287 qlDevices.prepend(match); 288 } 289 290 foreach(const QString &dev, qlDevices) { 291 qlReturn << audioDevice(devs.value(dev), dev); 292 } 293 return qlReturn; 294 } 295 296 WASAPIInput::WASAPIInput() { 297 }; 298 299 WASAPIInput::~WASAPIInput() { 300 bRunning = false; 301 wait(); 302 } 303 304 static IMMDevice *openNamedOrDefaultDevice(const QString& name, EDataFlow dataFlow, ERole role) { 305 HRESULT hr; 306 IMMDeviceEnumerator *pEnumerator = NULL; 307 308 hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void **>(&pEnumerator)); 309 if (!pEnumerator || FAILED(hr)) { 310 qWarning("WASAPI: Failed to instantiate enumerator: hr=0x%08lx", hr); 311 return NULL; 312 } 313 314 IMMDevice *pDevice = NULL; 315 // Try to find a device pointer for |name|. 316 if (!name.isEmpty()) { 317 STACKVAR(wchar_t, devname, name.length() + 1); 318 int len = name.toWCharArray(devname); 319 devname[len] = 0; 320 hr = pEnumerator->GetDevice(devname, &pDevice); 321 if (FAILED(hr)) { 322 qWarning("WASAPI: Failed to open selected device %s %ls (df=%d, e=%d, hr=0x%08lx), falling back to default", qPrintable(name), devname, dataFlow, role, hr); 323 } else { 324 WASAPINotificationClient::get().enlistDeviceAsUsed(devname); 325 } 326 } 327 328 // Use the default device if |pDevice| is still NULL. 329 // We retrieve the actual device name for the currently selected default device and 330 // open the device by it's real name to work around triggering the automatic 331 // ducking behavior. 332 if (!pDevice) { 333 hr = pEnumerator->GetDefaultAudioEndpoint(dataFlow, role, &pDevice); 334 if (FAILED(hr)) { 335 qWarning("WASAPI: Failed to open device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); 336 goto cleanup; 337 } 338 wchar_t *devname = NULL; 339 hr = pDevice->GetId(&devname); 340 if (FAILED(hr)) { 341 qWarning("WASAPI: Failed to query device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); 342 goto cleanup; 343 } 344 pDevice->Release(); 345 hr = pEnumerator->GetDevice(devname, &pDevice); 346 if (FAILED(hr)) { 347 qWarning("WASAPI: Failed to reopen default device: df=%d, e=%d, hr=0x%08lx", dataFlow, role, hr); 348 goto cleanup; 349 } 350 WASAPINotificationClient::get().enlistDefaultDeviceAsUsed(devname); 351 CoTaskMemFree(devname); 352 } 353 354 cleanup: 355 if (pEnumerator) 356 pEnumerator->Release(); 357 358 return pDevice; 359 } 360 361 void WASAPIInput::run() { 362 HRESULT hr; 363 IMMDevice *pMicDevice = NULL; 364 IAudioClient *pMicAudioClient = NULL; 365 IAudioCaptureClient *pMicCaptureClient = NULL; 366 IMMDevice *pEchoDevice = NULL; 367 IAudioClient *pEchoAudioClient = NULL; 368 IAudioCaptureClient *pEchoCaptureClient = NULL; 369 WAVEFORMATEX *micpwfx = NULL, *echopwfx = NULL; 370 WAVEFORMATEXTENSIBLE *micpwfxe = NULL, *echopwfxe = NULL; 371 WAVEFORMATEXTENSIBLE wfe; 372 UINT32 bufferFrameCount; 373 UINT32 numFramesAvailable; 374 UINT32 micPacketLength = 0, echoPacketLength = 0; 375 UINT32 allocLength; 376 UINT64 devicePosition; 377 UINT64 qpcPosition; 378 HANDLE hEvent; 379 BYTE *pData; 380 DWORD flags; 381 DWORD dwTaskIndex = 0; 382 HANDLE hMmThread; 383 float *tbuff = NULL; 384 short *sbuff = NULL; 385 bool doecho = g.s.doEcho(); 386 REFERENCE_TIME def, min, latency, want; 387 bool exclusive = false; 388 389 CoInitialize(NULL); 390 391 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 392 393 hMmThread = AvSetMmThreadCharacteristics(L"Pro Audio", &dwTaskIndex); 394 if (hMmThread == NULL) { 395 qWarning("WASAPIInput: Failed to set Pro Audio thread priority"); 396 } 397 398 // Open mic device. 399 pMicDevice = openNamedOrDefaultDevice(g.s.qsWASAPIInput, eCapture, WASAPIRoleFromSettings()); 400 if (!pMicDevice) 401 goto cleanup; 402 403 // Open echo capture device. 404 if (doecho) { 405 pEchoDevice = openNamedOrDefaultDevice(g.s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); 406 if (!pEchoDevice) 407 doecho = false; 408 } 409 410 hr = pMicDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void **) &pMicAudioClient); 411 if (FAILED(hr)) { 412 qWarning("WASAPIInput: Activate Mic AudioClient failed: hr=0x%08lx", hr); 413 goto cleanup; 414 } 415 416 def = min = latency = 0; 417 418 pMicAudioClient->GetDevicePeriod(&def, &min); 419 420 want = qMax<REFERENCE_TIME>(min, 100000); 421 qWarning("WASAPIInput: Latencies %lld %lld => %lld", def, min, want); 422 423 if (g.s.bExclusiveInput && ! doecho) { 424 for (int channels = 1; channels<=2; ++channels) { 425 ZeroMemory(&wfe, sizeof(wfe)); 426 wfe.Format.cbSize = 0; 427 wfe.Format.wFormatTag = WAVE_FORMAT_PCM; 428 wfe.Format.nChannels = channels; 429 wfe.Format.nSamplesPerSec = 48000; 430 wfe.Format.wBitsPerSample = 16; 431 wfe.Format.nBlockAlign = wfe.Format.nChannels * wfe.Format.wBitsPerSample / 8; 432 wfe.Format.nAvgBytesPerSec = wfe.Format.nBlockAlign * wfe.Format.nSamplesPerSec; 433 434 micpwfxe = &wfe; 435 micpwfx = reinterpret_cast<WAVEFORMATEX *>(&wfe); 436 437 hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, micpwfx, NULL); 438 if (SUCCEEDED(hr)) { 439 eMicFormat = SampleShort; 440 exclusive = true; 441 qWarning("WASAPIInput: Successfully opened exclusive mode"); 442 break; 443 } 444 445 micpwfxe = NULL; 446 micpwfx = NULL; 447 } 448 } 449 450 if (! micpwfxe) { 451 if (g.s.bExclusiveInput) 452 qWarning("WASAPIInput: Failed to open exclusive mode."); 453 454 if (!getAndCheckMixFormat("WASAPIInput", "Mic", pMicAudioClient, 455 &micpwfx, &micpwfxe, &eMicFormat)) { 456 goto cleanup; 457 } 458 459 hr = pMicAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, micpwfx, NULL); 460 if (FAILED(hr)) { 461 qWarning("WASAPIInput: Mic Initialize failed: hr=0x%08lx", hr); 462 if (hr == E_ACCESSDENIED) { 463 g.mw->msgBox(tr("Access to the microphone was denied. Please check that your operating system's microphone settings allow Mumble to use the microphone.")); 464 } 465 goto cleanup; 466 } 467 } 468 469 qWarning() << "WASAPIInput: Mic Stream format" << eMicFormat; 470 471 pMicAudioClient->GetStreamLatency(&latency); 472 hr = pMicAudioClient->GetBufferSize(&bufferFrameCount); 473 qWarning("WASAPIInput: Stream Latency %lld (%d)", latency, bufferFrameCount); 474 475 hr = pMicAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pMicCaptureClient); 476 if (FAILED(hr)) { 477 qWarning("WASAPIInput: Mic GetService failed: hr=0x%08lx", hr); 478 goto cleanup; 479 } 480 481 pMicAudioClient->SetEventHandle(hEvent); 482 if (FAILED(hr)) { 483 qWarning("WASAPIInput: Failed to set mic event: hr=0x%08lx", hr); 484 goto cleanup; 485 } 486 487 hr = pMicAudioClient->Start(); 488 if (FAILED(hr)) { 489 qWarning("WASAPIInput: Failed to start mic: hr=0x%08lx", hr); 490 goto cleanup; 491 } 492 493 iMicChannels = micpwfx->nChannels; 494 iMicFreq = micpwfx->nSamplesPerSec; 495 496 if (doecho) { 497 hr = pEchoDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void **) &pEchoAudioClient); 498 if (FAILED(hr)) { 499 qWarning("WASAPIInput: Activate Echo AudioClient failed: hr=0x%08lx", hr); 500 goto cleanup; 501 } 502 503 if (!getAndCheckMixFormat("WASAPIInput", "Echo", pEchoAudioClient, 504 &echopwfx, &echopwfxe, &eEchoFormat)) { 505 goto cleanup; 506 } 507 508 hr = pEchoAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, echopwfx, NULL); 509 if (FAILED(hr)) { 510 qWarning("WASAPIInput: Echo Initialize failed: hr=0x%08lx", hr); 511 goto cleanup; 512 } 513 514 hr = pEchoAudioClient->GetBufferSize(&bufferFrameCount); 515 hr = pEchoAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pEchoCaptureClient); 516 if (FAILED(hr)) { 517 qWarning("WASAPIInput: Echo GetService failed: hr=0x%08lx", hr); 518 goto cleanup; 519 } 520 521 pEchoAudioClient->SetEventHandle(hEvent); 522 if (FAILED(hr)) { 523 qWarning("WASAPIInput: Failed to set echo event: hr=0x%08lx", hr); 524 goto cleanup; 525 } 526 527 hr = pEchoAudioClient->Start(); 528 if (FAILED(hr)) { 529 qWarning("WASAPIInput: Failed to start Echo: hr=0x%08lx", hr); 530 goto cleanup; 531 } 532 533 qWarning() << "WASAPIInput: Echo Stream format" << eEchoFormat; 534 535 iEchoChannels = echopwfx->nChannels; 536 iEchoFreq = echopwfx->nSamplesPerSec; 537 } 538 539 initializeMixer(); 540 541 allocLength = (iMicLength / 2) * micpwfx->nChannels; 542 543 if (exclusive) { 544 sbuff = new short[allocLength]; 545 while (bRunning && ! FAILED(hr)) { 546 hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, &qpcPosition); 547 if (hr != AUDCLNT_S_BUFFER_EMPTY) { 548 if (FAILED(hr)) 549 goto cleanup; 550 551 UINT32 nFrames = numFramesAvailable * micpwfx->nChannels; 552 if (nFrames > allocLength) { 553 delete [] sbuff; 554 allocLength = nFrames; 555 sbuff = new short[allocLength]; 556 } 557 558 memcpy(sbuff, pData, nFrames * sizeof(short)); 559 hr = pMicCaptureClient->ReleaseBuffer(numFramesAvailable); 560 if (FAILED(hr)) 561 goto cleanup; 562 addMic(sbuff, numFramesAvailable); 563 } 564 if (! FAILED(hr)) 565 WaitForSingleObject(hEvent, 100); 566 } 567 } else { 568 tbuff = new float[allocLength]; 569 while (bRunning && ! FAILED(hr)) { 570 hr = pMicCaptureClient->GetNextPacketSize(&micPacketLength); 571 if (! FAILED(hr) && iEchoChannels) 572 hr = pEchoCaptureClient->GetNextPacketSize(&echoPacketLength); 573 if (FAILED(hr)) { 574 qWarning("WASAPIInput: GetNextPacketSize failed: hr=0x%08lx", hr); 575 goto cleanup; 576 } 577 578 while ((micPacketLength > 0) || (echoPacketLength > 0)) { 579 if (echoPacketLength > 0) { 580 hr = pEchoCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, &qpcPosition); 581 if (FAILED(hr)) { 582 qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); 583 goto cleanup; 584 } 585 586 UINT32 nFrames = numFramesAvailable * echopwfx->nChannels; 587 if (nFrames > allocLength) { 588 delete [] tbuff; 589 allocLength = nFrames; 590 tbuff = new float[allocLength]; 591 } 592 memcpy(tbuff, pData, nFrames * sizeof(float)); 593 hr = pEchoCaptureClient->ReleaseBuffer(numFramesAvailable); 594 if (FAILED(hr)) { 595 qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); 596 goto cleanup; 597 } 598 addEcho(tbuff, numFramesAvailable); 599 } else if (micPacketLength > 0) { 600 hr = pMicCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, &qpcPosition); 601 if (FAILED(hr)) { 602 qWarning("WASAPIInput: GetBuffer failed: hr=0x%08lx", hr); 603 goto cleanup; 604 } 605 606 UINT32 nFrames = numFramesAvailable * micpwfx->nChannels; 607 if (nFrames > allocLength) { 608 delete [] tbuff; 609 allocLength = nFrames; 610 tbuff = new float[allocLength]; 611 } 612 memcpy(tbuff, pData, nFrames * sizeof(float)); 613 hr = pMicCaptureClient->ReleaseBuffer(numFramesAvailable); 614 if (FAILED(hr)) { 615 qWarning("WASAPIInput: ReleaseBuffer failed: hr=0x%08lx", hr); 616 goto cleanup; 617 } 618 addMic(tbuff, numFramesAvailable); 619 } 620 hr = pMicCaptureClient->GetNextPacketSize(&micPacketLength); 621 if (! FAILED(hr) && iEchoChannels) 622 hr = pEchoCaptureClient->GetNextPacketSize(&echoPacketLength); 623 } 624 if (! FAILED(hr)) 625 WaitForSingleObject(hEvent, 2000); 626 } 627 } 628 629 cleanup: 630 if (micpwfx && !exclusive) 631 CoTaskMemFree(micpwfx); 632 if (echopwfx) 633 CoTaskMemFree(echopwfx); 634 635 if (pMicAudioClient) { 636 pMicAudioClient->Stop(); 637 pMicAudioClient->Release(); 638 } 639 if (pMicCaptureClient) 640 pMicCaptureClient->Release(); 641 if (pMicDevice) 642 pMicDevice->Release(); 643 644 if (pEchoAudioClient) { 645 pEchoAudioClient->Stop(); 646 pEchoAudioClient->Release(); 647 } 648 if (pEchoCaptureClient) 649 pEchoCaptureClient->Release(); 650 if (pEchoDevice) 651 pEchoDevice->Release(); 652 653 if (hMmThread != NULL) 654 AvRevertMmThreadCharacteristics(hMmThread); 655 656 if (hEvent != NULL) 657 CloseHandle(hEvent); 658 659 delete [] tbuff; 660 delete [] sbuff; 661 } 662 663 WASAPIOutput::WASAPIOutput() { 664 } 665 666 WASAPIOutput::~WASAPIOutput() { 667 bRunning = false; 668 wait(); 669 } 670 671 void WASAPIOutput::setVolumes(IMMDevice *pDevice, bool talking) { 672 HRESULT hr; 673 674 if (! talking) { 675 QMap<ISimpleAudioVolume *, VolumePair>::const_iterator i; 676 for (i=qmVolumes.constBegin(); i != qmVolumes.constEnd(); ++i) { 677 float fVolume = 1.0f; 678 hr = i.key()->GetMasterVolume(&fVolume); 679 if (qFuzzyCompare(i.value().second, fVolume)) 680 hr = i.key()->SetMasterVolume(i.value().first, NULL); 681 i.key()->Release(); 682 } 683 qmVolumes.clear(); 684 return; 685 } 686 687 IAudioSessionManager2 *pAudioSessionManager = NULL; 688 int max = 0; 689 DWORD dwMumble = GetCurrentProcessId(); 690 691 qmVolumes.clear(); 692 if (qFuzzyCompare(g.s.fOtherVolume, 1.0f)) 693 return; 694 695 // FIXME: Try to keep the session object around when returning volume. 696 697 if (SUCCEEDED(hr = pDevice->Activate(bIsWin7 ? __uuidof(IAudioSessionManager2) : __uuidof(IAudioSessionManager), CLSCTX_ALL, NULL, (void **) &pAudioSessionManager))) { 698 IAudioSessionEnumerator *pEnumerator = NULL; 699 IAudioSessionQuery *pMysticQuery = NULL; 700 if (! bIsWin7) { 701 if (SUCCEEDED(hr = pAudioSessionManager->QueryInterface(IID_IAudioSessionQuery, (void **) &pMysticQuery))) { 702 hr = pMysticQuery->GetQueryInterface(&pEnumerator); 703 } 704 } else { 705 hr = pAudioSessionManager->GetSessionEnumerator(&pEnumerator); 706 } 707 708 QSet<QUuid> seen; 709 710 if (SUCCEEDED(hr)) { 711 if (SUCCEEDED(hr = pEnumerator->GetCount(&max))) { 712 for (int i=0;i<max;++i) { 713 IAudioSessionControl *pControl = NULL; 714 if (SUCCEEDED(hr = pEnumerator->GetSession(i, &pControl))) { 715 setVolumeForSessionControl(pControl, dwMumble, seen); 716 pControl->Release(); 717 } 718 } 719 } 720 pEnumerator->Release(); 721 } 722 if (pMysticQuery) 723 pMysticQuery->Release(); 724 pAudioSessionManager->Release(); 725 } 726 } 727 728 bool WASAPIOutput::setVolumeForSessionControl2(IAudioSessionControl2 *control2, const DWORD mumblePID, QSet<QUuid> &seen) { 729 HRESULT hr; 730 DWORD pid; 731 732 // Don't set the volume for our own control 733 if (FAILED(hr = control2->GetProcessId(&pid)) || (pid == mumblePID)) 734 return true; 735 736 // Don't work on expired audio sessions 737 AudioSessionState ass; 738 if (FAILED(hr = control2->GetState(&ass)) || (ass == AudioSessionStateExpired)) 739 return false; 740 741 // Don't act twice on the same session 742 GUID group; 743 if (FAILED(hr = control2->GetGroupingParam(&group))) 744 return false; 745 746 QUuid quuid(group); 747 if (seen.contains(quuid)) 748 return true; 749 750 seen.insert(quuid); 751 752 // Adjust volume 753 ISimpleAudioVolume *pVolume = NULL; 754 if (FAILED(hr = control2->QueryInterface(__uuidof(ISimpleAudioVolume), (void **) &pVolume))) 755 return false; 756 757 BOOL bMute = TRUE; 758 bool keep = false; 759 if (SUCCEEDED(hr = pVolume->GetMute(&bMute)) && ! bMute) { 760 float fVolume = 1.0f; 761 if (SUCCEEDED(hr = pVolume->GetMasterVolume(&fVolume)) && ! qFuzzyCompare(fVolume,0.0f)) { 762 float fSetVolume = fVolume * g.s.fOtherVolume; 763 if (SUCCEEDED(hr = pVolume->SetMasterVolume(fSetVolume, NULL))) { 764 hr = pVolume->GetMasterVolume(&fSetVolume); 765 qmVolumes.insert(pVolume, VolumePair(fVolume,fSetVolume)); 766 keep = true; 767 } 768 } 769 } 770 771 if (! keep) 772 pVolume->Release(); 773 774 return true; 775 } 776 777 bool WASAPIOutput::setVolumeForSessionControl(IAudioSessionControl *control, const DWORD mumblePID, QSet<QUuid> &seen) { 778 HRESULT hr; 779 IAudioSessionControl2 *pControl2 = NULL; 780 781 if (!SUCCEEDED(hr = control->QueryInterface(bIsWin7 ? __uuidof(IAudioSessionControl2) : IID_IVistaAudioSessionControl2, (void **) &pControl2))) 782 return false; 783 784 bool result = setVolumeForSessionControl2(pControl2, mumblePID, seen); 785 786 pControl2->Release(); 787 return result; 788 } 789 790 static void SetDuckingOptOut(IMMDevice *pDevice) { 791 if (!bIsWin7) 792 return; 793 794 HRESULT hr; 795 IAudioSessionManager2 *pSessionManager2 = NULL; 796 IAudioSessionControl *pSessionControl = NULL; 797 IAudioSessionControl2 *pSessionControl2 = NULL; 798 799 // Get session manager & control1+2 to disable ducking 800 hr = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&pSessionManager2)); 801 if (FAILED(hr)) { 802 qWarning("WASAPIOutput: Activate AudioSessionManager2 failed: hr=0x%08lx", hr); 803 goto cleanup; 804 } 805 806 hr = pSessionManager2->GetAudioSessionControl(NULL, 0, &pSessionControl); 807 if (FAILED(hr)) { 808 qWarning("WASAPIOutput: GetAudioSessionControl failed: hr=0x%08lx", hr); 809 goto cleanup; 810 } 811 812 hr = pSessionControl->QueryInterface(__uuidof(IAudioSessionControl2), reinterpret_cast<void**>(&pSessionControl2)); 813 if (FAILED(hr)) { 814 qWarning("WASAPIOutput: Querying SessionControl2 failed: hr=0x%08lx", hr); 815 goto cleanup; 816 } 817 818 hr = pSessionControl2->SetDuckingPreference(TRUE); 819 if (FAILED(hr)) { 820 qWarning("WASAPIOutput: Failed to set ducking preference: hr=0x%08lx", hr); 821 goto cleanup; 822 } 823 824 cleanup: 825 if (pSessionControl2) 826 pSessionControl2->Release(); 827 828 if (pSessionControl) 829 pSessionControl->Release(); 830 831 if (pSessionManager2) 832 pSessionManager2->Release(); 833 } 834 835 void WASAPIOutput::run() { 836 HRESULT hr; 837 IMMDevice *pDevice = NULL; 838 IAudioClient *pAudioClient = NULL; 839 IAudioRenderClient *pRenderClient = NULL; 840 WAVEFORMATEX *pwfx = NULL; 841 WAVEFORMATEXTENSIBLE *pwfxe = NULL; 842 UINT32 bufferFrameCount; 843 REFERENCE_TIME def, min, latency, want; 844 UINT32 numFramesAvailable; 845 HANDLE hEvent; 846 BYTE *pData; 847 DWORD dwTaskIndex = 0; 848 HANDLE hMmThread; 849 int ns = 0; 850 unsigned int chanmasks[32]; 851 QMap<DWORD, float> qmVolumes; 852 bool lastspoke = false; 853 REFERENCE_TIME bufferDuration = (g.s.iOutputDelay > 1) ? (g.s.iOutputDelay + 1) * 100000 : 0; 854 bool exclusive = false; 855 bool mixed = false; 856 857 CoInitialize(NULL); 858 859 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 860 861 hMmThread = AvSetMmThreadCharacteristics(L"Pro Audio", &dwTaskIndex); 862 if (hMmThread == NULL) { 863 qWarning("WASAPIOutput: Failed to set Pro Audio thread priority"); 864 } 865 866 // Open the output device. 867 pDevice = openNamedOrDefaultDevice(g.s.qsWASAPIOutput, eRender, WASAPIRoleFromSettings()); 868 if (!pDevice) 869 goto cleanup; 870 871 // Opt-out of the Windows 7 ducking behavior 872 SetDuckingOptOut(pDevice); 873 874 hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void **) &pAudioClient); 875 if (FAILED(hr)) { 876 qWarning("WASAPIOutput: Activate AudioClient failed: hr=0x%08lx", hr); 877 goto cleanup; 878 } 879 880 pAudioClient->GetDevicePeriod(&def, &min); 881 want = qMax<REFERENCE_TIME>(min, 100000); 882 qWarning("WASAPIOutput: Latencies %lld %lld => %lld", def, min, want); 883 884 if (g.s.bExclusiveOutput) { 885 hr = pAudioClient->GetMixFormat(&pwfx); 886 if (FAILED(hr)) { 887 qWarning("WASAPIOutput: GetMixFormat failed: hr=0x%08lx", hr); 888 goto cleanup; 889 } 890 891 if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 892 pwfxe = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(pwfx); 893 } 894 895 if (!g.s.bPositionalAudio) { 896 // Override mix format and request stereo 897 pwfx->nChannels = 2; 898 if (pwfxe) { 899 pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; 900 } 901 } 902 903 pwfx->cbSize = 0; 904 if (pwfxe) { 905 pwfxe->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; 906 } else { 907 pwfx->wFormatTag = WAVE_FORMAT_PCM; 908 } 909 pwfx->nSamplesPerSec = 48000; 910 pwfx->wBitsPerSample = 16; 911 pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; 912 pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; 913 914 hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, want, want, pwfx, NULL); 915 if (SUCCEEDED(hr)) { 916 eSampleFormat = SampleShort; 917 exclusive = true; 918 qWarning("WASAPIOutput: Successfully opened exclusive mode"); 919 } else { 920 CoTaskMemFree(pwfx); 921 922 pwfxe = NULL; 923 pwfx = NULL; 924 } 925 } 926 927 if (!pwfx) { 928 if (g.s.bExclusiveOutput) 929 qWarning("WASAPIOutput: Failed to open exclusive mode."); 930 931 if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, 932 &pwfx, &pwfxe, &eSampleFormat)) { 933 goto cleanup; 934 } 935 936 if (!g.s.bPositionalAudio) { 937 pwfx->nChannels = 2; 938 pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; 939 pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; 940 941 if (pwfxe) { 942 pwfxe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; 943 } 944 945 WAVEFORMATEX *closestFormat = NULL; 946 hr = pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, pwfx, &closestFormat); 947 if (hr == S_FALSE) { 948 qWarning("WASAPIOutput: Driver says no to 2 channel output. Closest format: %d channels @ %lu kHz", closestFormat->nChannels, static_cast<unsigned long>(closestFormat->nSamplesPerSec)); 949 CoTaskMemFree(pwfx); 950 951 // Fall back to whatever the device offers. 952 953 if (!getAndCheckMixFormat("WASAPIOutput", "Output", pAudioClient, 954 &pwfx, &pwfxe, &eSampleFormat)) { 955 CoTaskMemFree(closestFormat); 956 goto cleanup; 957 } 958 } else if (FAILED(hr)) { 959 qWarning("WASAPIOutput: IsFormatSupported failed: hr=0x%08lx", hr); 960 } 961 962 CoTaskMemFree(closestFormat); 963 } 964 965 hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, pwfx, NULL); 966 if (FAILED(hr)) { 967 qWarning("WASAPIOutput: Initialize failed: hr=0x%08lx", hr); 968 goto cleanup; 969 } 970 } 971 972 qWarning() << "WASAPIOutput: Output stream format" << eSampleFormat; 973 974 pAudioClient->GetStreamLatency(&latency); 975 pAudioClient->GetBufferSize(&bufferFrameCount); 976 qWarning("WASAPIOutput: Stream Latency %lld (%d)", latency, bufferFrameCount); 977 978 iMixerFreq = pwfx->nSamplesPerSec; 979 980 qWarning("WASAPIOutput: Periods %lldus %lldus (latency %lldus)", def / 10LL, min / 10LL, latency / 10LL); 981 qWarning("WASAPIOutput: Buffer is %dus (%d)", (bufferFrameCount * 1000000) / iMixerFreq, g.s.iOutputDelay); 982 983 hr = pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&pRenderClient); 984 if (FAILED(hr)) { 985 qWarning("WASAPIOutput: GetService failed: hr=0x%08lx", hr); 986 goto cleanup; 987 } 988 989 pAudioClient->SetEventHandle(hEvent); 990 if (FAILED(hr)) { 991 qWarning("WASAPIOutput: Failed to set event: hr=0x%08lx", hr); 992 goto cleanup; 993 } 994 995 hr = pAudioClient->Start(); 996 if (FAILED(hr)) { 997 qWarning("WASAPIOutput: Failed to start: hr=0x%08lx", hr); 998 goto cleanup; 999 } 1000 1001 if (pwfxe) { 1002 for (int i=0;i<32;i++) { 1003 if (pwfxe->dwChannelMask & (1 << i)) { 1004 chanmasks[ns++] = 1 << i; 1005 } 1006 } 1007 } else { 1008 qWarning("WASAPIOutput: No chanmask available. Assigning in order."); 1009 1010 for (int i = 0; i < pwfx->nChannels && i < 32; ++i) { 1011 chanmasks[ns++] = 1 << i; 1012 } 1013 } 1014 1015 if (ns != pwfx->nChannels) { 1016 qWarning("WASAPIOutput: Chanmask bits doesn't match number of channels."); 1017 } 1018 1019 iChannels = pwfx->nChannels; 1020 initializeMixer(chanmasks); 1021 1022 numFramesAvailable = 0; 1023 1024 while (bRunning && ! FAILED(hr)) { 1025 if (!exclusive) { 1026 // Attenuate stream volumes. 1027 if (lastspoke != (g.bAttenuateOthers || mixed)) { 1028 lastspoke = g.bAttenuateOthers || mixed; 1029 setVolumes(pDevice, lastspoke); 1030 } 1031 1032 hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); 1033 if (FAILED(hr)) { 1034 qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); 1035 goto cleanup; 1036 } 1037 } 1038 1039 UINT32 packetLength = bufferFrameCount - numFramesAvailable; 1040 1041 while (packetLength > 0) { 1042 hr = pRenderClient->GetBuffer(packetLength, &pData); 1043 if (FAILED(hr)) { 1044 qWarning("WASAPIOutput: GetBuffer failed: hr=0x%08lx", hr); 1045 goto cleanup; 1046 } 1047 1048 mixed = mix(reinterpret_cast<float *>(pData), packetLength); 1049 if (mixed) 1050 hr = pRenderClient->ReleaseBuffer(packetLength, 0); 1051 else 1052 hr = pRenderClient->ReleaseBuffer(packetLength, AUDCLNT_BUFFERFLAGS_SILENT); 1053 if (FAILED(hr)) { 1054 qWarning("WASAPIOutput: ReleaseBuffer failed: hr=0x%08lx", hr); 1055 goto cleanup; 1056 } 1057 1058 // Exclusive mode rendering ends here. 1059 if (exclusive) 1060 break; 1061 1062 if (!g.s.bAttenuateOthers && !g.bAttenuateOthers) { 1063 mixed = false; 1064 } 1065 1066 if (lastspoke != (g.bAttenuateOthers || mixed)) { 1067 lastspoke = g.bAttenuateOthers || mixed; 1068 setVolumes(pDevice, lastspoke); 1069 } 1070 1071 hr = pAudioClient->GetCurrentPadding(&numFramesAvailable); 1072 if (FAILED(hr)) { 1073 qWarning("WASAPIOutput: GetCurrentPadding failed: hr=0x%08lx", hr); 1074 goto cleanup; 1075 } 1076 1077 packetLength = bufferFrameCount - numFramesAvailable; 1078 } 1079 if (! FAILED(hr)) 1080 WaitForSingleObject(hEvent, exclusive ? 100 : 2000); 1081 } 1082 1083 cleanup: 1084 if (pwfx) 1085 CoTaskMemFree(pwfx); 1086 1087 if (pDevice) { 1088 setVolumes(pDevice, false); 1089 } 1090 1091 if (pAudioClient) { 1092 pAudioClient->Stop(); 1093 pAudioClient->Release(); 1094 } 1095 if (pRenderClient) 1096 pRenderClient->Release(); 1097 if (pDevice) 1098 pDevice->Release(); 1099 1100 if (hMmThread != NULL) 1101 AvRevertMmThreadCharacteristics(hMmThread); 1102 1103 if (hEvent != NULL) 1104 CloseHandle(hEvent); 1105 } 1106