diff --git a/modules/audio_device/audio_device_name.h b/modules/audio_device/audio_device_name.h index 06a03fddc1..baabd781a7 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 #include -#include namespace webrtc { @@ -41,7 +41,7 @@ struct AudioDeviceName { std::string unique_id; // Unique identifier for the device. }; -typedef std::vector AudioDeviceNames; +typedef std::deque 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 52da075cc7..bf3bf1ab80 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* DirectionToString(CoreAudioBase::Direction direction) { } } +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 @@ bool CoreAudioBase::IsDefaultCommunicationsDevice(int index) const { 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 @@ std::string CoreAudioBase::GetDeviceID(int index) const { 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 @@ int CoreAudioBase::DeviceName(int index, 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 @@ int CoreAudioBase::DeviceName(int index, 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(device_names.size()) <= index) { RTC_LOG(LS_ERROR) << "Failed to get the device name"; return -1; @@ -299,27 +338,26 @@ int CoreAudioBase::DeviceName(int index, 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 3e33d689aa..87f306f541 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 @@ class CoreAudioBase : public IAudioSessionEvents { 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 @@ class CoreAudioBase : public IAudioSessionEvents { 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 8c1b06e02f..d55c0ae75c 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 @@ CoreAudioInput::~CoreAudioInput() { 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::NumDevices() const { 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 @@ int CoreAudioInput::RestartRecording() { } 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 aeada677b9..dc82a61493 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::NumDevices() const { 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 4aaf155ac8..29f73c24ac 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 @@ ComPtr CreateDeviceInternal(const std::string& device_id, _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 @@ ComPtr CreateDeviceInternal(const std::string& device_id, << 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 @@ ComPtr CreateDeviceInternal(const std::string& device_id, // 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 @@ ComPtr CreateCollectionInternal(EDataFlow data_flow) { 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 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 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 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 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 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 @@ EDataFlow GetDataFlow(IMMDevice* device) { 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); }