Improves device enumeration in ADM2 for Windows.
Summary of changes/improvements and fixes:
Changes container for list of devices from std::vector to std:deque to
allow fast insertion and deletion at both its beginning and its end. This
approach makes it easier to first build a list of all available devices
and then check the size of the list. If size > 0 => two more devices are
added at the front (Default and Default Communication). The old solution
contained a risk of adding invalid Default and Default Communication
devices in cases where not physical device could be found.
Adds usage of |device_index_| in CoreAudioBase to ensure that the selected
device is unique. The previous version used only an ID but that ID is not
unique when e.g. only one device exists since it can have up to three
different roles.
Improves logging and comments.
No-Try: True
Tbr: thaloun@chromium.org
Bug: webrtc:11107
Change-Id: I9a09f7716ed8d8858dcc6a5354b038fc06496166
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160050
Commit-Queue: Henrik Andreassson <henrika@webrtc.org>
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29874}
diff --git a/modules/audio_device/audio_device_name.h b/modules/audio_device/audio_device_name.h
index 06a03fd..baabd78 100644
--- a/modules/audio_device/audio_device_name.h
+++ b/modules/audio_device/audio_device_name.h
@@ -11,8 +11,8 @@
#ifndef MODULES_AUDIO_DEVICE_AUDIO_DEVICE_NAME_H_
#define MODULES_AUDIO_DEVICE_AUDIO_DEVICE_NAME_H_
+#include <deque>
#include <string>
-#include <vector>
namespace webrtc {
@@ -41,7 +41,7 @@
std::string unique_id; // Unique identifier for the device.
};
-typedef std::vector<AudioDeviceName> AudioDeviceNames;
+typedef std::deque<AudioDeviceName> AudioDeviceNames;
} // namespace webrtc
diff --git a/modules/audio_device/win/core_audio_base_win.cc b/modules/audio_device/win/core_audio_base_win.cc
index 52da075..bf3bf1a 100644
--- a/modules/audio_device/win/core_audio_base_win.cc
+++ b/modules/audio_device/win/core_audio_base_win.cc
@@ -56,6 +56,34 @@
}
}
+const char* RoleToString(const ERole role) {
+ switch (role) {
+ case eConsole:
+ return "Console";
+ case eMultimedia:
+ return "Multimedia";
+ case eCommunications:
+ return "Communications";
+ default:
+ return "Unsupported";
+ }
+}
+
+std::string IndexToString(int index) {
+ std::string ss = std::to_string(index);
+ switch (index) {
+ case kDefault:
+ ss += " (Default)";
+ break;
+ case kDefaultCommunications:
+ ss += " (Communications)";
+ break;
+ default:
+ break;
+ }
+ return ss;
+}
+
const char* SessionStateToString(AudioSessionState state) {
switch (state) {
case AudioSessionStateActive:
@@ -204,15 +232,23 @@
return index == kDefaultCommunications;
}
-bool CoreAudioBase::IsDefaultDevice(const std::string& device_id) const {
+bool CoreAudioBase::IsDefaultDeviceId(const std::string& device_id) const {
+ // Returns true if |device_id| corresponds to the id of the default
+ // device. Note that, if only one device is available (or if the user has not
+ // explicitly set a default device), |device_id| will also math
+ // IsDefaultCommunicationsDeviceId().
return (IsInput() &&
(device_id == core_audio_utility::GetDefaultInputDeviceID())) ||
(IsOutput() &&
(device_id == core_audio_utility::GetDefaultOutputDeviceID()));
}
-bool CoreAudioBase::IsDefaultCommunicationsDevice(
+bool CoreAudioBase::IsDefaultCommunicationsDeviceId(
const std::string& device_id) const {
+ // Returns true if |device_id| corresponds to the id of the default
+ // communication device. Note that, if only one device is available (or if
+ // the user has not explicitly set a communication device), |device_id| will
+ // also math IsDefaultDeviceId().
return (IsInput() &&
(device_id ==
core_audio_utility::GetCommunicationsInputDeviceID())) ||
@@ -256,13 +292,14 @@
int CoreAudioBase::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
- << "]";
+ << "]: index=" << IndexToString(index);
if (initialized_) {
return -1;
}
std::string device_id = GetDeviceID(index);
- RTC_DLOG(INFO) << "index=" << index << " => device_id: " << device_id;
+ RTC_DLOG(INFO) << "index=" << IndexToString(index)
+ << " => device_id: " << device_id;
device_index_ = index;
device_id_ = device_id;
@@ -273,7 +310,7 @@
std::string* name,
std::string* guid) const {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
- << "]";
+ << "]: index=" << IndexToString(index);
if (index > NumberOfEnumeratedDevices() - 1) {
RTC_LOG(LS_ERROR) << "Invalid device index";
return -1;
@@ -282,6 +319,8 @@
AudioDeviceNames device_names;
bool ok = IsInput() ? core_audio_utility::GetInputDeviceNames(&device_names)
: core_audio_utility::GetOutputDeviceNames(&device_names);
+ // Validate the index one extra time in-case the size of the generated list
+ // did not match NumberOfEnumeratedDevices().
if (!ok || static_cast<int>(device_names.size()) <= index) {
RTC_LOG(LS_ERROR) << "Failed to get the device name";
return -1;
@@ -299,27 +338,26 @@
bool CoreAudioBase::Init() {
RTC_DLOG(INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
<< "]";
+ RTC_DCHECK_GE(device_index_, 0);
RTC_DCHECK(!device_id_.empty());
RTC_DCHECK(audio_device_buffer_);
RTC_DCHECK(!audio_client_);
RTC_DCHECK(!audio_session_control_.Get());
- // Use an existing |device_id_| and set parameters which are required to
- // create an audio client. It is up to the parent class to set |device_id_|.
- // TODO(henrika): add unique information about device role since |device_id_|
- // does not uniquely identify the device and role if there is only one
- // physical device.
- std::string device_id = device_id_;
- ERole role = eConsole;
- if (IsDefaultDevice(device_id)) {
- device_id = AudioDeviceName::kDefaultDeviceId;
+ // Use an existing combination of |device_index_| and |device_id_| to set
+ // parameters which are required to create an audio client. It is up to the
+ // parent class to set |device_index_| and |device_id_|.
+ std::string device_id = AudioDeviceName::kDefaultDeviceId;
+ ERole role = ERole();
+ if (IsDefaultDevice(device_index_)) {
role = eConsole;
- } else if (IsDefaultCommunicationsDevice(device_id)) {
- device_id = AudioDeviceName::kDefaultDeviceId;
+ } else if (IsDefaultCommunicationsDevice(device_index_)) {
role = eCommunications;
} else {
- RTC_DLOG(LS_WARNING) << "Not using a default device";
+ device_id = device_id_;
}
+ RTC_LOG(LS_INFO) << "Unique device identifier: device_id=" << device_id
+ << ", role=" << RoleToString(role);
// Create an IAudioClient interface which enables us to create and initialize
// an audio stream between an audio application and the audio engine.
diff --git a/modules/audio_device/win/core_audio_base_win.h b/modules/audio_device/win/core_audio_base_win.h
index 3e33d68..87f306f 100644
--- a/modules/audio_device/win/core_audio_base_win.h
+++ b/modules/audio_device/win/core_audio_base_win.h
@@ -117,8 +117,8 @@
bool IsOutput() const;
bool IsDefaultDevice(int index) const;
bool IsDefaultCommunicationsDevice(int index) const;
- bool IsDefaultDevice(const std::string& device_id) const;
- bool IsDefaultCommunicationsDevice(const std::string& device_id) const;
+ bool IsDefaultDeviceId(const std::string& device_id) const;
+ bool IsDefaultCommunicationsDeviceId(const std::string& device_id) const;
EDataFlow GetDataFlow() const;
bool IsRestarting() const;
int64_t TimeSinceStart() const;
@@ -151,7 +151,7 @@
ScopedHandle restart_event_;
int64_t start_time_ = 0;
std::string device_id_;
- int device_index_;
+ int device_index_ = -1;
// Used by the IAudioSessionEvents implementations. Currently only utilized
// for debugging purposes.
LONG ref_count_ = 1;
diff --git a/modules/audio_device/win/core_audio_input_win.cc b/modules/audio_device/win/core_audio_input_win.cc
index 8c1b06e..d55c0ae 100644
--- a/modules/audio_device/win/core_audio_input_win.cc
+++ b/modules/audio_device/win/core_audio_input_win.cc
@@ -46,13 +46,13 @@
int CoreAudioInput::Init() {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
- StopRecording();
return 0;
}
int CoreAudioInput::Terminate() {
RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
+ StopRecording();
return 0;
}
@@ -63,11 +63,16 @@
int CoreAudioInput::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
+ RTC_DCHECK_GE(index, 0);
+ RTC_DCHECK_RUN_ON(&thread_checker_);
return CoreAudioBase::SetDevice(index);
}
int CoreAudioInput::SetDevice(AudioDeviceModule::WindowsDeviceType device) {
- RTC_DLOG(INFO) << __FUNCTION__ << ": " << device;
+ RTC_DLOG(INFO) << __FUNCTION__ << ": "
+ << ((device == AudioDeviceModule::kDefaultDevice)
+ ? "Default"
+ : "DefaultCommunication");
RTC_DCHECK_RUN_ON(&thread_checker_);
return SetDevice((device == AudioDeviceModule::kDefaultDevice) ? 0 : 1);
}
@@ -239,7 +244,6 @@
}
bool CoreAudioInput::Restarting() const {
- RTC_DLOG(INFO) << __FUNCTION__;
RTC_DCHECK_RUN_ON(&thread_checker_);
return IsRestarting();
}
diff --git a/modules/audio_device/win/core_audio_output_win.cc b/modules/audio_device/win/core_audio_output_win.cc
index aeada67..dc82a61 100644
--- a/modules/audio_device/win/core_audio_output_win.cc
+++ b/modules/audio_device/win/core_audio_output_win.cc
@@ -61,12 +61,16 @@
int CoreAudioOutput::SetDevice(int index) {
RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
+ RTC_DCHECK_GE(index, 0);
RTC_DCHECK_RUN_ON(&thread_checker_);
return CoreAudioBase::SetDevice(index);
}
int CoreAudioOutput::SetDevice(AudioDeviceModule::WindowsDeviceType device) {
- RTC_DLOG(INFO) << __FUNCTION__ << ": " << device;
+ RTC_DLOG(INFO) << __FUNCTION__ << ": "
+ << ((device == AudioDeviceModule::kDefaultDevice)
+ ? "Default"
+ : "DefaultCommunication");
RTC_DCHECK_RUN_ON(&thread_checker_);
return SetDevice((device == AudioDeviceModule::kDefaultDevice) ? 0 : 1);
}
diff --git a/modules/audio_device/win/core_audio_utility_win.cc b/modules/audio_device/win/core_audio_utility_win.cc
index 4aaf155..29f73c2 100644
--- a/modules/audio_device/win/core_audio_utility_win.cc
+++ b/modules/audio_device/win/core_audio_utility_win.cc
@@ -295,6 +295,13 @@
_com_error error(S_FALSE);
if (device_id == AudioDeviceName::kDefaultDeviceId) {
+ // Get the default audio endpoint for the specified data-flow direction and
+ // role. Note that, if only a single rendering or capture device is
+ // available, the system always assigns all three rendering or capture roles
+ // to that device. If the method fails to find a rendering or capture device
+ // for the specified role, this means that no rendering or capture device is
+ // available at all. If no device is available, the method sets the output
+ // pointer to NULL and returns ERROR_NOT_FOUND.
error = device_enum->GetDefaultAudioEndpoint(
data_flow, role, audio_endpoint_device.GetAddressOf());
if (FAILED(error.Error())) {
@@ -303,6 +310,8 @@
<< ErrorToString(error);
}
} else {
+ // Ask for an audio endpoint device that is identified by an endpoint ID
+ // string.
error = device_enum->GetDevice(rtc::ToUtf16(device_id).c_str(),
audio_endpoint_device.GetAddressOf());
if (FAILED(error.Error())) {
@@ -313,7 +322,7 @@
// Verify that the audio endpoint device is active, i.e., that the audio
// adapter that connects to the endpoint device is present and enabled.
- if (SUCCEEDED(error.Error()) &&
+ if (SUCCEEDED(error.Error()) && !audio_endpoint_device.Get() &&
!IsDeviceActive(audio_endpoint_device.Get())) {
RTC_LOG(LS_WARNING) << "Selected endpoint device is not active";
audio_endpoint_device.Reset();
@@ -463,77 +472,124 @@
bool GetDeviceNamesInternal(EDataFlow data_flow,
webrtc::AudioDeviceNames* device_names) {
- // Always add the default device in index 0 and the default communication
- // device as index 1 in the vector. The name of the default device starts
- // with "Default - " and the default communication device starts with
- // "Communication - ".
- // Example of friendly name: "Default - Headset (SB Arena Headset)"
- ERole role[] = {eConsole, eCommunications};
+ RTC_DLOG(LS_INFO) << "GetDeviceNamesInternal: flow="
+ << FlowToString(data_flow);
+
+ // Generate a collection of active audio endpoint devices for the specified
+ // direction.
+ ComPtr<IMMDeviceCollection> collection = CreateCollectionInternal(data_flow);
+ if (!collection.Get()) {
+ RTC_LOG(LS_ERROR) << "Failed to create a collection of active devices";
+ return false;
+ }
+
+ // Retrieve the number of active (present, not disabled and plugged in) audio
+ // devices for the specified direction.
+ UINT number_of_active_devices = 0;
+ _com_error error = collection->GetCount(&number_of_active_devices);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDeviceCollection::GetCount failed: "
+ << ErrorToString(error);
+ return false;
+ }
+
+ if (number_of_active_devices == 0) {
+ RTC_DLOG(LS_WARNING) << "Found no active devices";
+ return false;
+ }
+
+ // Loop over all active devices and add friendly name and unique id to the
+ // |device_names| queue. For now, devices are added at indexes 0, 1, ..., N-1
+ // but they will be moved to 2,3,..., N+1 at the next stage when default and
+ // default communication devices are added at index 0 and 1.
+ ComPtr<IMMDevice> audio_device;
+ for (UINT i = 0; i < number_of_active_devices; ++i) {
+ // Retrieve a pointer to the specified item in the device collection.
+ error = collection->Item(i, audio_device.GetAddressOf());
+ if (FAILED(error.Error())) {
+ // Skip this item and try to get the next item instead; will result in an
+ // incomplete list of devices.
+ RTC_LOG(LS_WARNING) << "IMMDeviceCollection::Item failed: "
+ << ErrorToString(error);
+ continue;
+ }
+ if (!audio_device.Get()) {
+ RTC_LOG(LS_WARNING) << "Invalid audio device";
+ continue;
+ }
+
+ // Retrieve the complete device name for the given audio device endpoint.
+ AudioDeviceName device_name(
+ GetDeviceFriendlyNameInternal(audio_device.Get()),
+ GetDeviceIdInternal(audio_device.Get()));
+ // Add combination of user-friendly and unique name to the output list.
+ device_names->push_back(device_name);
+ }
+
+ // Log a warning of the list of device is not complete but let's keep on
+ // trying to add default and default communications device at the front.
+ if (device_names->size() != number_of_active_devices) {
+ RTC_DLOG(LS_WARNING)
+ << "List of device names does not contain all active devices";
+ }
+
+ // Avoid adding default and default communication devices if no active device
+ // could be added to the queue. We might as well break here and return false
+ // since no active devices were identified.
+ if (device_names->empty()) {
+ RTC_DLOG(LS_ERROR) << "List of active devices is empty";
+ return false;
+ }
+
+ // Prepend the queue with two more elements: one for the default device and
+ // one for the default communication device (can correspond to the same unique
+ // id if only one active device exists). The first element (index 0) is the
+ // default device and the second element (index 1) is the default
+ // communication device.
+ ERole role[] = {eCommunications, eConsole};
ComPtr<IMMDevice> default_device;
AudioDeviceName default_device_name;
for (size_t i = 0; i < arraysize(role); ++i) {
default_device = CreateDeviceInternal(AudioDeviceName::kDefaultDeviceId,
data_flow, role[i]);
if (!default_device.Get()) {
- return false;
+ // Add empty strings to device name if the device could not be created.
+ RTC_DLOG(LS_WARNING) << "Failed to add device with role: "
+ << RoleToString(role[i]);
+ default_device_name.device_name = std::string();
+ default_device_name.unique_id = std::string();
+ } else {
+ // Populate the device name with friendly name and unique id.
+ std::string device_name;
+ device_name += (role[i] == eConsole ? "Default - " : "Communication - ");
+ device_name += GetDeviceFriendlyNameInternal(default_device.Get());
+ std::string unique_id = GetDeviceIdInternal(default_device.Get());
+ default_device_name.device_name = std::move(device_name);
+ default_device_name.unique_id = std::move(unique_id);
}
- std::string device_name;
- device_name += (role[i] == eConsole ? "Default - " : "Communication - ");
- device_name += GetDeviceFriendlyNameInternal(default_device.Get());
- std::string unique_id = GetDeviceIdInternal(default_device.Get());
-
- default_device_name.device_name = std::move(device_name);
- default_device_name.unique_id = std::move(unique_id);
- RTC_DLOG(INFO) << "friendly name: " << default_device_name.device_name;
- RTC_DLOG(INFO) << "unique id : " << default_device_name.unique_id;
- // Add combination of user-friendly and unique name to the output list.
- device_names->emplace_back(default_device_name);
+ // Add combination of user-friendly and unique name to the output queue.
+ // The last element (<=> eConsole) will be at the front of the queue, hence
+ // at index 0. Empty strings will be added for cases where no default
+ // devices were found.
+ device_names->push_front(default_device_name);
}
- // Next, add all active input devices on index 2 and above. Note that,
- // one device can have more than one role. Hence, if only one input device
- // is present, the output vector will contain three elements all with the
- // same unique ID but with different names.
- // Example (one capture device but three elements in device_names):
- // 0: friendly name: Default - Headset (SB Arena Headset)
- // 0: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338}
- // 1: friendly name: Communication - Headset (SB Arena Headset)
- // 1: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338}
- // 2: friendly name: Headset (SB Arena Headset)
- // 2: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338}
-
- // Generate a collection of active audio endpoint devices for the specified
- // direction.
- ComPtr<IMMDeviceCollection> collection = CreateCollectionInternal(data_flow);
- if (!collection.Get()) {
- return false;
- }
-
- // Retrieve the number of active audio devices for the specified direction.
- UINT number_of_active_devices = 0;
- collection->GetCount(&number_of_active_devices);
- if (number_of_active_devices == 0) {
- return true;
- }
-
- // Loop over all active devices and add friendly name and unique ID to the
- // |device_names| list which already contains two elements
- RTC_DCHECK_EQ(device_names->size(), 2);
- for (UINT i = 0; i < number_of_active_devices; ++i) {
- // Retrieve a pointer to the specified item in the device collection.
- ComPtr<IMMDevice> audio_device;
- _com_error error = collection->Item(i, audio_device.GetAddressOf());
- if (FAILED(error.Error()))
- continue;
- // Retrieve the complete device name for the given audio device endpoint.
- AudioDeviceName device_name(
- GetDeviceFriendlyNameInternal(audio_device.Get()),
- GetDeviceIdInternal(audio_device.Get()));
- RTC_DLOG(INFO) << "friendly name: " << device_name.device_name;
- RTC_DLOG(INFO) << "unique id : " << device_name.unique_id;
- // Add combination of user-friendly and unique name to the output list.
- device_names->emplace_back(device_name);
+ // Example of log output when only one device is active. Note that the queue
+ // contains two extra elements at index 0 (Default) and 1 (Communication) to
+ // allow selection of device by role instead of id. All elements corresponds
+ // the same unique id.
+ // [0] friendly name: Default - Headset Microphone (2- Arctis 7 Chat)
+ // [0] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ // [1] friendly name: Communication - Headset Microphone (2- Arctis 7 Chat)
+ // [1] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ // [2] friendly name: Headset Microphone (2- Arctis 7 Chat)
+ // [2] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ for (size_t i = 0; i < device_names->size(); ++i) {
+ RTC_DLOG(INFO) << "[" << i
+ << "] friendly name: " << (*device_names)[i].device_name;
+ RTC_DLOG(INFO) << "[" << i
+ << "] unique id : " << (*device_names)[i].unique_id;
}
return true;
@@ -741,12 +797,14 @@
bool GetInputDeviceNames(webrtc::AudioDeviceNames* device_names) {
RTC_DLOG(INFO) << "GetInputDeviceNames";
RTC_DCHECK(device_names);
+ RTC_DCHECK(device_names->empty());
return GetDeviceNamesInternal(eCapture, device_names);
}
bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names) {
RTC_DLOG(INFO) << "GetOutputDeviceNames";
RTC_DCHECK(device_names);
+ RTC_DCHECK(device_names->empty());
return GetDeviceNamesInternal(eRender, device_names);
}