diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-24 07:18:19 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-24 07:18:19 +0000 |
commit | d5577f7266e34ed412fd3b9b9aed2450094b5995 (patch) | |
tree | 806c82daafc17fc3700ea9800217dbbbf4fa9ea3 /media | |
parent | a3c8aa0b48eefddfe8718febb58bd7a685339e08 (diff) | |
download | chromium_src-d5577f7266e34ed412fd3b9b9aed2450094b5995.zip chromium_src-d5577f7266e34ed412fd3b9b9aed2450094b5995.tar.gz chromium_src-d5577f7266e34ed412fd3b9b9aed2450094b5995.tar.bz2 |
Adds support for capture device enumeration on Windows.
Example output (two devices found on Windows 7 machine):
device_name: Microphone (6- SB Arena Headset)
unique_id: {0.0.1.00000000}.{24515814-26fd-4382-b4bc-0a8d847ed853}
device_name: Microphone (Realtek High Definition Audio)
unique_id: {0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}
BUG=None
TEST=audio_input_device_unittest.cc
Review URL: http://codereview.chromium.org/8606006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111500 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_input_device_unittest.cc | 139 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.cc | 29 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.h | 19 | ||||
-rw-r--r-- | media/audio/win/device_enumeration_win.cc | 125 | ||||
-rw-r--r-- | media/audio/win/device_enumeration_win.h | 26 | ||||
-rw-r--r-- | media/media.gyp | 2 |
6 files changed, 319 insertions, 21 deletions
diff --git a/media/audio/audio_input_device_unittest.cc b/media/audio/audio_input_device_unittest.cc index e709936..2d50f4b 100644 --- a/media/audio/audio_input_device_unittest.cc +++ b/media/audio/audio_input_device_unittest.cc @@ -2,32 +2,145 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/environment.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_com_initializer.h" +#include "media/audio/audio_manager.h" #include "media/audio/audio_manager_base.h" +#if defined(OS_WIN) +#include "media/audio/win/audio_manager_win.h" +#endif #include "testing/gtest/include/gtest/gtest.h" -namespace media { +using base::win::ScopedCOMInitializer; +using media::AudioDeviceNames; -// Test that devices can be enumerated. -TEST(AudioInputDeviceTest, EnumerateDevices) { - AudioDeviceNames device_names; - ASSERT_TRUE(AudioManager::GetAudioManager() != NULL); - AudioManager::GetAudioManager()->GetAudioInputDeviceNames( - &device_names); +// Test fixture which allows us to override the default enumeration API on +// Windows. +class AudioInputDeviceTest + : public ::testing::Test { + protected: +#if defined(OS_WIN) + // Store current device-enumeration type to ensure that it can be restored + // after last test in this test suite. + static void SetUpTestCase() { + enumeration_type_ = static_cast<AudioManagerWin*>( + AudioManager::GetAudioManager())->enumeration_type(); + } + + // Restore pre-test state of device-enumeration type. + static void TearDownTestCase() { + static_cast<AudioManagerWin*>( + AudioManager::GetAudioManager())->SetEnumerationType(enumeration_type_); + } + + bool SetMMDeviceEnumeration(AudioManager* audio_manager) { + AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager); + // Windows Wave is used as default if Windows XP was detected => + // return false since MMDevice is not supported on XP. + if (amw->enumeration_type() == AudioManagerWin::kWaveEnumeration) + return false; + + amw->SetEnumerationType(AudioManagerWin::kMMDeviceEnumeration); + return true; + } + + void SetWaveEnumeration(AudioManager* audio_manager) { + AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager); + amw->SetEnumerationType(AudioManagerWin::kWaveEnumeration); + } + + // Stores the pre-test state representing the device-enumeration type. + static AudioManagerWin::EnumerationType enumeration_type_; +#endif +}; + +#if defined(OS_WIN) +AudioManagerWin::EnumerationType AudioInputDeviceTest::enumeration_type_ = + AudioManagerWin::kUninitializedEnumeration; +#endif + +// Convenience method which ensures that we are not running on the build +// bots which lacks audio device support. +static bool CanRunAudioTests() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + if (env->HasVar("CHROME_HEADLESS")) + return false; + return true; +} + +// Helper method which verifies that the device list starts with a valid +// default record followed by non-default device names. +static void CheckDeviceNames(const AudioDeviceNames& device_names) { if (!device_names.empty()) { AudioDeviceNames::const_iterator it = device_names.begin(); - // The first device in the list is the prepended default device. + + // The first device in the list should always be the default device. EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceName), - it->device_name); + it->device_name); EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceId), it->unique_id); ++it; - // Other devices should have non-empty name and id. + // Other devices should have non-empty name and id and should not contain + // default name or id. while (it != device_names.end()) { - EXPECT_NE("", it->device_name); - EXPECT_NE("", it->unique_id); + EXPECT_FALSE(it->device_name.empty()); + EXPECT_FALSE(it->unique_id.empty()); + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceName), + it->device_name); + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceId), + it->unique_id); ++it; } } } -} // namespace media +// Test that devices can be enumerated. +TEST_F(AudioInputDeviceTest, EnumerateDevices) { + if (!CanRunAudioTests()) + return; + // The MMDevice API requires a correct COM environment. + ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); + AudioDeviceNames device_names; + AudioManager* audio_man = AudioManager::GetAudioManager(); + EXPECT_TRUE(audio_man != NULL); + audio_man->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +// Run additional tests for Windows since enumeration can be done using +// two different APIs. MMDevice is default for Vista and higher and Wave +// is default for XP and lower. +#if defined(OS_WIN) + +// Override default enumeration API and force usage of Windows MMDevice. +// This test will only run on Windows Vista and higher. +TEST_F(AudioInputDeviceTest, EnumerateDevicesWinMMDevice) { + if (!CanRunAudioTests()) + return; + // The MMDevice API requires a correct COM environment. + ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); + AudioDeviceNames device_names; + AudioManager* audio_man = AudioManager::GetAudioManager(); + if (!SetMMDeviceEnumeration(audio_man)) { + // Usage of MMDevice will fail on XP and lower. + return; + } + audio_man->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +// Override default enumeration API and force usage of Windows Wave. +// This test will run on Windows XP, Windows Vista and Windows 7. +TEST_F(AudioInputDeviceTest, EnumerateDevicesWinWave) { + if (!CanRunAudioTests()) + return; + AudioDeviceNames device_names; + AudioManager* audio_man = AudioManager::GetAudioManager(); + EXPECT_TRUE(audio_man != NULL); + SetWaveEnumeration(audio_man); + audio_man->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +#endif diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc index c999f61..92f59d8 100644 --- a/media/audio/win/audio_manager_win.cc +++ b/media/audio/win/audio_manager_win.cc @@ -24,6 +24,7 @@ #include "media/audio/win/audio_low_latency_input_win.h" #include "media/audio/win/audio_low_latency_output_win.h" #include "media/audio/win/audio_manager_win.h" +#include "media/audio/win/device_enumeration_win.h" #include "media/audio/win/wavein_input_win.h" #include "media/audio/win/waveout_output_win.h" #include "media/base/limits.h" @@ -99,6 +100,13 @@ static string16 GetDeviceAndDriverInfo(HDEVINFO device_info, AudioManagerWin::AudioManagerWin() : num_output_streams_(0) { + if (base::win::GetVersion() <= base::win::VERSION_XP) { + // Use the Wave API for device enumeration if XP or lower. + enumeration_type_ = kWaveEnumeration; + } else { + // Use the MMDevice API for device enumeration if Vista or higher. + enumeration_type_ = kMMDeviceEnumeration; + } } AudioManagerWin::~AudioManagerWin() { @@ -280,19 +288,26 @@ void AudioManagerWin::ShowAudioInputSettings() { void AudioManagerWin::GetAudioInputDeviceNames( media::AudioDeviceNames* device_names) { - // TODO(xians): query a full list of valid devices. - if (HasAudioInputDevices()) { - // Add the default device to the list. - // We use index 0 to make up the unique_id to identify the - // default devices. + DCHECK(enumeration_type() != kUninitializedEnumeration); + // Enumerate all active audio-endpoint capture devices. + if (enumeration_type() == kWaveEnumeration) { + // Utilize the Wave API for Windows XP. + GetInputDeviceNamesWinXP(device_names); + } else { + // Utilize the MMDevice API (part of Core Audio) for Vista and higher. + GetInputDeviceNamesWin(device_names); + } + + // Always add default device parameters as first element. + if (!device_names->empty()) { media::AudioDeviceName name; name.device_name = AudioManagerBase::kDefaultDeviceName; name.unique_id = AudioManagerBase::kDefaultDeviceId; - device_names->push_back(name); + device_names->push_front(name); } } -// static +/// static AudioManager* AudioManager::CreateAudioManager() { return new AudioManagerWin(); } diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h index f1d73d8..6c519ee 100644 --- a/media/audio/win/audio_manager_win.h +++ b/media/audio/win/audio_manager_win.h @@ -6,9 +6,11 @@ #define MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_ #include <windows.h> +#include <string> #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" #include "media/audio/audio_manager_base.h" class PCMWaveOutAudioOutputStream; @@ -16,7 +18,7 @@ class PCMWaveOutAudioOutputStream; // Windows implementation of the AudioManager singleton. This class is internal // to the audio output and only internal users can call methods not exposed by // the AudioManager class. -class AudioManagerWin : public AudioManagerBase { +class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase { public: AudioManagerWin(); // Implementation of AudioManager. @@ -42,8 +44,23 @@ class AudioManagerWin : public AudioManagerBase { void ReleaseInputStream(AudioInputStream* stream); private: + enum EnumerationType { + kUninitializedEnumeration = 0, + kMMDeviceEnumeration, + kWaveEnumeration, + }; + virtual ~AudioManagerWin(); + // Allow unit test to modify the utilized enumeration API. + friend class AudioInputDeviceTest; + + EnumerationType enumeration_type_; + EnumerationType enumeration_type() { return enumeration_type_; } + void SetEnumerationType(EnumerationType type) { + enumeration_type_ = type; + } + // Number of currently open output streams. int num_output_streams_; diff --git a/media/audio/win/device_enumeration_win.cc b/media/audio/win/device_enumeration_win.cc new file mode 100644 index 0000000..0087a89 --- /dev/null +++ b/media/audio/win/device_enumeration_win.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <MMDeviceAPI.h> +#include <mmsystem.h> +#include <Functiondiscoverykeys_devpkey.h> // MMDeviceAPI.h must come first + +#include "media/audio/win/audio_manager_win.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_comptr.h" + +using media::AudioDeviceNames; +using base::win::ScopedComPtr; +using base::win::ScopedCoMem; + +bool GetInputDeviceNamesWin(AudioDeviceNames* device_names) { + // It is assumed that this method is called from a COM thread, i.e., + // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. + ScopedComPtr<IMMDeviceEnumerator> enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), + enumerator.ReceiveVoid()); + if (FAILED(hr)) { + LOG(WARNING) << "Failed to create IMMDeviceEnumerator: " << std::hex << hr; + return false; + } + + // Generate a collection of active audio capture endpoint devices. + // This method will succeed even if all devices are disabled. + ScopedComPtr<IMMDeviceCollection> collection; + hr = enumerator->EnumAudioEndpoints(eCapture, + DEVICE_STATE_ACTIVE, + collection.Receive()); + if (FAILED(hr)) + return false; + + // Retrieve the number of active capture devices. + UINT number_of_active_devices = 0; + collection->GetCount(&number_of_active_devices); + if (number_of_active_devices == 0) + return true; + + media::AudioDeviceName device; + + // Loop over all active capture devices and add friendly name and + // unique ID to the |device_names| list. + for (UINT i = 0; i < number_of_active_devices; ++i) { + // Retrieve unique name of endpoint device. + // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}". + ScopedComPtr<IMMDevice> audio_device; + ScopedCoMem<WCHAR> endpoint_device_id; + hr = collection->Item(i, audio_device.Receive()); + if (FAILED(hr)) + continue; + audio_device->GetId(&endpoint_device_id); + + // Store the unique name. + device.unique_id = WideToUTF8(static_cast<WCHAR*>(endpoint_device_id)); + + // Retrieve user-friendly name of endpoint device. + // Example: "Microphone (Realtek High Definition Audio)". + ScopedComPtr<IPropertyStore> properties; + hr = audio_device->OpenPropertyStore(STGM_READ, properties.Receive()); + if (SUCCEEDED(hr)) { + PROPVARIANT friendly_name; + PropVariantInit(&friendly_name); + hr = properties->GetValue(PKEY_Device_FriendlyName, &friendly_name); + + // Store the user-friendly name. + if (SUCCEEDED(hr) && + friendly_name.vt == VT_LPWSTR && friendly_name.pwszVal) { + device.device_name = WideToUTF8(friendly_name.pwszVal); + } + PropVariantClear(&friendly_name); + } + + // Add combination of user-friendly and unique name to the output list. + device_names->push_back(device); + } + + return true; +} + +bool GetInputDeviceNamesWinXP(AudioDeviceNames* device_names) { + // Retrieve the number of active waveform input devices. + UINT number_of_active_devices = waveInGetNumDevs(); + if (number_of_active_devices == 0) + return true; + + media::AudioDeviceName device; + WAVEINCAPS capabilities; + MMRESULT err = MMSYSERR_NOERROR; + + // Loop over all active capture devices and add friendly name and + // unique ID to the |device_names| list. Note that, for Wave on XP, + // the "unique" name will simply be a copy of the friendly name since + // there is no safe method to retrieve a unique device name on XP. + for (UINT i = 0; i < number_of_active_devices; ++i) { + // Retrieve the capabilities of the specified waveform-audio input device. + err = waveInGetDevCaps(i, &capabilities, sizeof(capabilities)); + if (err != MMSYSERR_NOERROR) + continue; + + if (capabilities.szPname != NULL) { + // Store the user-friendly name. Max length is MAXPNAMELEN(=32) + // characters and the name cane be truncated on XP. + // Example: "Microphone (Realtek High Defini". + device.device_name = WideToUTF8(capabilities.szPname); + + // Store the "unique" name (we use same as friendly name on Windows XP). + device.unique_id = WideToUTF8(capabilities.szPname); + + // Add combination of user-friendly and unique name to the output list. + device_names->push_back(device); + } + } + + return true; +} diff --git a/media/audio/win/device_enumeration_win.h b/media/audio/win/device_enumeration_win.h new file mode 100644 index 0000000..a797eec --- /dev/null +++ b/media/audio/win/device_enumeration_win.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_ +#define MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_ + +#include "media/audio/audio_device_name.h" + +// Returns a list of audio input device structures (name and unique device ID) +// using the MMDevice API which is supported on Windows Vista and higher. +// Example record in the output list: +// - device_name: "Microphone (Realtek High Definition Audio)". +// - unique_id: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}" +// This method must be called from a COM thread using MTA. +bool GetInputDeviceNamesWin(media::AudioDeviceNames* device_names); + +// Returns a list of audio input device structures (name and unique device ID) +// using the WaveIn API which is supported on Windows XP and higher. +// Example record in the output list: +// - device_name: "Microphone (Realtek High Defini". +// - unique_id: "Microphone (Realtek High Defini" (same as friendly name). +bool GetInputDeviceNamesWinXP(media::AudioDeviceNames* device_names); + +#endif // MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_ + diff --git a/media/media.gyp b/media/media.gyp index b47be2c..44a7bcc 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -87,6 +87,8 @@ 'audio/win/audio_manager_win.h', 'audio/win/avrt_wrapper_win.cc', 'audio/win/avrt_wrapper_win.h', + 'audio/win/device_enumeration_win.cc', + 'audio/win/device_enumeration_win.h', 'audio/win/wavein_input_win.cc', 'audio/win/wavein_input_win.h', 'audio/win/waveout_output_win.cc', |