chromium/media/audio/cras/audio_manager_cras.cc

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

#include "media/audio/cras/audio_manager_cras.h"

#include <stddef.h>

#include <algorithm>
#include <map>
#include <utility>

#include "base/check_op.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/nix/xdg_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/cras/cras_input.h"
#include "media/audio/cras/cras_unified.h"
#include "media/audio/cras/cras_util.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
#include "media/base/localized_strings.h"
#include "media/base/media_switches.h"

namespace media {
namespace {

// Default sample rate for input and output streams.
const int kDefaultSampleRate = 48000;

// Default input buffer size.
const int kDefaultInputBufferSize = 1024;

// Default output buffer size.
const int kDefaultOutputBufferSize = 512;

}  // namespace

bool AudioManagerCras::HasAudioOutputDevices() {
  return true;
}

bool AudioManagerCras::HasAudioInputDevices() {
  return !cras_util_->CrasGetAudioDevices(DeviceType::kInput).empty();
}

AudioManagerCras::AudioManagerCras(std::unique_ptr<AudioThread> audio_thread,
                                   AudioLogFactory* audio_log_factory)
    : AudioManagerCrasBase(std::move(audio_thread), audio_log_factory),
      cras_util_(std::make_unique<CrasUtil>()),
      main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      weak_ptr_factory_(this) {
  weak_this_ = weak_ptr_factory_.GetWeakPtr();
}

AudioManagerCras::~AudioManagerCras() = default;

void AudioManagerCras::GetAudioInputDeviceNames(
    AudioDeviceNames* device_names) {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kInput)) {
    device_names->emplace_back(device.name, base::NumberToString(device.id));
  }
  if (!device_names->empty()) {
    device_names->push_front(AudioDeviceName::CreateDefault());
  }
}

void AudioManagerCras::GetAudioOutputDeviceNames(
    AudioDeviceNames* device_names) {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kOutput)) {
    device_names->emplace_back(device.name, base::NumberToString(device.id));
  }
  if (!device_names->empty()) {
    device_names->push_front(AudioDeviceName::CreateDefault());
  }
}

// Checks if a system AEC with a specific group ID is flagged to be deactivated
// by the field trial.
bool IsSystemAecDeactivated(int aec_group_id) {
  return base::GetFieldTrialParamByFeatureAsBool(
      media::kCrOSSystemAECDeactivatedGroups,
      base::NumberToString(aec_group_id), false);
}

// Checks if the board with `aec_group_id` is flagged by the field trial to not
// allow using DSP-based AEC effect.
bool IsDspBasedAecDeactivated(int aec_group_id) {
  return base::GetFieldTrialParamByFeatureAsBool(
             media::kCrOSDspBasedAecDeactivatedGroups,
             base::NumberToString(aec_group_id), false) ||
         !base::FeatureList::IsEnabled(media::kCrOSDspBasedAecAllowed);
}

// Checks if the board with `aec_group_id` is flagged by the field trial to not
// allow using DSP-based NS effect.
bool IsDspBasedNsDeactivated(int aec_group_id) {
  return base::GetFieldTrialParamByFeatureAsBool(
             media::kCrOSDspBasedNsDeactivatedGroups,
             base::NumberToString(aec_group_id), false) ||
         !base::FeatureList::IsEnabled(media::kCrOSDspBasedNsAllowed);
}

// Checks if the board with `aec_group_id` is flagged by the field trial to not
// allow using DSP-based AGC effect.
bool IsDspBasedAgcDeactivated(int aec_group_id) {
  return base::GetFieldTrialParamByFeatureAsBool(
             media::kCrOSDspBasedAgcDeactivatedGroups,
             base::NumberToString(aec_group_id), false) ||
         !base::FeatureList::IsEnabled(media::kCrOSDspBasedAgcAllowed);
}

// Specifies which DSP-based effects are allowed based on media constraints and
// any finch field trials.
void SetAllowedDspBasedEffects(int aec_group_id, AudioParameters& params) {
  int effects = params.effects();

  // Allow AEC to be applied by CRAS on DSP if the AEC is active in CRAS and if
  // using the AEC on DSP has not been deactivated by any field trials.
  if ((effects & AudioParameters::ECHO_CANCELLER) &&
      !IsDspBasedAecDeactivated(aec_group_id)) {
    effects = effects | AudioParameters::ALLOW_DSP_ECHO_CANCELLER;
  } else {
    effects = effects & ~AudioParameters::ALLOW_DSP_ECHO_CANCELLER;
  }

  // Allow NS to be applied by CRAS on DSP if the NS is active in CRAS and if
  // using the NS on DSP has not been deactivated by any field trials.
  if ((effects & AudioParameters::NOISE_SUPPRESSION) &&
      !IsDspBasedNsDeactivated(aec_group_id)) {
    effects = effects | AudioParameters::ALLOW_DSP_NOISE_SUPPRESSION;
  } else {
    effects = effects & ~AudioParameters::ALLOW_DSP_NOISE_SUPPRESSION;
  }

  // Allow AGC to be applied by CRAS on DSP if the AGC is active in CRAS and if
  // using the AGC on DSP has not been deactivated by any field trials.
  if ((effects & AudioParameters::AUTOMATIC_GAIN_CONTROL) &&
      !IsDspBasedAgcDeactivated(aec_group_id)) {
    effects = effects | AudioParameters::ALLOW_DSP_AUTOMATIC_GAIN_CONTROL;
  } else {
    effects = effects & ~AudioParameters::ALLOW_DSP_AUTOMATIC_GAIN_CONTROL;
  }

  params.set_effects(effects);
}

