WASAPIStream.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // Copyright 2018 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "AudioCommon/WASAPIStream.h"
  4. #ifdef _WIN32
  5. // clang-format off
  6. #include <Audioclient.h>
  7. #include <mmdeviceapi.h>
  8. #include <devpkey.h>
  9. #include <functiondiscoverykeys_devpkey.h>
  10. #include <wil/resource.h>
  11. // clang-format on
  12. #include <thread>
  13. #include "Common/Assert.h"
  14. #include "Common/HRWrap.h"
  15. #include "Common/Logging/Log.h"
  16. #include "Common/StringUtil.h"
  17. #include "Common/Thread.h"
  18. #include "Core/Config/MainSettings.h"
  19. #include "VideoCommon/OnScreenDisplay.h"
  20. using Microsoft::WRL::ComPtr;
  21. WASAPIStream::WASAPIStream()
  22. {
  23. if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
  24. m_coinitialize.activate();
  25. m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  26. m_format.Format.nChannels = 2;
  27. m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate();
  28. m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * 4;
  29. m_format.Format.nBlockAlign = 4;
  30. m_format.Format.wBitsPerSample = 16;
  31. m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
  32. m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample;
  33. m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
  34. m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  35. }
  36. WASAPIStream::~WASAPIStream()
  37. {
  38. m_running.store(false, std::memory_order_relaxed);
  39. if (m_thread.joinable())
  40. m_thread.join();
  41. }
  42. bool WASAPIStream::IsValid()
  43. {
  44. return true;
  45. }
  46. static bool HandleWinAPI(std::string_view message, HRESULT result)
  47. {
  48. if (FAILED(result))
  49. {
  50. std::string error;
  51. switch (result)
  52. {
  53. case AUDCLNT_E_DEVICE_IN_USE:
  54. error = "Audio endpoint already in use!";
  55. break;
  56. default:
  57. error = Common::GetHResultMessage(result);
  58. break;
  59. }
  60. ERROR_LOG_FMT(AUDIO, "WASAPI: {}: {} ({:08x})", message, error, result);
  61. }
  62. return SUCCEEDED(result);
  63. }
  64. static void ForEachNamedDevice(const std::function<bool(ComPtr<IMMDevice>, std::string)>& callback)
  65. {
  66. HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  67. // RPC_E_CHANGED_MODE means that thread has COM already initialized with a different threading
  68. // model. We don't necessarily need multithreaded model here, so don't treat this as an error
  69. if (result != RPC_E_CHANGED_MODE && !HandleWinAPI("Failed to call CoInitialize", result))
  70. return;
  71. wil::unique_couninitialize_call cleanup;
  72. if (FAILED(result))
  73. cleanup.release(); // CoUninitialize must be matched with each successful CoInitialize call, so
  74. // don't call it if initialize fails
  75. ComPtr<IMMDeviceEnumerator> enumerator;
  76. result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
  77. IID_PPV_ARGS(enumerator.GetAddressOf()));
  78. if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result))
  79. return;
  80. ComPtr<IMMDeviceCollection> devices;
  81. result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, devices.GetAddressOf());
  82. if (!HandleWinAPI("Failed to get available devices", result))
  83. return;
  84. UINT count;
  85. devices->GetCount(&count);
  86. for (u32 i = 0; i < count; i++)
  87. {
  88. ComPtr<IMMDevice> device;
  89. devices->Item(i, device.GetAddressOf());
  90. if (!HandleWinAPI("Failed to get device " + std::to_string(i), result))
  91. continue;
  92. ComPtr<IPropertyStore> device_properties;
  93. result = device->OpenPropertyStore(STGM_READ, device_properties.GetAddressOf());
  94. if (!HandleWinAPI("Failed to initialize IPropertyStore", result))
  95. continue;
  96. wil::unique_prop_variant device_name;
  97. device_properties->GetValue(PKEY_Device_FriendlyName, device_name.addressof());
  98. if (!callback(std::move(device), TStrToUTF8(device_name.pwszVal)))
  99. break;
  100. }
  101. }
  102. std::vector<std::string> WASAPIStream::GetAvailableDevices()
  103. {
  104. std::vector<std::string> device_names;
  105. ForEachNamedDevice([&device_names](ComPtr<IMMDevice>, std::string n) {
  106. device_names.push_back(std::move(n));
  107. return true;
  108. });
  109. return device_names;
  110. }
  111. ComPtr<IMMDevice> WASAPIStream::GetDeviceByName(std::string_view name)
  112. {
  113. ComPtr<IMMDevice> device;
  114. ForEachNamedDevice([&device, &name](ComPtr<IMMDevice> d, std::string n) {
  115. if (n == name)
  116. {
  117. device = std::move(d);
  118. return false;
  119. }
  120. return true;
  121. });
  122. return device;
  123. }
  124. bool WASAPIStream::Init()
  125. {
  126. ASSERT(m_enumerator == nullptr);
  127. HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
  128. IID_PPV_ARGS(m_enumerator.GetAddressOf()));
  129. if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result))
  130. return false;
  131. return true;
  132. }
  133. bool WASAPIStream::SetRunning(bool running)
  134. {
  135. if (running)
  136. {
  137. ComPtr<IMMDevice> device;
  138. HRESULT result;
  139. if (Config::Get(Config::MAIN_WASAPI_DEVICE) == "default")
  140. {
  141. result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf());
  142. }
  143. else
  144. {
  145. result = S_OK;
  146. device = GetDeviceByName(Config::Get(Config::MAIN_WASAPI_DEVICE));
  147. if (!device)
  148. {
  149. ERROR_LOG_FMT(AUDIO, "Can't find device '{}', falling back to default",
  150. Config::Get(Config::MAIN_WASAPI_DEVICE));
  151. result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf());
  152. }
  153. }
  154. if (!HandleWinAPI("Failed to obtain default endpoint", result))
  155. return false;
  156. // Show a friendly name in the log
  157. ComPtr<IPropertyStore> device_properties;
  158. result = device->OpenPropertyStore(STGM_READ, device_properties.GetAddressOf());
  159. if (!HandleWinAPI("Failed to initialize IPropertyStore", result))
  160. return false;
  161. wil::unique_prop_variant device_name;
  162. device_properties->GetValue(PKEY_Device_FriendlyName, device_name.addressof());
  163. INFO_LOG_FMT(AUDIO, "Using audio endpoint '{}'", TStrToUTF8(device_name.pwszVal));
  164. ComPtr<IAudioClient> audio_client;
  165. // Get IAudioDevice
  166. result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr,
  167. reinterpret_cast<LPVOID*>(audio_client.GetAddressOf()));
  168. if (!HandleWinAPI("Failed to activate IAudioClient", result))
  169. return false;
  170. REFERENCE_TIME device_period = 0;
  171. result = audio_client->GetDevicePeriod(nullptr, &device_period);
  172. device_period += Config::Get(Config::MAIN_AUDIO_LATENCY) * (10000 / m_format.Format.nChannels);
  173. INFO_LOG_FMT(AUDIO, "Audio period set to {}", device_period);
  174. if (!HandleWinAPI("Failed to obtain device period", result))
  175. return false;
  176. result = audio_client->Initialize(
  177. AUDCLNT_SHAREMODE_EXCLUSIVE,
  178. AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period,
  179. device_period, reinterpret_cast<WAVEFORMATEX*>(&m_format), nullptr);
  180. if (result == AUDCLNT_E_UNSUPPORTED_FORMAT)
  181. {
  182. OSD::AddMessage("Your current audio device doesn't support 16-bit 48000 hz PCM audio. WASAPI "
  183. "exclusive mode won't work.",
  184. 6000U);
  185. return false;
  186. }
  187. if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
  188. {
  189. result = audio_client->GetBufferSize(&m_frames_in_buffer);
  190. if (!HandleWinAPI("Failed to get aligned buffer size", result))
  191. return false;
  192. // Get IAudioDevice
  193. result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr,
  194. reinterpret_cast<LPVOID*>(audio_client.ReleaseAndGetAddressOf()));
  195. if (!HandleWinAPI("Failed to reactivate IAudioClient", result))
  196. return false;
  197. device_period =
  198. static_cast<REFERENCE_TIME>(
  199. 10000.0 * 1000 * m_frames_in_buffer / m_format.Format.nSamplesPerSec + 0.5) +
  200. Config::Get(Config::MAIN_AUDIO_LATENCY) * 10000;
  201. result = audio_client->Initialize(
  202. AUDCLNT_SHAREMODE_EXCLUSIVE,
  203. AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period,
  204. device_period, reinterpret_cast<WAVEFORMATEX*>(&m_format), nullptr);
  205. }
  206. if (!HandleWinAPI("Failed to initialize IAudioClient", result))
  207. return false;
  208. result = audio_client->GetBufferSize(&m_frames_in_buffer);
  209. if (!HandleWinAPI("Failed to get buffer size from IAudioClient", result))
  210. return false;
  211. ComPtr<IAudioRenderClient> audio_renderer;
  212. result = audio_client->GetService(IID_PPV_ARGS(audio_renderer.GetAddressOf()));
  213. if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result))
  214. return false;
  215. wil::unique_event_nothrow need_data_event;
  216. need_data_event.create();
  217. audio_client->SetEventHandle(need_data_event.get());
  218. result = audio_client->Start();
  219. if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result))
  220. return false;
  221. INFO_LOG_FMT(AUDIO, "WASAPI: Successfully initialized!");
  222. // "Commit" audio client and audio renderer now
  223. m_audio_client = std::move(audio_client);
  224. m_audio_renderer = std::move(audio_renderer);
  225. m_need_data_event = std::move(need_data_event);
  226. m_running.store(true, std::memory_order_relaxed);
  227. m_thread = std::thread(&WASAPIStream::SoundLoop, this);
  228. }
  229. else
  230. {
  231. m_running.store(false, std::memory_order_relaxed);
  232. if (m_thread.joinable())
  233. m_thread.join();
  234. m_need_data_event.reset();
  235. m_audio_renderer.Reset();
  236. m_audio_client.Reset();
  237. }
  238. return true;
  239. }
  240. void WASAPIStream::SoundLoop()
  241. {
  242. Common::SetCurrentThreadName("WASAPI Handler");
  243. BYTE* data;
  244. m_audio_renderer->GetBuffer(m_frames_in_buffer, &data);
  245. m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, AUDCLNT_BUFFERFLAGS_SILENT);
  246. while (m_running.load(std::memory_order_relaxed))
  247. {
  248. WaitForSingleObject(m_need_data_event.get(), 1000);
  249. m_audio_renderer->GetBuffer(m_frames_in_buffer, &data);
  250. s16* audio_data = reinterpret_cast<s16*>(data);
  251. GetMixer()->Mix(audio_data, m_frames_in_buffer);
  252. const bool is_muted =
  253. Config::Get(Config::MAIN_AUDIO_MUTED) || Config::Get(Config::MAIN_AUDIO_VOLUME) == 0;
  254. const bool need_volume_adjustment = Config::Get(Config::MAIN_AUDIO_VOLUME) != 100 && !is_muted;
  255. if (need_volume_adjustment)
  256. {
  257. const float volume = Config::Get(Config::MAIN_AUDIO_VOLUME) / 100.0f;
  258. for (u32 i = 0; i < m_frames_in_buffer * 2; i++)
  259. *audio_data++ *= volume;
  260. }
  261. m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, is_muted ? AUDCLNT_BUFFERFLAGS_SILENT : 0);
  262. }
  263. }
  264. #endif // _WIN32