Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/audio/win/core_audio_util_win.h" | 5 #include "media/audio/win/core_audio_util_win.h" |
| 6 | 6 |
| 7 #include <audioclient.h> | 7 #include <audioclient.h> |
| 8 #include <devicetopology.h> | 8 #include <devicetopology.h> |
| 9 #include <functiondiscoverykeys_devpkey.h> | 9 #include <functiondiscoverykeys_devpkey.h> |
| 10 | 10 |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 129 | 129 |
| 130 static bool LoadAudiosesDll() { | 130 static bool LoadAudiosesDll() { |
| 131 static const wchar_t* const kAudiosesDLL = | 131 static const wchar_t* const kAudiosesDLL = |
| 132 L"%WINDIR%\\system32\\audioses.dll"; | 132 L"%WINDIR%\\system32\\audioses.dll"; |
| 133 | 133 |
| 134 wchar_t path[MAX_PATH] = {0}; | 134 wchar_t path[MAX_PATH] = {0}; |
| 135 ExpandEnvironmentStringsW(kAudiosesDLL, path, arraysize(path)); | 135 ExpandEnvironmentStringsW(kAudiosesDLL, path, arraysize(path)); |
| 136 return (LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) != NULL); | 136 return (LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) != NULL); |
| 137 } | 137 } |
| 138 | 138 |
| 139 static bool CanCreateDeviceEnumerator() { | |
| 140 ScopedComPtr<IMMDeviceEnumerator> device_enumerator; | |
| 141 HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), | |
| 142 NULL, CLSCTX_INPROC_SERVER); | |
| 143 | |
| 144 // If we hit CO_E_NOTINITIALIZED, CoInitialize has not been called and it | |
| 145 // must be called at least once for each thread that uses the COM library. | |
| 146 CHECK_NE(hr, CO_E_NOTINITIALIZED); | |
| 147 | |
| 148 return SUCCEEDED(hr); | |
| 149 } | |
| 150 | |
| 151 static std::string GetDeviceID(IMMDevice* device) { | 139 static std::string GetDeviceID(IMMDevice* device) { |
| 152 ScopedCoMem<WCHAR> device_id_com; | 140 ScopedCoMem<WCHAR> device_id_com; |
| 153 std::string device_id; | 141 std::string device_id; |
| 154 if (SUCCEEDED(device->GetId(&device_id_com))) | 142 if (SUCCEEDED(device->GetId(&device_id_com))) |
| 155 base::WideToUTF8(device_id_com, wcslen(device_id_com), &device_id); | 143 base::WideToUTF8(device_id_com, wcslen(device_id_com), &device_id); |
| 156 return device_id; | 144 return device_id; |
| 157 } | 145 } |
| 158 | 146 |
| 159 bool CoreAudioUtil::IsSupported() { | 147 static HRESULT GetDeviceFriendlyNameInternal(IMMDevice* device, |
| 148 std::string* friendly_name) { | |
| 149 // Retrieve user-friendly name of endpoint device. | |
| 150 // Example: "Microphone (Realtek High Definition Audio)". | |
| 151 ScopedComPtr<IPropertyStore> properties; | |
| 152 HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive()); | |
| 153 if (FAILED(hr)) | |
| 154 return hr; | |
| 155 | |
| 156 base::win::ScopedPropVariant friendly_name_pv; | |
| 157 hr = properties->GetValue(PKEY_Device_FriendlyName, | |
| 158 friendly_name_pv.Receive()); | |
| 159 if (FAILED(hr)) | |
| 160 return hr; | |
| 161 | |
| 162 if (friendly_name_pv.get().vt == VT_LPWSTR && | |
| 163 friendly_name_pv.get().pwszVal) { | |
| 164 base::WideToUTF8(friendly_name_pv.get().pwszVal, | |
| 165 wcslen(friendly_name_pv.get().pwszVal), friendly_name); | |
| 166 } | |
| 167 | |
| 168 return hr; | |
| 169 } | |
| 170 | |
| 171 static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal() { | |
| 172 ScopedComPtr<IMMDeviceEnumerator> device_enumerator; | |
| 173 HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), | |
| 174 NULL, CLSCTX_INPROC_SERVER); | |
| 175 if (hr == CO_E_NOTINITIALIZED) { | |
| 176 LOG(ERROR) << "CoCreateInstance fails with CO_E_NOTINITIALIZED"; | |
| 177 // We have seen crashes which indicates that this method can in fact | |
| 178 // fail with CO_E_NOTINITIALIZED in combination with certain 3rd party | |
| 179 // modules. Calling CoInitializeEx is an attempt to resolve the reported | |
| 180 // issues. See http://crbug.com/378465 for details. | |
| 181 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); | |
|
henrika (OOO until Aug 14)
2015/04/07 07:41:08
I hope Tommi can comment on this part.
DaleCurtis
2015/04/07 22:24:54
Can you elaborate on your question? I think it was
henrika (OOO until Aug 14)
2015/04/08 06:57:36
I think you are correct but maybe Tommi has a seco
| |
| 182 if (SUCCEEDED(hr)) { | |
| 183 hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), | |
| 184 NULL, CLSCTX_INPROC_SERVER); | |
| 185 } | |
| 186 } | |
| 187 return device_enumerator; | |
| 188 } | |
| 189 | |
| 190 static bool IsRemoteSession() { | |
| 191 return !!GetSystemMetrics(SM_REMOTESESSION); | |
| 192 } | |
| 193 | |
| 194 static bool IsRemoteDeviceInternal(IMMDevice* device) { | |
| 195 DCHECK(IsRemoteSession()); | |
| 196 | |
| 197 std::string device_name; | |
| 198 HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name); | |
| 199 | |
| 200 // This method should only be called if IsRemoteSession() is true, so assume | |
| 201 // we have a remote audio device if we can't tell. | |
| 202 if (FAILED(hr)) | |
| 203 return true; | |
| 204 | |
| 205 return device_name == "Remote Audio"; | |
|
henrika (OOO until Aug 14)
2015/04/07 07:41:08
Any reference to how we know that this is always t
DaleCurtis
2015/04/07 22:24:54
Couldn't find any reference. I have no idea if "Re
henrika (OOO until Aug 14)
2015/04/08 06:57:36
Acknowledged.
| |
| 206 } | |
| 207 | |
| 208 static bool IsSupportedInternal() { | |
| 160 // It is possible to force usage of WaveXxx APIs by using a command line flag. | 209 // It is possible to force usage of WaveXxx APIs by using a command line flag. |
| 161 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | 210 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| 162 if (cmd_line->HasSwitch(switches::kForceWaveAudio)) { | 211 if (cmd_line->HasSwitch(switches::kForceWaveAudio)) { |
| 163 DVLOG(1) << "Forcing usage of Windows WaveXxx APIs"; | 212 DVLOG(1) << "Forcing usage of Windows WaveXxx APIs"; |
| 164 return false; | 213 return false; |
| 165 } | 214 } |
| 166 | 215 |
| 167 // Microsoft does not plan to make the Core Audio APIs available for use | 216 // Microsoft does not plan to make the Core Audio APIs available for use |
| 168 // with earlier versions of Windows, including Microsoft Windows Server 2003, | 217 // with earlier versions of Windows, including Microsoft Windows Server 2003, |
| 169 // Windows XP, Windows Millennium Edition, Windows 2000, and Windows 98. | 218 // Windows XP, Windows Millennium Edition, Windows 2000, and Windows 98. |
| 170 if (base::win::GetVersion() < base::win::VERSION_VISTA) | 219 if (base::win::GetVersion() < base::win::VERSION_VISTA) |
| 171 return false; | 220 return false; |
| 172 | 221 |
| 173 // The audio core APIs are implemented in the Mmdevapi.dll and Audioses.dll | 222 // The audio core APIs are implemented in the Mmdevapi.dll and Audioses.dll |
| 174 // system components. | 223 // system components. |
| 175 // Dependency Walker shows that it is enough to verify possibility to load | 224 // Dependency Walker shows that it is enough to verify possibility to load |
| 176 // the Audioses DLL since it depends on Mmdevapi.dll. | 225 // the Audioses DLL since it depends on Mmdevapi.dll. |
| 177 // See http://crbug.com/166397 why this extra step is required to guarantee | 226 // See http://crbug.com/166397 why this extra step is required to guarantee |
| 178 // Core Audio support. | 227 // Core Audio support. |
| 179 static bool g_audioses_dll_available = LoadAudiosesDll(); | 228 if (!LoadAudiosesDll()) |
| 180 if (!g_audioses_dll_available) | |
| 181 return false; | 229 return false; |
| 182 | 230 |
| 183 // Being able to load the Audioses.dll does not seem to be sufficient for | 231 // Being able to load the Audioses.dll does not seem to be sufficient for |
| 184 // all devices to guarantee Core Audio support. To be 100%, we also verify | 232 // all devices to guarantee Core Audio support. To be 100%, we also verify |
| 185 // that it is possible to a create the IMMDeviceEnumerator interface. If this | 233 // that it is possible to a create the IMMDeviceEnumerator interface. If this |
| 186 // works as well we should be home free. | 234 // works as well we should be home free. |
| 187 static bool g_can_create_device_enumerator = CanCreateDeviceEnumerator(); | 235 ScopedComPtr<IMMDeviceEnumerator> device_enumerator = |
| 188 LOG_IF(ERROR, !g_can_create_device_enumerator) | 236 CreateDeviceEnumeratorInternal(); |
| 189 << "Failed to create Core Audio device enumerator on thread with ID " | 237 if (!device_enumerator) { |
| 190 << GetCurrentThreadId(); | 238 LOG(ERROR) |
| 191 return g_can_create_device_enumerator; | 239 << "Failed to create Core Audio device enumerator on thread with ID " |
| 240 << GetCurrentThreadId(); | |
| 241 return false; | |
| 242 } | |
| 243 | |
| 244 // Don't use CoreAudio when a remote desktop session with remote audio is | |
| 245 // present; several users report only WaveAudio working for them and crash | |
| 246 // reports show hangs when calling into the OS for CoreAudio API calls. See | |
| 247 // http://crbug.com/422522 and http://crbug.com/180591. | |
| 248 // | |
| 249 // Note: There's another check in WASAPIAudioOutputStream::Open() for the case | |
| 250 // where a remote session is created after Chrome has been started. Graceful | |
| 251 // fallback to WaveOut will occur in this case via AudioOutputResampler. | |
| 252 if (!IsRemoteSession()) | |
| 253 return true; | |
| 254 | |
| 255 ScopedComPtr<IMMDevice> device; | |
| 256 HRESULT hr = device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, | |
| 257 device.Receive()); | |
| 258 | |
| 259 // Assume remote audio playback if we can't tell. | |
| 260 if (FAILED(hr)) | |
|
henrika (OOO until Aug 14)
2015/04/07 07:41:08
Do we need this check? Is it not included in IsRem
DaleCurtis
2015/04/07 22:24:54
Seems GetDefaultAudioEndpoint could still fail? th
henrika (OOO until Aug 14)
2015/04/08 06:57:36
Acknowledged.
| |
| 261 return false; | |
| 262 | |
| 263 return !IsRemoteDeviceInternal(device.get()); | |
| 264 } | |
| 265 | |
| 266 bool CoreAudioUtil::IsSupported() { | |
| 267 static bool g_is_supported = IsSupportedInternal(); | |
| 268 return g_is_supported; | |
| 192 } | 269 } |
| 193 | 270 |
| 194 base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) { | 271 base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) { |
| 195 // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond. | 272 // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond. |
| 196 return base::TimeDelta::FromMicroseconds(0.1 * time + 0.5); | 273 return base::TimeDelta::FromMicroseconds(0.1 * time + 0.5); |
| 197 } | 274 } |
| 198 | 275 |
| 199 AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() { | 276 AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() { |
| 200 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | 277 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| 201 if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) | 278 if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 226 // Retrieve the number of active audio devices for the specified direction | 303 // Retrieve the number of active audio devices for the specified direction |
| 227 UINT number_of_active_devices = 0; | 304 UINT number_of_active_devices = 0; |
| 228 collection->GetCount(&number_of_active_devices); | 305 collection->GetCount(&number_of_active_devices); |
| 229 DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ") | 306 DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ") |
| 230 << "number of devices: " << number_of_active_devices; | 307 << "number of devices: " << number_of_active_devices; |
| 231 return static_cast<int>(number_of_active_devices); | 308 return static_cast<int>(number_of_active_devices); |
| 232 } | 309 } |
| 233 | 310 |
| 234 ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() { | 311 ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() { |
| 235 DCHECK(IsSupported()); | 312 DCHECK(IsSupported()); |
| 236 ScopedComPtr<IMMDeviceEnumerator> device_enumerator; | 313 ScopedComPtr<IMMDeviceEnumerator> device_enumerator = |
| 237 HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), | 314 CreateDeviceEnumeratorInternal(); |
| 238 NULL, CLSCTX_INPROC_SERVER); | 315 CHECK(device_enumerator); |
| 239 if (hr == CO_E_NOTINITIALIZED) { | |
| 240 LOG(ERROR) << "CoCreateInstance fails with CO_E_NOTINITIALIZED"; | |
| 241 // We have seen crashes which indicates that this method can in fact | |
| 242 // fail with CO_E_NOTINITIALIZED in combination with certain 3rd party | |
| 243 // modules. Calling CoInitializeEx is an attempt to resolve the reported | |
| 244 // issues. See http://crbug.com/378465 for details. | |
| 245 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); | |
| 246 if (SUCCEEDED(hr)) { | |
| 247 hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), | |
| 248 NULL, CLSCTX_INPROC_SERVER); | |
| 249 } | |
| 250 } | |
| 251 CHECK(SUCCEEDED(hr)); | |
| 252 return device_enumerator; | 316 return device_enumerator; |
| 253 } | 317 } |
| 254 | 318 |
| 255 ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow, | 319 ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow, |
| 256 ERole role) { | 320 ERole role) { |
| 257 DCHECK(IsSupported()); | 321 DCHECK(IsSupported()); |
| 258 ScopedComPtr<IMMDevice> endpoint_device; | 322 ScopedComPtr<IMMDevice> endpoint_device; |
| 259 | 323 |
| 260 // Create the IMMDeviceEnumerator interface. | 324 // Create the IMMDeviceEnumerator interface. |
| 261 ScopedComPtr<IMMDeviceEnumerator> device_enumerator = | 325 ScopedComPtr<IMMDeviceEnumerator> device_enumerator = |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 316 HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) { | 380 HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) { |
| 317 DCHECK(IsSupported()); | 381 DCHECK(IsSupported()); |
| 318 | 382 |
| 319 // Retrieve unique name of endpoint device. | 383 // Retrieve unique name of endpoint device. |
| 320 // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}". | 384 // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}". |
| 321 AudioDeviceName device_name; | 385 AudioDeviceName device_name; |
| 322 device_name.unique_id = GetDeviceID(device); | 386 device_name.unique_id = GetDeviceID(device); |
| 323 if (device_name.unique_id.empty()) | 387 if (device_name.unique_id.empty()) |
| 324 return E_FAIL; | 388 return E_FAIL; |
| 325 | 389 |
| 326 // Retrieve user-friendly name of endpoint device. | 390 HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name.device_name); |
| 327 // Example: "Microphone (Realtek High Definition Audio)". | |
| 328 ScopedComPtr<IPropertyStore> properties; | |
| 329 HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive()); | |
| 330 if (FAILED(hr)) | 391 if (FAILED(hr)) |
| 331 return hr; | 392 return hr; |
| 332 base::win::ScopedPropVariant friendly_name; | |
| 333 hr = properties->GetValue(PKEY_Device_FriendlyName, friendly_name.Receive()); | |
| 334 if (FAILED(hr)) | |
| 335 return hr; | |
| 336 if (friendly_name.get().vt == VT_LPWSTR && friendly_name.get().pwszVal) { | |
| 337 base::WideToUTF8(friendly_name.get().pwszVal, | |
| 338 wcslen(friendly_name.get().pwszVal), | |
| 339 &device_name.device_name); | |
| 340 } | |
| 341 | 393 |
| 342 *name = device_name; | 394 *name = device_name; |
| 343 DVLOG(2) << "friendly name: " << device_name.device_name; | 395 DVLOG(2) << "friendly name: " << device_name.device_name; |
| 344 DVLOG(2) << "unique id : " << device_name.unique_id; | 396 DVLOG(2) << "unique id : " << device_name.unique_id; |
| 345 return hr; | 397 return hr; |
| 346 } | 398 } |
| 347 | 399 |
| 348 std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device, | 400 std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device, |
| 349 IMMDeviceEnumerator* enumerator) { | 401 IMMDeviceEnumerator* enumerator) { |
| 350 DCHECK(IsSupported()); | 402 DCHECK(IsSupported()); |
| (...skipping 496 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 847 if (FAILED(render_client->GetBuffer(num_frames_to_fill, &data))) | 899 if (FAILED(render_client->GetBuffer(num_frames_to_fill, &data))) |
| 848 return false; | 900 return false; |
| 849 | 901 |
| 850 // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to | 902 // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to |
| 851 // explicitly write silence data to the rendering buffer. | 903 // explicitly write silence data to the rendering buffer. |
| 852 DVLOG(2) << "filling up " << num_frames_to_fill << " frames with silence"; | 904 DVLOG(2) << "filling up " << num_frames_to_fill << " frames with silence"; |
| 853 return SUCCEEDED(render_client->ReleaseBuffer(num_frames_to_fill, | 905 return SUCCEEDED(render_client->ReleaseBuffer(num_frames_to_fill, |
| 854 AUDCLNT_BUFFERFLAGS_SILENT)); | 906 AUDCLNT_BUFFERFLAGS_SILENT)); |
| 855 } | 907 } |
| 856 | 908 |
| 909 bool CoreAudioUtil::IsRemoteOutputDevice(const std::string& device_id) { | |
| 910 DCHECK(IsSupported()); | |
| 911 if (!IsRemoteSession()) | |
| 912 return false; | |
| 913 ScopedComPtr<IMMDevice> device(device_id.empty() | |
| 914 ? CreateDefaultDevice(eRender, eConsole) | |
| 915 : CreateDevice(device_id)); | |
| 916 // Assume remote audio if we can't tell. | |
| 917 return device ? IsRemoteDeviceInternal(device.get()) : true; | |
| 918 } | |
| 919 | |
| 857 } // namespace media | 920 } // namespace media |
| OLD | NEW |