// Collects flags values for whether, and in what way, the AEC, NS or AGC
// effects should be enforced in spite of them not being flagged as supported by
// the board.
void RetrieveSystemEffectFeatures(bool& enforce_system_aec,
                                  bool& enforce_system_ns,
                                  bool& enforce_system_agc,
                                  bool& tuned_system_aec_allowed) {
  const bool enforce_system_aec_ns_agc_feature =
      base::FeatureList::IsEnabled(media::kCrOSEnforceSystemAecNsAgc);
  const bool enforce_system_aec_ns_feature =
      base::FeatureList::IsEnabled(media::kCrOSEnforceSystemAecNs);
  const bool enforce_system_aec_agc_feature =
      base::FeatureList::IsEnabled(media::kCrOSEnforceSystemAecAgc);
  const bool enforce_system_aec_feature =
      base::FeatureList::IsEnabled(media::kCrOSEnforceSystemAec);
  const bool enforce_system_aec_by_policy =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSystemAecEnabled);

  enforce_system_aec =
      enforce_system_aec_feature || enforce_system_aec_ns_agc_feature ||
      enforce_system_aec_ns_feature || enforce_system_aec_agc_feature ||
      enforce_system_aec_by_policy;
  enforce_system_ns =
      enforce_system_aec_ns_agc_feature || enforce_system_aec_ns_feature;
  enforce_system_agc =
      enforce_system_aec_ns_agc_feature || enforce_system_aec_agc_feature;

  tuned_system_aec_allowed =
      base::FeatureList::IsEnabled(media::kCrOSSystemAEC);
}

AudioParameters AudioManagerCras::GetStreamParametersForSystem(
    int user_buffer_size) {
  AudioParameters params(
      AudioParameters::AUDIO_PCM_LOW_LATENCY, ChannelLayoutConfig::Stereo(),
      kDefaultSampleRate, user_buffer_size,
      AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
                                            limits::kMaxAudioBufferSize));

  bool enforce_system_aec;
  bool enforce_system_ns;
  bool enforce_system_agc;
  bool tuned_system_aec_allowed;
  RetrieveSystemEffectFeatures(enforce_system_aec, enforce_system_ns,
                               enforce_system_agc, tuned_system_aec_allowed);

  // Activation of the system AEC. Allow experimentation with system AEC with
  // all devices, but enable it by default on devices that actually support it.
  params.set_effects(params.effects() |
                     AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);

  // Rephrase the field aec_supported to properly reflect its meaning in this
  // context (since it currently signals whether an CrAS APM with tuned settings
  // is available).
  const bool tuned_system_apm_available = cras_util_->CrasGetAecSupported();

  // Don't use the system AEC if it is deactivated for this group ID. Also never
  // activate NS nor AGC for this board if the AEC is not activated, since this
  // will cause issues for the Browser AEC.
  bool use_system_aec =
      (tuned_system_apm_available && tuned_system_aec_allowed) ||
      enforce_system_aec;

  bool system_ns_supported = cras_util_->CrasGetNsSupported();
  bool system_agc_supported = cras_util_->CrasGetAgcSupported();

  int aec_group_id = cras_util_->CrasGetAecGroupId();
  if (!use_system_aec || IsSystemAecDeactivated(aec_group_id)) {
    SetAllowedDspBasedEffects(aec_group_id, params);
    return params;
  }

  // Activation of the system AEC.
  params.set_effects(params.effects() | AudioParameters::ECHO_CANCELLER);

  // Don't use system NS or AGC if the AEC has board-specific tunings.
  if (!tuned_system_apm_available) {
    // Activation of the system NS.
    if (system_ns_supported || enforce_system_ns) {
      params.set_effects(params.effects() | AudioParameters::NOISE_SUPPRESSION);
    }

    // Activation of the system AGC.
    if (system_agc_supported || enforce_system_agc) {
      params.set_effects(params.effects() |
                         AudioParameters::AUTOMATIC_GAIN_CONTROL);
    }
  }

  if (base::FeatureList::IsEnabled(media::kCrOSSystemVoiceIsolationOption)) {
    if (cras_util_->CrasGetVoiceIsolationSupported()) {
      params.set_effects(params.effects() |
                         AudioParameters::VOICE_ISOLATION_SUPPORTED);
    }
  }

  SetAllowedDspBasedEffects(aec_group_id, params);
  return params;
}

