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 |