blob: fef5c90b7498b23197a659f3a4703399a5d2b535 [file] [log] [blame]
// Copyright (c) 2013 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 "chromeos/audio/cras_audio_handler.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/sys_info.h"
#include "chromeos/audio/audio_devices_pref_handler_stub.h"
#include "chromeos/dbus/dbus_thread_manager.h"
using std::max;
using std::min;
namespace chromeos {
namespace {
// Default value for unmuting, as a percent in the range [0, 100].
// Used when sound is unmuted, but volume was less than kMuteThresholdPercent.
const int kDefaultUnmuteVolumePercent = 4;
// Volume value which should be considered as muted in range [0, 100].
const int kMuteThresholdPercent = 1;
// The duration of HDMI output re-discover grace period in milliseconds.
const int kHDMIRediscoverGracePeriodDurationInMs = 2000;
// Mixer matrix, [0.5, 0.5; 0.5, 0.5]
const std::vector<double> kStereoToMono = {0.5, 0.5, 0.5, 0.5};
// Mixer matrix, [1, 0; 0, 1]
const std::vector<double> kStereoToStereo = {1, 0, 0, 1};
static CrasAudioHandler* g_cras_audio_handler = NULL;
bool IsSameAudioDevice(const AudioDevice& a, const AudioDevice& b) {
return a.stable_device_id == b.stable_device_id && a.is_input == b.is_input &&
a.type == b.type && a.device_name == b.device_name;
}
bool IsInNodeList(uint64_t node_id,
const CrasAudioHandler::NodeIdList& id_list) {
return std::find(id_list.begin(), id_list.end(), node_id) != id_list.end();
}
bool IsDeviceInList(const AudioDevice& device, const AudioNodeList& node_list) {
for (const AudioNode& node : node_list) {
if (device.stable_device_id == node.stable_device_id)
return true;
}
return false;
}
} // namespace
CrasAudioHandler::AudioObserver::AudioObserver() {
}
CrasAudioHandler::AudioObserver::~AudioObserver() {
}
void CrasAudioHandler::AudioObserver::OnOutputNodeVolumeChanged(
uint64_t /* node_id */,
int /* volume */) {
}
void CrasAudioHandler::AudioObserver::OnInputNodeGainChanged(
uint64_t /* node_id */,
int /* gain */) {
}
void CrasAudioHandler::AudioObserver::OnOutputMuteChanged(
bool /* mute_on */,
bool /* system_adjust */) {}
void CrasAudioHandler::AudioObserver::OnInputMuteChanged(bool /* mute_on */) {
}
void CrasAudioHandler::AudioObserver::OnAudioNodesChanged() {
}
void CrasAudioHandler::AudioObserver::OnActiveOutputNodeChanged() {
}
void CrasAudioHandler::AudioObserver::OnActiveInputNodeChanged() {
}
void CrasAudioHandler::AudioObserver::OnOuputChannelRemixingChanged(
bool /* mono_on */) {
}
// static
void CrasAudioHandler::Initialize(
scoped_refptr<AudioDevicesPrefHandler> audio_pref_handler) {
CHECK(!g_cras_audio_handler);
g_cras_audio_handler = new CrasAudioHandler(audio_pref_handler);
}
// static
void CrasAudioHandler::InitializeForTesting() {
CHECK(!g_cras_audio_handler);
CrasAudioHandler::Initialize(new AudioDevicesPrefHandlerStub());
}
// static
void CrasAudioHandler::Shutdown() {
CHECK(g_cras_audio_handler);
delete g_cras_audio_handler;
g_cras_audio_handler = NULL;
}
// static
bool CrasAudioHandler::IsInitialized() {
return g_cras_audio_handler != NULL;
}
// static
CrasAudioHandler* CrasAudioHandler::Get() {
CHECK(g_cras_audio_handler)
<< "CrasAudioHandler::Get() called before Initialize().";
return g_cras_audio_handler;
}
void CrasAudioHandler::AddAudioObserver(AudioObserver* observer) {
observers_.AddObserver(observer);
}
void CrasAudioHandler::RemoveAudioObserver(AudioObserver* observer) {
observers_.RemoveObserver(observer);
}
bool CrasAudioHandler::HasKeyboardMic() {
return GetKeyboardMic() != NULL;
}
bool CrasAudioHandler::IsOutputMuted() {
return output_mute_on_;
}
bool CrasAudioHandler::IsOutputMutedForDevice(uint64_t device_id) {
const AudioDevice* device = GetDeviceFromId(device_id);
if (!device)
return false;
DCHECK(!device->is_input);
return audio_pref_handler_->GetMuteValue(*device);
}
bool CrasAudioHandler::IsOutputVolumeBelowDefaultMuteLevel() {
return output_volume_ <= kMuteThresholdPercent;
}
bool CrasAudioHandler::IsInputMuted() {
return input_mute_on_;
}
bool CrasAudioHandler::IsInputMutedForDevice(uint64_t device_id) {
const AudioDevice* device = GetDeviceFromId(device_id);
if (!device)
return false;
DCHECK(device->is_input);
// We don't record input mute state for each device in the prefs,
// for any non-active input device, we assume mute is off.
if (device->id == active_input_node_id_)
return input_mute_on_;
return false;
}
int CrasAudioHandler::GetOutputDefaultVolumeMuteThreshold() {
return kMuteThresholdPercent;
}
int CrasAudioHandler::GetOutputVolumePercent() {
return output_volume_;
}
int CrasAudioHandler::GetOutputVolumePercentForDevice(uint64_t device_id) {
if (device_id == active_output_node_id_) {
return output_volume_;
} else {
const AudioDevice* device = GetDeviceFromId(device_id);
return static_cast<int>(audio_pref_handler_->GetOutputVolumeValue(device));
}
}
int CrasAudioHandler::GetInputGainPercent() {
return input_gain_;
}
int CrasAudioHandler::GetInputGainPercentForDevice(uint64_t device_id) {
if (device_id == active_input_node_id_) {
return input_gain_;
} else {
const AudioDevice* device = GetDeviceFromId(device_id);
return static_cast<int>(audio_pref_handler_->GetInputGainValue(device));
}
}
uint64_t CrasAudioHandler::GetPrimaryActiveOutputNode() const {
return active_output_node_id_;
}
uint64_t CrasAudioHandler::GetPrimaryActiveInputNode() const {
return active_input_node_id_;
}
void CrasAudioHandler::GetAudioDevices(AudioDeviceList* device_list) const {
device_list->clear();
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it)
device_list->push_back(it->second);
}
bool CrasAudioHandler::GetPrimaryActiveOutputDevice(AudioDevice* device) const {
const AudioDevice* active_device = GetDeviceFromId(active_output_node_id_);
if (!active_device || !device)
return false;
*device = *active_device;
return true;
}
void CrasAudioHandler::SetKeyboardMicActive(bool active) {
const AudioDevice* keyboard_mic = GetKeyboardMic();
if (!keyboard_mic)
return;
// Keyboard mic is invisible to chromeos users. It is always added or removed
// as additional active node.
DCHECK(active_input_node_id_ && active_input_node_id_ != keyboard_mic->id);
if (active)
AddActiveNode(keyboard_mic->id, true);
else
RemoveActiveNodeInternal(keyboard_mic->id, true);
}
void CrasAudioHandler::AddActiveNode(uint64_t node_id, bool notify) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device) {
VLOG(1) << "AddActiveInputNode: Cannot find device id="
<< "0x" << std::hex << node_id;
return;
}
// If there is no primary active device, set |node_id| to primary active node.
if ((device->is_input && !active_input_node_id_) ||
(!device->is_input && !active_output_node_id_)) {
SwitchToDevice(*device, notify, ACTIVATE_BY_USER);
return;
}
AddAdditionalActiveNode(node_id, notify);
}
void CrasAudioHandler::ChangeActiveNodes(const NodeIdList& new_active_ids) {
// Flags for whether there are input or output nodes passed in from
// |new_active_ids|. If there are no input nodes passed in, we will not
// make any change for input nodes; same for the output nodes.
bool request_input_change = false;
bool request_output_change = false;
// Flags for whether we will actually change active status of input
// or output nodes.
bool make_input_change = false;
bool make_output_change = false;
NodeIdList nodes_to_activate;
for (size_t i = 0; i < new_active_ids.size(); ++i) {
const AudioDevice* device = GetDeviceFromId(new_active_ids[i]);
if (device) {
if (device->is_input)
request_input_change = true;
else
request_output_change = true;
// If the new active device is already active, keep it as active.
if (device->active)
continue;
nodes_to_activate.push_back(new_active_ids[i]);
if (device->is_input)
make_input_change = true;
else
make_output_change = true;
}
}
// Remove all existing active devices that are not in the |new_active_ids|
// list.
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
AudioDevice device = it->second;
// Remove the existing active input or output nodes that are not in the new
// active node list if there are new input or output nodes specified.
if (device.active) {
if ((device.is_input && request_input_change &&
!IsInNodeList(device.id, new_active_ids))) {
make_input_change = true;
RemoveActiveNodeInternal(device.id, false); // no notification.
} else if (!device.is_input && request_output_change &&
!IsInNodeList(device.id, new_active_ids)) {
make_output_change = true;
RemoveActiveNodeInternal(device.id, false); // no notification.
}
}
}
// Adds the new active devices. Note that this function is used by audio
// extension to manage multiple active nodes, in which case the devices
// selection preference is controlled by the app, we intentionally exclude
// the additional nodes from saving their device states as a result.
for (size_t i = 0; i < nodes_to_activate.size(); ++i)
AddActiveNode(nodes_to_activate[i], false); // no notification.
// Notify the active nodes change now.
if (make_input_change)
NotifyActiveNodeChanged(true);
if (make_output_change)
NotifyActiveNodeChanged(false);
}
void CrasAudioHandler::SwapInternalSpeakerLeftRightChannel(bool swap) {
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end();
++it) {
const AudioDevice& device = it->second;
if (!device.is_input && device.type == AUDIO_TYPE_INTERNAL_SPEAKER) {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->SwapLeftRight(
device.id, swap);
break;
}
}
}
void CrasAudioHandler::SetOutputMono(bool mono_on) {
output_mono_on_ = mono_on;
if (mono_on) {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetGlobalOutputChannelRemix(output_channels_, kStereoToMono);
} else {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetGlobalOutputChannelRemix(output_channels_, kStereoToStereo);
}
FOR_EACH_OBSERVER(
AudioObserver, observers_,
OnOuputChannelRemixingChanged(mono_on));
}
bool CrasAudioHandler::IsOutputMonoEnabled() const {
return output_mono_on_;
}
bool CrasAudioHandler::has_alternative_input() const {
return has_alternative_input_;
}
bool CrasAudioHandler::has_alternative_output() const {
return has_alternative_output_;
}
void CrasAudioHandler::SetOutputVolumePercent(int volume_percent) {
// Set all active devices to the same volume.
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end();
it++) {
const AudioDevice& device = it->second;
if (!device.is_input && device.active)
SetOutputNodeVolumePercent(device.id, volume_percent);
}
}
// TODO: Rename the 'Percent' to something more meaningful.
void CrasAudioHandler::SetInputGainPercent(int gain_percent) {
// TODO(jennyz): Should we set all input devices' gain to the same level?
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end();
it++) {
const AudioDevice& device = it->second;
if (device.is_input && device.active)
SetInputNodeGainPercent(active_input_node_id_, gain_percent);
}
}
void CrasAudioHandler::AdjustOutputVolumeByPercent(int adjust_by_percent) {
SetOutputVolumePercent(output_volume_ + adjust_by_percent);
}
void CrasAudioHandler::SetOutputMute(bool mute_on) {
if (!SetOutputMuteInternal(mute_on))
return;
// Save the mute state for all active output audio devices.
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end();
it++) {
const AudioDevice& device = it->second;
if (!device.is_input && device.active) {
audio_pref_handler_->SetMuteValue(device, output_mute_on_);
}
}
FOR_EACH_OBSERVER(
AudioObserver, observers_,
OnOutputMuteChanged(output_mute_on_, false /* system_adjust */));
}
void CrasAudioHandler::AdjustOutputVolumeToAudibleLevel() {
if (output_volume_ <= kMuteThresholdPercent) {
// Avoid the situation when sound has been unmuted, but the volume
// is set to a very low value, so user still can't hear any sound.
SetOutputVolumePercent(kDefaultUnmuteVolumePercent);
}
}
void CrasAudioHandler::SetInputMute(bool mute_on) {
SetInputMuteInternal(mute_on);
FOR_EACH_OBSERVER(AudioObserver, observers_,
OnInputMuteChanged(input_mute_on_));
}
void CrasAudioHandler::SetActiveDevice(const AudioDevice& active_device,
bool notify,
DeviceActivateType activate_by) {
if (active_device.is_input) {
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->SetActiveInputNode(active_device.id);
} else {
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->SetActiveOutputNode(active_device.id);
}
if (notify)
NotifyActiveNodeChanged(active_device.is_input);
// Save active state for the nodes.
for (AudioDeviceMap::iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
const AudioDevice& device = it->second;
if (device.is_input != active_device.is_input)
continue;
SaveDeviceState(device, device.active, activate_by);
}
}
void CrasAudioHandler::SaveDeviceState(const AudioDevice& device,
bool active,
DeviceActivateType activate_by) {
// Don't save the active state for non-simple usage device, which is invisible
// to end users.
if (!device.is_for_simple_usage())
return;
if (!active) {
audio_pref_handler_->SetDeviceActive(device, false, false);
} else {
switch (activate_by) {
case ACTIVATE_BY_USER:
audio_pref_handler_->SetDeviceActive(device, true, true);
break;
case ACTIVATE_BY_PRIORITY:
audio_pref_handler_->SetDeviceActive(device, true, false);
break;
default:
// The device is made active due to its previous active state in prefs,
// don't change its active state settings in prefs.
break;
}
}
}
void CrasAudioHandler::SetVolumeGainPercentForDevice(uint64_t device_id,
int value) {
const AudioDevice* device = GetDeviceFromId(device_id);
if (!device)
return;
if (device->is_input)
SetInputNodeGainPercent(device_id, value);
else
SetOutputNodeVolumePercent(device_id, value);
}
void CrasAudioHandler::SetMuteForDevice(uint64_t device_id, bool mute_on) {
if (device_id == active_output_node_id_) {
SetOutputMute(mute_on);
return;
} else if (device_id == active_input_node_id_) {
VLOG(1) << "SetMuteForDevice sets active input device id="
<< "0x" << std::hex << device_id << " mute=" << mute_on;
SetInputMute(mute_on);
return;
}
const AudioDevice* device = GetDeviceFromId(device_id);
// Input device's mute state is not recorded in the pref. crbug.com/365050.
if (device && !device->is_input)
audio_pref_handler_->SetMuteValue(*device, mute_on);
}
void CrasAudioHandler::LogErrors() {
log_errors_ = true;
}
// If the HDMI device is the active output device, when the device enters/exits
// docking mode, or HDMI display changes resolution, or chromeos device
// suspends/resumes, cras will lose the HDMI output node for a short period of
// time, then rediscover it. This hotplug behavior will cause the audio output
// be leaked to the alternatvie active audio output during HDMI re-discovering
// period. See crbug.com/503667.
void CrasAudioHandler::SetActiveHDMIOutoutRediscoveringIfNecessary(
bool force_rediscovering) {
if (!GetDeviceFromId(active_output_node_id_))
return;
// Marks the start of the HDMI re-discovering grace period, during which we
// will mute the audio output to prevent it to be be leaked to the
// alternative output device.
if ((hdmi_rediscovering_ && force_rediscovering) ||
(!hdmi_rediscovering_ && IsHDMIPrimaryOutputDevice())) {
StartHDMIRediscoverGracePeriod();
}
}
CrasAudioHandler::CrasAudioHandler(
scoped_refptr<AudioDevicesPrefHandler> audio_pref_handler)
: audio_pref_handler_(audio_pref_handler),
output_mute_on_(false),
input_mute_on_(false),
output_volume_(0),
input_gain_(0),
active_output_node_id_(0),
active_input_node_id_(0),
has_alternative_input_(false),
has_alternative_output_(false),
output_mute_locked_(false),
output_channels_(2),
output_mono_on_(false),
log_errors_(false),
hdmi_rediscover_grace_period_duration_in_ms_(
kHDMIRediscoverGracePeriodDurationInMs),
hdmi_rediscovering_(false),
weak_ptr_factory_(this) {
if (!audio_pref_handler.get())
return;
// If the DBusThreadManager or the CrasAudioClient aren't available, there
// isn't much we can do. This should only happen when running tests.
if (!chromeos::DBusThreadManager::IsInitialized() ||
!chromeos::DBusThreadManager::Get() ||
!chromeos::DBusThreadManager::Get()->GetCrasAudioClient())
return;
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->AddObserver(this);
audio_pref_handler_->AddAudioPrefObserver(this);
if (chromeos::DBusThreadManager::Get()->GetSessionManagerClient()) {
chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
AddObserver(this);
}
InitializeAudioState();
}
CrasAudioHandler::~CrasAudioHandler() {
hdmi_rediscover_timer_.Stop();
if (!chromeos::DBusThreadManager::IsInitialized() ||
!chromeos::DBusThreadManager::Get() ||
!chromeos::DBusThreadManager::Get()->GetCrasAudioClient())
return;
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
RemoveObserver(this);
chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
RemoveObserver(this);
if (audio_pref_handler_.get())
audio_pref_handler_->RemoveAudioPrefObserver(this);
audio_pref_handler_ = NULL;
}
void CrasAudioHandler::AudioClientRestarted() {
// Make sure the logging is enabled in case cras server
// restarts after crashing.
LogErrors();
InitializeAudioState();
}
void CrasAudioHandler::NodesChanged() {
if (cras_service_available_)
GetNodes();
}
void CrasAudioHandler::OutputNodeVolumeChanged(uint64_t node_id, int volume) {
const AudioDevice* device = this->GetDeviceFromId(node_id);
// If this is not an active output node, ignore this event. Because when this
// node set to active, it will be applied with the volume value stored in
// preference.
if (!device || !device->active || device->is_input) {
LOG(ERROR) << "Unexpexted OutputNodeVolumeChanged received on node: 0x"
<< std::hex << node_id;
return;
}
// Sync internal volume state and notify UI for the change. We trust cras
// signal to report the volume state of the device, no matter which source
// set the volume, i.e., volume could be set from non-chrome source, like
// Bluetooth headset, etc. Assume all active output devices share a single
// volume.
output_volume_ = volume;
audio_pref_handler_->SetVolumeGainValue(*device, volume);
FOR_EACH_OBSERVER(AudioObserver, observers_,
OnOutputNodeVolumeChanged(node_id, volume));
}
void CrasAudioHandler::ActiveOutputNodeChanged(uint64_t node_id) {
if (active_output_node_id_ == node_id)
return;
// Active audio output device should always be changed by chrome.
// During system boot, cras may change active input to unknown device 0x1,
// we don't need to log it, since it is not an valid device.
if (GetDeviceFromId(node_id)) {
LOG_IF(WARNING, log_errors_)
<< "Active output node changed unexpectedly by system node_id="
<< "0x" << std::hex << node_id;
}
}
void CrasAudioHandler::ActiveInputNodeChanged(uint64_t node_id) {
if (active_input_node_id_ == node_id)
return;
// Active audio input device should always be changed by chrome.
// During system boot, cras may change active input to unknown device 0x2,
// we don't need to log it, since it is not an valid device.
if (GetDeviceFromId(node_id)) {
LOG_IF(WARNING, log_errors_)
<< "Active input node changed unexpectedly by system node_id="
<< "0x" << std::hex << node_id;
}
}
void CrasAudioHandler::OnAudioPolicyPrefChanged() {
ApplyAudioPolicy();
}
void CrasAudioHandler::EmitLoginPromptVisibleCalled() {
// Enable logging after cras server is started, which will be after
// EmitLoginPromptVisible.
LogErrors();
}
const AudioDevice* CrasAudioHandler::GetDeviceFromId(uint64_t device_id) const {
AudioDeviceMap::const_iterator it = audio_devices_.find(device_id);
if (it == audio_devices_.end())
return NULL;
return &(it->second);
}
const AudioDevice* CrasAudioHandler::GetDeviceFromStableDeviceId(
uint64_t stable_device_id) const {
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
if (it->second.stable_device_id == stable_device_id)
return &(it->second);
}
return NULL;
}
const AudioDevice* CrasAudioHandler::GetKeyboardMic() const {
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); it++) {
if (it->second.is_input && it->second.type == AUDIO_TYPE_KEYBOARD_MIC)
return &(it->second);
}
return NULL;
}
void CrasAudioHandler::SetupAudioInputState() {
// Set the initial audio state to the ones read from audio prefs.
const AudioDevice* device = GetDeviceFromId(active_input_node_id_);
if (!device) {
LOG_IF(ERROR, log_errors_)
<< "Can't set up audio state for unknown input device id ="
<< "0x" << std::hex << active_input_node_id_;
return;
}
input_gain_ = audio_pref_handler_->GetInputGainValue(device);
VLOG(1) << "SetupAudioInputState for active device id="
<< "0x" << std::hex << device->id << " mute=" << input_mute_on_;
SetInputMuteInternal(input_mute_on_);
// TODO(rkc,jennyz): Set input gain once we decide on how to store
// the gain values since the range and step are both device specific.
}
void CrasAudioHandler::SetupAudioOutputState() {
const AudioDevice* device = GetDeviceFromId(active_output_node_id_);
if (!device) {
LOG_IF(ERROR, log_errors_)
<< "Can't set up audio state for unknown output device id ="
<< "0x" << std::hex << active_output_node_id_;
return;
}
DCHECK(!device->is_input);
// Mute the output during HDMI re-discovering grace period.
if (hdmi_rediscovering_ && !IsHDMIPrimaryOutputDevice()) {
VLOG(1) << "Mute the output during HDMI re-discovering grace period";
output_mute_on_ = true;
} else {
output_mute_on_ = audio_pref_handler_->GetMuteValue(*device);
}
output_volume_ = audio_pref_handler_->GetOutputVolumeValue(device);
SetOutputMuteInternal(output_mute_on_);
SetOutputNodeVolume(active_output_node_id_, output_volume_);
}
// This sets up the state of an additional active node.
void CrasAudioHandler::SetupAdditionalActiveAudioNodeState(uint64_t node_id) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device) {
VLOG(1) << "Can't set up audio state for unknown device id ="
<< "0x" << std::hex << node_id;
return;
}
DCHECK(node_id != active_output_node_id_ && node_id != active_input_node_id_);
// Note: The mute state is a system wide state, we don't set mute per device,
// but just keep the mute state consistent for the active node in prefs.
// The output volume should be set to the same value for all active output
// devices. For input devices, we don't restore their gain value so far.
// TODO(jennyz): crbug.com/417418, track the status for the decison if
// we should persist input gain value in prefs.
if (!device->is_input) {
audio_pref_handler_->SetMuteValue(*device, IsOutputMuted());
SetOutputNodeVolumePercent(node_id, GetOutputVolumePercent());
}
}
void CrasAudioHandler::InitializeAudioState() {
ApplyAudioPolicy();
// Defer querying cras for GetNodes until cras service becomes available.
cras_service_available_ = false;
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->WaitForServiceToBeAvailable(base::Bind(
&CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable,
weak_ptr_factory_.GetWeakPtr()));
}
void CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable(
bool service_is_available) {
if (!service_is_available) {
LOG(ERROR) << "Cras service is not available";
cras_service_available_ = false;
return;
}
cras_service_available_ = true;
GetNodes();
}
void CrasAudioHandler::ApplyAudioPolicy() {
output_mute_locked_ = false;
if (!audio_pref_handler_->GetAudioOutputAllowedValue()) {
// Mute the device, but do not update the preference.
SetOutputMuteInternal(true);
output_mute_locked_ = true;
} else {
// Restore the mute state.
const AudioDevice* device = GetDeviceFromId(active_output_node_id_);
if (device)
SetOutputMuteInternal(audio_pref_handler_->GetMuteValue(*device));
}
// Policy for audio input is handled by kAudioCaptureAllowed in the Chrome
// media system.
}
void CrasAudioHandler::SetOutputNodeVolume(uint64_t node_id, int volume) {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetOutputNodeVolume(node_id, volume);
}
void CrasAudioHandler::SetOutputNodeVolumePercent(uint64_t node_id,
int volume_percent) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device || device->is_input)
return;
volume_percent = min(max(volume_percent, 0), 100);
if (volume_percent <= kMuteThresholdPercent)
volume_percent = 0;
// Save the volume setting in pref in case this is called on non-active
// node for configuration.
audio_pref_handler_->SetVolumeGainValue(*device, volume_percent);
if (device->active)
SetOutputNodeVolume(node_id, volume_percent);
}
bool CrasAudioHandler::SetOutputMuteInternal(bool mute_on) {
if (output_mute_locked_)
return false;
output_mute_on_ = mute_on;
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetOutputUserMute(mute_on);
return true;
}
void CrasAudioHandler::SetInputNodeGain(uint64_t node_id, int gain) {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetInputNodeGain(node_id, gain);
}
void CrasAudioHandler::SetInputNodeGainPercent(uint64_t node_id,
int gain_percent) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device || !device->is_input)
return;
// NOTE: We do not sanitize input gain values since the range is completely
// dependent on the device.
if (active_input_node_id_ == node_id)
input_gain_ = gain_percent;
audio_pref_handler_->SetVolumeGainValue(*device, gain_percent);
if (device->active) {
SetInputNodeGain(node_id, gain_percent);
FOR_EACH_OBSERVER(AudioObserver, observers_,
OnInputNodeGainChanged(node_id, gain_percent));
}
}
void CrasAudioHandler::SetInputMuteInternal(bool mute_on) {
input_mute_on_ = mute_on;
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
SetInputMute(mute_on);
}
void CrasAudioHandler::GetNodes() {
chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->GetNodes(
base::Bind(&CrasAudioHandler::HandleGetNodes,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&CrasAudioHandler::HandleGetNodesError,
weak_ptr_factory_.GetWeakPtr()));
}
bool CrasAudioHandler::ChangeActiveDevice(
const AudioDevice& new_active_device) {
uint64_t& current_active_node_id = new_active_device.is_input
? active_input_node_id_
: active_output_node_id_;
// If the device we want to switch to is already the current active device,
// do nothing.
if (new_active_device.active &&
new_active_device.id == current_active_node_id) {
return false;
}
bool found_new_active_device = false;
// Reset all other input or output devices' active status. The active audio
// device from the previous user session can be remembered by cras, but not
// in chrome. see crbug.com/273271.
for (AudioDeviceMap::iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
if (it->second.is_input == new_active_device.is_input &&
it->second.id != new_active_device.id) {
it->second.active = false;
} else if (it->second.is_input == new_active_device.is_input &&
it->second.id == new_active_device.id) {
found_new_active_device = true;
}
}
if (!found_new_active_device) {
LOG(ERROR) << "Invalid new active device: " << new_active_device.ToString();
return false;
}
// Set the current active input/output device to the new_active_device.
current_active_node_id = new_active_device.id;
audio_devices_[current_active_node_id].active = true;
return true;
}
void CrasAudioHandler::SwitchToDevice(const AudioDevice& device,
bool notify,
DeviceActivateType activate_by) {
if (!ChangeActiveDevice(device))
return;
if (device.is_input)
SetupAudioInputState();
else
SetupAudioOutputState();
SetActiveDevice(device, notify, activate_by);
}
bool CrasAudioHandler::HasDeviceChange(const AudioNodeList& new_nodes,
bool is_input,
AudioDevicePriorityQueue* new_discovered,
bool* device_removed,
bool* active_device_removed) {
*device_removed = false;
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
const AudioDevice& device = it->second;
if (is_input != device.is_input)
continue;
if (!IsDeviceInList(device, new_nodes)) {
*device_removed = true;
if ((is_input && device.id == active_input_node_id_) ||
(!is_input && device.id == active_output_node_id_)) {
*active_device_removed = true;
}
}
}
bool new_or_changed_device = false;
while (!new_discovered->empty())
new_discovered->pop();
for (AudioNodeList::const_iterator it = new_nodes.begin();
it != new_nodes.end(); ++it) {
if (is_input != it->is_input)
continue;
// Check if the new device is not in the old device list.
AudioDevice device(*it);
DeviceStatus status = CheckDeviceStatus(device);
if (status == NEW_DEVICE)
new_discovered->push(device);
if (status == NEW_DEVICE || status == CHANGED_DEVICE) {
new_or_changed_device = true;
}
}
return new_or_changed_device || *device_removed;
}
CrasAudioHandler::DeviceStatus CrasAudioHandler::CheckDeviceStatus(
const AudioDevice& device) {
const AudioDevice* device_found =
GetDeviceFromStableDeviceId(device.stable_device_id);
if (!device_found)
return NEW_DEVICE;
if (!IsSameAudioDevice(device, *device_found)) {
LOG(ERROR) << "Different Audio devices with same stable device id:"
<< " new device: " << device.ToString()
<< " old device: " << device_found->ToString();
return CHANGED_DEVICE;
} else if (device.active != device_found->active) {
return CHANGED_DEVICE;
}
return OLD_DEVICE;
}
void CrasAudioHandler::NotifyActiveNodeChanged(bool is_input) {
if (is_input)
FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveInputNodeChanged());
else
FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveOutputNodeChanged());
}
bool CrasAudioHandler::GetActiveDeviceFromUserPref(bool is_input,
AudioDevice* active_device) {
bool found_active_device = false;
bool last_active_device_activate_by_user = false;
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
AudioDevice device = it->second;
if (device.is_input != is_input || !device.is_for_simple_usage())
continue;
bool active = false;
bool activate_by_user = false;
if (!audio_pref_handler_->GetDeviceActive(device, &active,
&activate_by_user) ||
!active) {
continue;
}
if (!found_active_device) {
found_active_device = true;
*active_device = device;
last_active_device_activate_by_user = activate_by_user;
continue;
}
// Choose the best one among multiple active devices from prefs.
if (activate_by_user) {
if (!last_active_device_activate_by_user) {
// Device activated by user has higher priority than the one
// is not activated by user.
*active_device = device;
last_active_device_activate_by_user = true;
} else {
// If there are more than one active devices activated by user in the
// prefs, most likely, after the device was shut down, and before it
// is rebooted, user has plugged in some previously unplugged audio
// devices. For such case, it does not make sense to honor the active
// states in the prefs.
VLOG(1) << "Found more than one user activated devices in the prefs.";
return false;
}
} else if (!last_active_device_activate_by_user) {
// If there are more than one active devices activated by priority in the
// prefs, most likely, cras is still enumerating the audio devices
// progressively. For such case, it does not make sense to honor the
// active states in the prefs.
VLOG(1) << "Found more than one active devices by priority in the prefs.";
return false;
}
}
if (found_active_device && !active_device->is_for_simple_usage()) {
// This is an odd case which is rare but possible to happen during cras
// initialization depeneding the audio device enumation process. The only
// audio node coming from cras is an internal audio device not visible
// to user, such as AUDIO_TYPE_POST_MIX_LOOPBACK.
return false;
}
return found_active_device;
}
void CrasAudioHandler::HandleNonHotplugNodesChange(
bool is_input,
const AudioDevicePriorityQueue& hotplug_nodes,
bool has_device_change,
bool has_device_removed,
bool active_device_removed) {
bool has_current_active_node =
is_input ? active_input_node_id_ : active_output_node_id_;
// No device change, extra NodesChanged signal received.
if (!has_device_change && has_current_active_node)
return;
if (hotplug_nodes.empty()) {
if (has_device_removed) {
if (!active_device_removed && has_current_active_node) {
// Removed a non-active device, keep the current active device.
return;
}
if (active_device_removed) {
// Unplugged the current active device.
SwitchToTopPriorityDevice(is_input);
return;
}
}
// Some unexpected error happens on cras side. See crbug.com/586026.
// Either cras sent stale nodes to chrome again or cras triggered some
// error. Restore the previously selected active.
VLOG(1) << "Odd case from cras, the active node is lost unexpectedly.";
SwitchToPreviousActiveDeviceIfAvailable(is_input);
} else {
// Looks like a new chrome session starts.
SwitchToPreviousActiveDeviceIfAvailable(is_input);
}
}
void CrasAudioHandler::HandleHotPlugDevice(
const AudioDevice& hotplug_device,
const AudioDevicePriorityQueue& device_priority_queue) {
// This most likely may happen during the transition period of cras
// initialization phase, in which a non-simple-usage node may appear like
// a hotplug node.
if (!hotplug_device.is_for_simple_usage())
return;
bool last_state_active = false;
bool last_activate_by_user = false;
if (!audio_pref_handler_->GetDeviceActive(hotplug_device, &last_state_active,
&last_activate_by_user)) {
// |hotplug_device| is plugged in for the first time, activate it if it
// is of the highest priority.
if (device_priority_queue.top().id == hotplug_device.id) {
VLOG(1) << "Hotplug a device for the first time: "
<< hotplug_device.ToString();
SwitchToDevice(hotplug_device, true, ACTIVATE_BY_PRIORITY);
}
} else if (last_state_active) {
VLOG(1) << "Hotplug a device, restore to active: "
<< hotplug_device.ToString();
SwitchToDevice(hotplug_device, true, ACTIVATE_BY_RESTORE_PREVIOUS_STATE);
} else {
// Do not active the device if its previous state is inactive.
VLOG(1) << "Hotplug device remains inactive as its previous state:"
<< hotplug_device.ToString();
}
}
void CrasAudioHandler::SwitchToTopPriorityDevice(bool is_input) {
AudioDevice top_device =
is_input ? input_devices_pq_.top() : output_devices_pq_.top();
if (top_device.is_for_simple_usage())
SwitchToDevice(top_device, true, ACTIVATE_BY_PRIORITY);
}
void CrasAudioHandler::SwitchToPreviousActiveDeviceIfAvailable(bool is_input) {
AudioDevice previous_active_device;
if (GetActiveDeviceFromUserPref(is_input, &previous_active_device)) {
DCHECK(previous_active_device.is_for_simple_usage());
// Switch to previous active device stored in user prefs.
SwitchToDevice(previous_active_device, true,
ACTIVATE_BY_RESTORE_PREVIOUS_STATE);
} else {
// No previous active device, switch to the top priority device.
SwitchToTopPriorityDevice(is_input);
}
}
void CrasAudioHandler::UpdateDevicesAndSwitchActive(
const AudioNodeList& nodes) {
size_t old_output_device_size = 0;
size_t old_input_device_size = 0;
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
if (it->second.is_input)
++old_input_device_size;
else
++old_output_device_size;
}
AudioDevicePriorityQueue hotplug_output_nodes;
AudioDevicePriorityQueue hotplug_input_nodes;
bool has_output_removed = false;
bool has_input_removed = false;
bool active_output_removed = false;
bool active_input_removed = false;
bool output_devices_changed =
HasDeviceChange(nodes, false, &hotplug_output_nodes, &has_output_removed,
&active_output_removed);
bool input_devices_changed =
HasDeviceChange(nodes, true, &hotplug_input_nodes, &has_input_removed,
&active_input_removed);
audio_devices_.clear();
has_alternative_input_ = false;
has_alternative_output_ = false;
while (!input_devices_pq_.empty())
input_devices_pq_.pop();
while (!output_devices_pq_.empty())
output_devices_pq_.pop();
size_t new_output_device_size = 0;
size_t new_input_device_size = 0;
for (size_t i = 0; i < nodes.size(); ++i) {
AudioDevice device(nodes[i]);
audio_devices_[device.id] = device;
if (!has_alternative_input_ &&
device.is_input &&
device.type != AUDIO_TYPE_INTERNAL_MIC &&
device.type != AUDIO_TYPE_KEYBOARD_MIC) {
has_alternative_input_ = true;
} else if (!has_alternative_output_ &&
!device.is_input &&
device.type != AUDIO_TYPE_INTERNAL_SPEAKER) {
has_alternative_output_ = true;
}
if (device.is_input) {
input_devices_pq_.push(device);
++new_input_device_size;
} else {
output_devices_pq_.push(device);
++new_output_device_size;
}
}
// Handle output device changes.
HandleAudioDeviceChange(false, output_devices_pq_, hotplug_output_nodes,
output_devices_changed, has_output_removed,
active_output_removed);
// Handle input device changes.
HandleAudioDeviceChange(true, input_devices_pq_, hotplug_input_nodes,
input_devices_changed, has_input_removed,
active_input_removed);
}
void CrasAudioHandler::HandleAudioDeviceChange(
bool is_input,
const AudioDevicePriorityQueue& devices_pq,
const AudioDevicePriorityQueue& hotplug_nodes,
bool has_device_change,
bool has_device_removed,
bool active_device_removed) {
uint64_t& active_node_id =
is_input ? active_input_node_id_ : active_output_node_id_;
// No audio devices found.
if (devices_pq.empty()) {
VLOG(1) << "No " << (is_input ? "input" : "output") << " devices found";
active_node_id = 0;
NotifyActiveNodeChanged(is_input);
return;
}
// If the previous active device is removed from the new node list,
// or changed to inactive by cras, reset active_node_id.
// See crbug.com/478968.
const AudioDevice* active_device = GetDeviceFromId(active_node_id);
if (!active_device || !active_device->active)
active_node_id = 0;
if (!active_node_id || hotplug_nodes.empty() || hotplug_nodes.size() > 1) {
HandleNonHotplugNodesChange(is_input, hotplug_nodes, has_device_change,
has_device_removed, active_device_removed);
} else {
// Typical user hotplug case.
HandleHotPlugDevice(hotplug_nodes.top(), devices_pq);
}
}
void CrasAudioHandler::HandleGetNodes(const chromeos::AudioNodeList& node_list,
bool success) {
if (!success) {
LOG_IF(ERROR, log_errors_) << "Failed to retrieve audio nodes data";
return;
}
UpdateDevicesAndSwitchActive(node_list);
FOR_EACH_OBSERVER(AudioObserver, observers_, OnAudioNodesChanged());
}
void CrasAudioHandler::HandleGetNodesError(const std::string& error_name,
const std::string& error_msg) {
LOG_IF(ERROR, log_errors_) << "Failed to call GetNodes: "
<< error_name << ": " << error_msg;
}
void CrasAudioHandler::AddAdditionalActiveNode(uint64_t node_id, bool notify) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device) {
VLOG(1) << "AddActiveInputNode: Cannot find device id="
<< "0x" << std::hex << node_id;
return;
}
audio_devices_[node_id].active = true;
SetupAdditionalActiveAudioNodeState(node_id);
if (device->is_input) {
DCHECK(node_id != active_input_node_id_);
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->AddActiveInputNode(node_id);
if (notify)
NotifyActiveNodeChanged(true);
} else {
DCHECK(node_id != active_output_node_id_);
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->AddActiveOutputNode(node_id);
if (notify)
NotifyActiveNodeChanged(false);
}
}
void CrasAudioHandler::RemoveActiveNodeInternal(uint64_t node_id, bool notify) {
const AudioDevice* device = GetDeviceFromId(node_id);
if (!device) {
VLOG(1) << "RemoveActiveInputNode: Cannot find device id="
<< "0x" << std::hex << node_id;
return;
}
audio_devices_[node_id].active = false;
if (device->is_input) {
if (node_id == active_input_node_id_)
active_input_node_id_ = 0;
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->RemoveActiveInputNode(node_id);
if (notify)
NotifyActiveNodeChanged(true);
} else {
if (node_id == active_output_node_id_)
active_output_node_id_ = 0;
chromeos::DBusThreadManager::Get()
->GetCrasAudioClient()
->RemoveActiveOutputNode(node_id);
if (notify)
NotifyActiveNodeChanged(false);
}
}
void CrasAudioHandler::UpdateAudioAfterHDMIRediscoverGracePeriod() {
VLOG(1) << "HDMI output re-discover grace period ends.";
hdmi_rediscovering_ = false;
if (!IsOutputMutedForDevice(active_output_node_id_)) {
// Unmute the audio output after the HDMI transition period.
VLOG(1) << "Unmute output after HDMI rediscovering grace period.";
SetOutputMuteInternal(false);
// Notify UI about the mute state change.
FOR_EACH_OBSERVER(
AudioObserver, observers_,
OnOutputMuteChanged(output_mute_on_, true /* system adjustment */));
}
}
bool CrasAudioHandler::IsHDMIPrimaryOutputDevice() const {
const AudioDevice* device = GetDeviceFromId(active_output_node_id_);
return (device && device->type == chromeos::AUDIO_TYPE_HDMI);
}
void CrasAudioHandler::StartHDMIRediscoverGracePeriod() {
VLOG(1) << "Start HDMI rediscovering grace period.";
hdmi_rediscovering_ = true;
hdmi_rediscover_timer_.Stop();
hdmi_rediscover_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(
hdmi_rediscover_grace_period_duration_in_ms_),
this, &CrasAudioHandler::UpdateAudioAfterHDMIRediscoverGracePeriod);
}
void CrasAudioHandler::SetHDMIRediscoverGracePeriodForTesting(
int duration_in_ms) {
hdmi_rediscover_grace_period_duration_in_ms_ = duration_in_ms;
}
} // namespace chromeos