AudioParameters AudioManagerCras::GetInputStreamParameters(
    const std::string& device_id) {
  DCHECK(GetTaskRunner()->BelongsToCurrentThread());

  int user_buffer_size = GetUserBufferSize();
  user_buffer_size =
      user_buffer_size ? user_buffer_size : kDefaultInputBufferSize;

  return GetStreamParametersForSystem(user_buffer_size);
}

std::string AudioManagerCras::GetDefaultInputDeviceID() {
  DCHECK(GetTaskRunner()->BelongsToCurrentThread());
  return base::NumberToString(GetPrimaryActiveInputNode());
}

std::string AudioManagerCras::GetDefaultOutputDeviceID() {
  DCHECK(GetTaskRunner()->BelongsToCurrentThread());
  return base::NumberToString(GetPrimaryActiveOutputNode());
}

std::string AudioManagerCras::GetGroupIDInput(const std::string& device_id) {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kInput)) {
    if (base::NumberToString(device.id) == device_id ||
        (AudioDeviceDescription::IsDefaultDevice(device_id) && device.active)) {
      return device.dev_name;
    }
  }
  return "";
}

std::string AudioManagerCras::GetGroupIDOutput(const std::string& device_id) {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kOutput)) {
    if (base::NumberToString(device.id) == device_id ||
        (AudioDeviceDescription::IsDefaultDevice(device_id) && device.active)) {
      return device.dev_name;
    }
  }
  return "";
}

std::string AudioManagerCras::GetAssociatedOutputDeviceID(
    const std::string& input_device_id) {
  if (AudioDeviceDescription::IsDefaultDevice(input_device_id)) {
    // Note: the default input should not be associated to any output, as this
    // may lead to accidental uses of a pinned stream.
    return "";
  }

  std::string device_name = GetGroupIDInput(input_device_id);

  if (device_name.empty()) {
    return "";
  }

  // Now search for an output device with the same device name.
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kOutput)) {
    if (device.dev_name == device_name) {
      return base::NumberToString(device.id);
    }
  }
  return "";
}

AudioParameters AudioManagerCras::GetPreferredOutputStreamParameters(
    const std::string& output_device_id,
    const AudioParameters& input_params) {
  DCHECK(GetTaskRunner()->BelongsToCurrentThread());
  ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Stereo();
  int sample_rate = kDefaultSampleRate;
  int buffer_size = GetUserBufferSize();
  if (input_params.IsValid()) {
    channel_layout_config = input_params.channel_layout_config();
    sample_rate = input_params.sample_rate();
    if (!buffer_size) {  // Not user-provided.
      buffer_size =
          std::min(static_cast<int>(limits::kMaxAudioBufferSize),
                   std::max(static_cast<int>(limits::kMinAudioBufferSize),
                            input_params.frames_per_buffer()));
    }
    return AudioParameters(
        AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout_config,
        sample_rate, buffer_size,
        AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
                                              limits::kMaxAudioBufferSize));
  }

  // Get max supported channels from |output_device_id| or the primary active
  // one if |output_device_id| is the default device.
  uint64_t preferred_device_id;
  if (AudioDeviceDescription::IsDefaultDevice(output_device_id)) {
    preferred_device_id = GetPrimaryActiveOutputNode();
  } else {
    if (!base::StringToUint64(output_device_id, &preferred_device_id)) {
      preferred_device_id = 0;  // 0 represents invalid |output_device_id|.
    }
  }

  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kOutput)) {
    if (device.id == preferred_device_id) {
      channel_layout_config = ChannelLayoutConfig::Guess(
          static_cast<int>(device.max_supported_channels));
      // Fall-back to old fashion: always fixed to STEREO layout.
      if (channel_layout_config.channel_layout() ==
          CHANNEL_LAYOUT_UNSUPPORTED) {
        channel_layout_config = ChannelLayoutConfig::Stereo();
      }
      break;
    }
  }

  if (!buffer_size) {  // Not user-provided.
    buffer_size = cras_util_->CrasGetDefaultOutputBufferSize();
  }

  if (buffer_size <= 0) {
    buffer_size = kDefaultOutputBufferSize;
  }

  return AudioParameters(
      AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout_config,
      sample_rate, buffer_size,
      AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
                                            limits::kMaxAudioBufferSize));
}

uint64_t AudioManagerCras::GetPrimaryActiveInputNode() {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kInput)) {
    if (device.active) {
      return device.id;
    }
  }
  return 0;
}

uint64_t AudioManagerCras::GetPrimaryActiveOutputNode() {
  for (const auto& device :
       cras_util_->CrasGetAudioDevices(DeviceType::kOutput)) {
    if (device.active) {
      return device.id;
    }
  }
  return 0;
}

bool AudioManagerCras::IsDefault(const std::string& device_id, bool is_input) {
  return AudioDeviceDescription::IsDefaultDevice(device_id);
}

enum CRAS_CLIENT_TYPE AudioManagerCras::GetClientType() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  return CRAS_CLIENT_TYPE_CHROME;
#else
  return CRAS_CLIENT_TYPE_LACROS;
#endif
}

}  // namespace media