chromium/chromeos/ash/components/audio/audio_device.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/audio/audio_device.h"

#include <stdint.h>

#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/format_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"

namespace ash {

namespace {

// Get the priority for a particular device type. The priority returned
// will be between 0 to 3, the higher number meaning a higher priority.
uint8_t GetDevicePriority(AudioDeviceType type, bool is_input) {
  switch (type) {
    case AudioDeviceType::kHeadphone:
    case AudioDeviceType::kLineout:
    case AudioDeviceType::kMic:
    case AudioDeviceType::kUsb:
    case AudioDeviceType::kBluetooth:
      return 3;
    case AudioDeviceType::kHdmi:
      return 2;
    case AudioDeviceType::kInternalSpeaker:
    case AudioDeviceType::kInternalMic:
    case AudioDeviceType::kFrontMic:
      return 1;
    // Lower the priority of bluetooth mic to prevent unexpected bad eperience
    // to user because of bluetooth audio profile switching. Make priority to
    // zero so this mic will never be automatically chosen.
    case AudioDeviceType::kBluetoothNbMic:
    // Rear mic should have priority lower than front mic to prevent poor
    // quality input caused by accidental selecting to rear side mic.
    case AudioDeviceType::kRearMic:
    case AudioDeviceType::kKeyboardMic:
    case AudioDeviceType::kHotword:
    case AudioDeviceType::kPostMixLoopback:
    case AudioDeviceType::kPostDspLoopback:
    case AudioDeviceType::kAlsaLoopback:
    case AudioDeviceType::kOther:
    default:
      return 0;
  }
}

}  // namespace

// static
std::string AudioDevice::GetTypeString(AudioDeviceType type) {
  switch (type) {
    case AudioDeviceType::kHeadphone:
      return "HEADPHONE";
    case AudioDeviceType::kMic:
      return "MIC";
    case AudioDeviceType::kUsb:
      return "USB";
    case AudioDeviceType::kBluetooth:
      return "BLUETOOTH";
    case AudioDeviceType::kBluetoothNbMic:
      return "BLUETOOTH_NB_MIC";
    case AudioDeviceType::kHdmi:
      return "HDMI";
    case AudioDeviceType::kInternalSpeaker:
      return "INTERNAL_SPEAKER";
    case AudioDeviceType::kInternalMic:
      return "INTERNAL_MIC";
    case AudioDeviceType::kFrontMic:
      return "FRONT_MIC";
    case AudioDeviceType::kRearMic:
      return "REAR_MIC";
    case AudioDeviceType::kKeyboardMic:
      return "KEYBOARD_MIC";
    case AudioDeviceType::kHotword:
      return "HOTWORD";
    case AudioDeviceType::kLineout:
      return "LINEOUT";
    case AudioDeviceType::kPostMixLoopback:
      return "POST_MIX_LOOPBACK";
    case AudioDeviceType::kPostDspLoopback:
      return "POST_DSP_LOOPBACK";
    case AudioDeviceType::kAlsaLoopback:
      return "ALSA_LOOPBACK";
    case AudioDeviceType::kOther:
    default:
      return "OTHER";
  }
}

// static
AudioDeviceType AudioDevice::GetAudioType(const std::string& node_type) {
  if (base::Contains(node_type, "HEADPHONE")) {
    return AudioDeviceType::kHeadphone;
  } else if (base::Contains(node_type, "INTERNAL_MIC")) {
    return AudioDeviceType::kInternalMic;
  } else if (base::Contains(node_type, "FRONT_MIC")) {
    return AudioDeviceType::kFrontMic;
  } else if (base::Contains(node_type, "REAR_MIC")) {
    return AudioDeviceType::kRearMic;
  } else if (base::Contains(node_type, "KEYBOARD_MIC")) {
    return AudioDeviceType::kKeyboardMic;
  } else if (base::Contains(node_type, "BLUETOOTH_NB_MIC")) {
    return AudioDeviceType::kBluetoothNbMic;
  } else if (base::Contains(node_type, "MIC")) {
    return AudioDeviceType::kMic;
  } else if (base::Contains(node_type, "USB")) {
    return AudioDeviceType::kUsb;
  } else if (base::Contains(node_type, "BLUETOOTH")) {
    return AudioDeviceType::kBluetooth;
  } else if (base::Contains(node_type, "HDMI")) {
    return AudioDeviceType::kHdmi;
  } else if (base::Contains(node_type, "INTERNAL_SPEAKER")) {
    return AudioDeviceType::kInternalSpeaker;
  }
  // TODO(hychao): Remove the 'AOKR' matching line after CRAS switches
  // node type naming to 'HOTWORD'.
  else if (base::Contains(node_type, "AOKR")) {
    return AudioDeviceType::kHotword;
  } else if (base::Contains(node_type, "HOTWORD")) {
    return AudioDeviceType::kHotword;
  } else if (base::Contains(node_type, "LINEOUT")) {
    return AudioDeviceType::kLineout;
  } else if (base::Contains(node_type, "POST_MIX_LOOPBACK")) {
    return AudioDeviceType::kPostMixLoopback;
  } else if (base::Contains(node_type, "POST_DSP_LOOPBACK")) {
    return AudioDeviceType::kPostDspLoopback;
  } else if (base::Contains(node_type, "ALSA_LOOPBACK")) {
    return AudioDeviceType::kAlsaLoopback;
  } else {
    return AudioDeviceType::kOther;
  }
}

AudioDevice::AudioDevice() = default;

AudioDevice::AudioDevice(const AudioNode& node) {
  is_input = node.is_input;
  id = node.id;
  stable_device_id_version = node.StableDeviceIdVersion();
  stable_device_id = node.StableDeviceId();
  if (stable_device_id_version == 2) {
    deprecated_stable_device_id = node.stable_device_id_v1;
  }
  type = GetAudioType(node.type);
  if (!node.name.empty() && node.name != "(default)") {
    display_name = node.name;
  } else {
    display_name = node.device_name;
  }
  device_name = node.device_name;
  priority = GetDevicePriority(type, node.is_input);
  active = node.active;
  plugged_time = node.plugged_time;
  max_supported_channels = node.max_supported_channels;
  audio_effect = node.audio_effect;
  number_of_volume_steps = node.number_of_volume_steps;
}

AudioDevice::AudioDevice(const AudioDevice& other) = default;

AudioDevice& AudioDevice::operator=(const AudioDevice& other) = default;

std::string AudioDevice::ToString() const {
  if (stable_device_id_version == 0) {
    return "Null device";
  }

  std::string result;
  base::StringAppendF(&result, "is_input = %s ", is_input ? "true" : "false");
  base::StringAppendF(&result, "id = 0x%" PRIx64 " ", id);
  base::StringAppendF(&result, "stable_device_id_version = %d",
                      stable_device_id_version);
  base::StringAppendF(&result, "stable_device_id = 0x%" PRIx64 " ",
                      stable_device_id);
  base::StringAppendF(&result, "deprecated_stable_device_id = 0x%" PRIx64 " ",
                      deprecated_stable_device_id);
  base::StringAppendF(&result, "display_name = %s ", display_name.c_str());
  base::StringAppendF(&result, "device_name = %s ", device_name.c_str());
  base::StringAppendF(&result, "type = %s ", GetTypeString(type).c_str());
  base::StringAppendF(&result, "priority = %d ", priority);
  base::StringAppendF(&result, "user_priority = %d ", user_priority);
  base::StringAppendF(&result, "active = %s ", active ? "true" : "false");
  base::StringAppendF(&result, "plugged_time = %s ",
                      base::NumberToString(plugged_time).c_str());
  base::StringAppendF(&result, "max_supported_channels = %s ",
                      base::NumberToString(max_supported_channels).c_str());
  base::StringAppendF(&result, "audio_effect = %s ",
                      base::NumberToString(audio_effect).c_str());
  base::StringAppendF(&result, "number_of_volume_steps = %s ",
                      base::NumberToString(number_of_volume_steps).c_str());
  return result;
}

bool AudioDevice::IsExternalDevice() const {
  if (!is_for_simple_usage()) {
    return false;
  }

  return is_input ? !IsInternalMic() : !IsInternalSpeaker();
}

bool AudioDevice::IsInternalMic() const {
  switch (type) {
    case AudioDeviceType::kInternalMic:
    case AudioDeviceType::kFrontMic:
    case AudioDeviceType::kRearMic:
      return true;
    default:
      return false;
  }
}

bool AudioDevice::IsInternalSpeaker() const {
  return type == AudioDeviceType::kInternalSpeaker;
}

bool LessBuiltInPriority(const AudioDevice& a, const AudioDevice& b) {
  if (a.priority < b.priority) {
    return true;
  } else if (b.priority < a.priority) {
    return false;
  } else if (a.plugged_time < b.plugged_time) {
    return true;
  } else {
    return false;
  }
}

bool LessUserPriority(const AudioDevice& a, const AudioDevice& b) {
  if (a.user_priority < b.user_priority) {
    return true;
  } else if (b.user_priority < a.user_priority) {
    return false;
  } else {
    return LessBuiltInPriority(a, b);
  }
}

bool AudioDeviceCompare::operator()(const AudioDevice& a,
                                    const AudioDevice& b) const {
  return LessUserPriority(a, b);
}

}  // namespace ash