diff options
author | dalecurtis <dalecurtis@chromium.org> | 2015-04-08 10:21:35 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-08 17:22:59 +0000 |
commit | d0d6ea4bbdd431f0b794ae5e8cf353e2e87593ab (patch) | |
tree | 9fb1b98c56cdfa592c94366f6c40398d437ffca9 /media/audio | |
parent | de96417fd6268522589f13f29a3c3cdb87d71bea (diff) | |
download | chromium_src-d0d6ea4bbdd431f0b794ae5e8cf353e2e87593ab.zip chromium_src-d0d6ea4bbdd431f0b794ae5e8cf353e2e87593ab.tar.gz chromium_src-d0d6ea4bbdd431f0b794ae5e8cf353e2e87593ab.tar.bz2 |
Disable low latency audio under remote desktop sessions.
CoreAudio seems to be the source of a lot of hangs when under a
remote desktop session and we've had past success with moving
these users over to WaveOut, so do so for all remote sessions.
This first queries the SM_REMOTESESSION system metric and then
tries to get the friendly name of the default audio device; if
it can't do that and a remote session is present or it can and
friendly name is "Remote Audio", CoreAudio functions are
disabled.
This change also adds a check to WASAPIAudioOutputStream::Open
to fail when a remote audio stream is requested; which will
trigger fallback in the AudioOutputResampler (if the user
resumes their session locally later they'll get WASAPI again
once streams have been closed for a few seconds).
BUG=422522
TEST=remote desktop session w/ remote audio gets WaveOut; works
with xfreerdp and Microsoft Remote Desktop client for OS; local
session gets WASAPI, connecting and starting a stream remotely
gets WaveOut.
Review URL: https://codereview.chromium.org/1060673002
Cr-Commit-Position: refs/heads/master@{#324246}
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/win/audio_low_latency_output_win.cc | 5 | ||||
-rw-r--r-- | media/audio/win/core_audio_util_win.cc | 161 | ||||
-rw-r--r-- | media/audio/win/core_audio_util_win.h | 3 |
3 files changed, 120 insertions, 49 deletions
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc index f7b31a3..53b8a8d 100644 --- a/media/audio/win/audio_low_latency_output_win.cc +++ b/media/audio/win/audio_low_latency_output_win.cc @@ -135,6 +135,11 @@ bool WASAPIAudioOutputStream::Open() { DCHECK(!audio_client_.get()); DCHECK(!audio_render_client_.get()); + // Don't allow WASAPI streams to be created for remote output devices, this + // frequently leads to hangs of the audio thread. http://crbug.com/422522. + if (CoreAudioUtil::IsRemoteOutputDevice(device_id_)) + return false; + // Will be set to true if we ended up opening the default communications // device. bool communications_device = false; diff --git a/media/audio/win/core_audio_util_win.cc b/media/audio/win/core_audio_util_win.cc index b56d6e6..b3bc62f 100644 --- a/media/audio/win/core_audio_util_win.cc +++ b/media/audio/win/core_audio_util_win.cc @@ -136,18 +136,6 @@ static bool LoadAudiosesDll() { return (LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) != NULL); } -static bool CanCreateDeviceEnumerator() { - ScopedComPtr<IMMDeviceEnumerator> device_enumerator; - HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), - NULL, CLSCTX_INPROC_SERVER); - - // If we hit CO_E_NOTINITIALIZED, CoInitialize has not been called and it - // must be called at least once for each thread that uses the COM library. - CHECK_NE(hr, CO_E_NOTINITIALIZED); - - return SUCCEEDED(hr); -} - static std::string GetDeviceID(IMMDevice* device) { ScopedCoMem<WCHAR> device_id_com; std::string device_id; @@ -156,7 +144,68 @@ static std::string GetDeviceID(IMMDevice* device) { return device_id; } -bool CoreAudioUtil::IsSupported() { +static HRESULT GetDeviceFriendlyNameInternal(IMMDevice* device, + std::string* friendly_name) { + // Retrieve user-friendly name of endpoint device. + // Example: "Microphone (Realtek High Definition Audio)". + ScopedComPtr<IPropertyStore> properties; + HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive()); + if (FAILED(hr)) + return hr; + + base::win::ScopedPropVariant friendly_name_pv; + hr = properties->GetValue(PKEY_Device_FriendlyName, + friendly_name_pv.Receive()); + if (FAILED(hr)) + return hr; + + if (friendly_name_pv.get().vt == VT_LPWSTR && + friendly_name_pv.get().pwszVal) { + base::WideToUTF8(friendly_name_pv.get().pwszVal, + wcslen(friendly_name_pv.get().pwszVal), friendly_name); + } + + return hr; +} + +static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal() { + ScopedComPtr<IMMDeviceEnumerator> device_enumerator; + HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER); + if (hr == CO_E_NOTINITIALIZED) { + LOG(ERROR) << "CoCreateInstance fails with CO_E_NOTINITIALIZED"; + // We have seen crashes which indicates that this method can in fact + // fail with CO_E_NOTINITIALIZED in combination with certain 3rd party + // modules. Calling CoInitializeEx is an attempt to resolve the reported + // issues. See http://crbug.com/378465 for details. + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (SUCCEEDED(hr)) { + hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER); + } + } + return device_enumerator; +} + +static bool IsRemoteSession() { + return !!GetSystemMetrics(SM_REMOTESESSION); +} + +static bool IsRemoteDeviceInternal(IMMDevice* device) { + DCHECK(IsRemoteSession()); + + std::string device_name; + HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name); + + // This method should only be called if IsRemoteSession() is true, so assume + // we have a remote audio device if we can't tell. + if (FAILED(hr)) + return true; + + return device_name == "Remote Audio"; +} + +static bool IsSupportedInternal() { // It is possible to force usage of WaveXxx APIs by using a command line flag. const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); if (cmd_line->HasSwitch(switches::kForceWaveAudio)) { @@ -176,19 +225,47 @@ bool CoreAudioUtil::IsSupported() { // the Audioses DLL since it depends on Mmdevapi.dll. // See http://crbug.com/166397 why this extra step is required to guarantee // Core Audio support. - static bool g_audioses_dll_available = LoadAudiosesDll(); - if (!g_audioses_dll_available) + if (!LoadAudiosesDll()) return false; // Being able to load the Audioses.dll does not seem to be sufficient for // all devices to guarantee Core Audio support. To be 100%, we also verify // that it is possible to a create the IMMDeviceEnumerator interface. If this // works as well we should be home free. - static bool g_can_create_device_enumerator = CanCreateDeviceEnumerator(); - LOG_IF(ERROR, !g_can_create_device_enumerator) - << "Failed to create Core Audio device enumerator on thread with ID " - << GetCurrentThreadId(); - return g_can_create_device_enumerator; + ScopedComPtr<IMMDeviceEnumerator> device_enumerator = + CreateDeviceEnumeratorInternal(); + if (!device_enumerator) { + LOG(ERROR) + << "Failed to create Core Audio device enumerator on thread with ID " + << GetCurrentThreadId(); + return false; + } + + // Don't use CoreAudio when a remote desktop session with remote audio is + // present; several users report only WaveAudio working for them and crash + // reports show hangs when calling into the OS for CoreAudio API calls. See + // http://crbug.com/422522 and http://crbug.com/180591. + // + // Note: There's another check in WASAPIAudioOutputStream::Open() for the case + // where a remote session is created after Chrome has been started. Graceful + // fallback to WaveOut will occur in this case via AudioOutputResampler. + if (!IsRemoteSession()) + return true; + + ScopedComPtr<IMMDevice> device; + HRESULT hr = device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, + device.Receive()); + + // Assume remote audio playback if we can't tell. + if (FAILED(hr)) + return false; + + return !IsRemoteDeviceInternal(device.get()); +} + +bool CoreAudioUtil::IsSupported() { + static bool g_is_supported = IsSupportedInternal(); + return g_is_supported; } base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) { @@ -233,22 +310,9 @@ int CoreAudioUtil::NumberOfActiveDevices(EDataFlow data_flow) { ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() { DCHECK(IsSupported()); - ScopedComPtr<IMMDeviceEnumerator> device_enumerator; - HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), - NULL, CLSCTX_INPROC_SERVER); - if (hr == CO_E_NOTINITIALIZED) { - LOG(ERROR) << "CoCreateInstance fails with CO_E_NOTINITIALIZED"; - // We have seen crashes which indicates that this method can in fact - // fail with CO_E_NOTINITIALIZED in combination with certain 3rd party - // modules. Calling CoInitializeEx is an attempt to resolve the reported - // issues. See http://crbug.com/378465 for details. - hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (SUCCEEDED(hr)) { - hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), - NULL, CLSCTX_INPROC_SERVER); - } - } - CHECK(SUCCEEDED(hr)); + ScopedComPtr<IMMDeviceEnumerator> device_enumerator = + CreateDeviceEnumeratorInternal(); + CHECK(device_enumerator); return device_enumerator; } @@ -323,21 +387,9 @@ HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) { if (device_name.unique_id.empty()) return E_FAIL; - // Retrieve user-friendly name of endpoint device. - // Example: "Microphone (Realtek High Definition Audio)". - ScopedComPtr<IPropertyStore> properties; - HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive()); - if (FAILED(hr)) - return hr; - base::win::ScopedPropVariant friendly_name; - hr = properties->GetValue(PKEY_Device_FriendlyName, friendly_name.Receive()); + HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name.device_name); if (FAILED(hr)) return hr; - if (friendly_name.get().vt == VT_LPWSTR && friendly_name.get().pwszVal) { - base::WideToUTF8(friendly_name.get().pwszVal, - wcslen(friendly_name.get().pwszVal), - &device_name.device_name); - } *name = device_name; DVLOG(2) << "friendly name: " << device_name.device_name; @@ -854,4 +906,15 @@ bool CoreAudioUtil::FillRenderEndpointBufferWithSilence( AUDCLNT_BUFFERFLAGS_SILENT)); } +bool CoreAudioUtil::IsRemoteOutputDevice(const std::string& device_id) { + DCHECK(IsSupported()); + if (!IsRemoteSession()) + return false; + ScopedComPtr<IMMDevice> device(device_id.empty() + ? CreateDefaultDevice(eRender, eConsole) + : CreateDevice(device_id)); + // Assume remote audio if we can't tell. + return device ? IsRemoteDeviceInternal(device.get()) : true; +} + } // namespace media diff --git a/media/audio/win/core_audio_util_win.h b/media/audio/win/core_audio_util_win.h index 4e93531..75e90ce 100644 --- a/media/audio/win/core_audio_util_win.h +++ b/media/audio/win/core_audio_util_win.h @@ -229,6 +229,9 @@ class MEDIA_EXPORT CoreAudioUtil { static bool FillRenderEndpointBufferWithSilence( IAudioClient* client, IAudioRenderClient* render_client); + // Returns true if the given output device is a remote audio endpoint. + static bool IsRemoteOutputDevice(const std::string& device_id); + private: CoreAudioUtil() {} ~CoreAudioUtil() {} |