diff options
author | rkc@chromium.org <rkc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-30 20:51:37 +0000 |
---|---|---|
committer | rkc@chromium.org <rkc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-30 20:51:37 +0000 |
commit | 47de7230cdd4d2708a4af607249d8f36e368764a (patch) | |
tree | 765d91a2ea5bdfeb7531dac89165badc546e0215 /chromeos/audio | |
parent | b3a5aeeaa91b682a1d90b131892a21886fcdcc3e (diff) | |
download | chromium_src-47de7230cdd4d2708a4af607249d8f36e368764a.zip chromium_src-47de7230cdd4d2708a4af607249d8f36e368764a.tar.gz chromium_src-47de7230cdd4d2708a4af607249d8f36e368764a.tar.bz2 |
Move audio device switching to Chrome.
Currently when a user plugs in (or plugs out) an audio device, cras checks the device priority and various other factors then switches the active device. Move this code to within Chrome. We're doing this primarily to be able to control the volume when an active device switches. We are moving to storing per device volumes in Chrome and we can't switch to the correct volume without a lag time (with the old volume) for a device unless the device switching is done in Chrome.
This CL is still untested - will discuss the changes needed in Cras that I can make locally to test this CL.
R=hshi@chromium.org, satorux@chromium.org
BUG=175798
TEST=Plug in headphones to a device and ensure that the new active device are the headphones.
Review URL: https://codereview.chromium.org/13993031
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197462 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos/audio')
-rw-r--r-- | chromeos/audio/audio_device.cc | 78 | ||||
-rw-r--r-- | chromeos/audio/audio_device.h | 28 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_handler.cc | 4 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_switch_handler.cc | 161 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_switch_handler.h | 81 |
5 files changed, 331 insertions, 21 deletions
diff --git a/chromeos/audio/audio_device.cc b/chromeos/audio/audio_device.cc index f41acda..4c28719 100644 --- a/chromeos/audio/audio_device.cc +++ b/chromeos/audio/audio_device.cc @@ -6,36 +6,71 @@ #include "base/format_macros.h" #include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" #include "base/utf_string_conversions.h" 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 GetDevicePriority(chromeos::AudioDeviceType type) { + switch (type) { + // Fall through. + case chromeos::AUDIO_TYPE_HEADPHONE: + case chromeos::AUDIO_TYPE_MIC: + case chromeos::AUDIO_TYPE_USB: + case chromeos::AUDIO_TYPE_BLUETOOTH: + return 3; + case chromeos::AUDIO_TYPE_HDMI: + return 2; + // Fall through. + case chromeos::AUDIO_TYPE_INTERNAL_SPEAKER: + case chromeos::AUDIO_TYPE_INTERNAL_MIC: + return 1; + // Fall through. + case chromeos::AUDIO_TYPE_OTHER: + default: + return 0; + } +} + std::string GetTypeString(chromeos::AudioDeviceType type) { - if (type == chromeos::AUDIO_TYPE_INTERNAL) - return "INTERNAL"; - else if (type == chromeos::AUDIO_TYPE_HEADPHONE) - return "HEADPHONE"; - else if (type == chromeos::AUDIO_TYPE_USB) - return "USB"; - else if (type == chromeos::AUDIO_TYPE_BLUETOOTH) - return "BLUETOOTH"; - else if (type == chromeos::AUDIO_TYPE_HDMI) - return "HDMI"; - else - return "OTHER"; + switch (type) { + case chromeos::AUDIO_TYPE_HEADPHONE: + return "HEADPHONE"; + case chromeos::AUDIO_TYPE_MIC: + return "MIC"; + case chromeos::AUDIO_TYPE_USB: + return "USB"; + case chromeos::AUDIO_TYPE_BLUETOOTH: + return "BLUETOOTH"; + case chromeos::AUDIO_TYPE_HDMI: + return "HDMI"; + case chromeos::AUDIO_TYPE_INTERNAL_SPEAKER: + return "INTERNAL_SPEAKER"; + case chromeos::AUDIO_TYPE_INTERNAL_MIC: + return "INTERNAL_MIC"; + case chromeos::AUDIO_TYPE_OTHER: + default: + return "OTHER"; + } } chromeos::AudioDeviceType GetAudioType(const std::string& node_type) { - if (node_type.find("INTERNAL_") != std::string::npos) - return chromeos::AUDIO_TYPE_INTERNAL; - else if (node_type.find("HEADPHONE") != std::string::npos) + if (node_type.find("HEADPHONE") != std::string::npos) return chromeos::AUDIO_TYPE_HEADPHONE; + else if (node_type.find("MIC") != std::string::npos) + return chromeos::AUDIO_TYPE_MIC; else if (node_type.find("USB") != std::string::npos) return chromeos::AUDIO_TYPE_USB; else if (node_type.find("BLUETOOTH") != std::string::npos) return chromeos::AUDIO_TYPE_BLUETOOTH; else if (node_type.find("HDMI") != std::string::npos) return chromeos::AUDIO_TYPE_HDMI; + else if (node_type.find("INTERNAL_SPEAKER") != std::string::npos) + return chromeos::AUDIO_TYPE_INTERNAL_SPEAKER; + else if (node_type.find("INTERNAL_MIC") != std::string::npos) + return chromeos::AUDIO_TYPE_INTERNAL_MIC; else return chromeos::AUDIO_TYPE_OTHER; } @@ -47,7 +82,9 @@ namespace chromeos { AudioDevice::AudioDevice() : is_input(false), id(0), - active(false) { + priority(0), + active(false), + plugged_time(0) { } AudioDevice::AudioDevice(const AudioNode& node) { @@ -58,7 +95,9 @@ AudioDevice::AudioDevice(const AudioNode& node) { display_name = UTF8ToUTF16(node.name); else display_name = UTF8ToUTF16(node.device_name); + priority = GetDevicePriority(type); active = node.active; + plugged_time = node.plugged_time; } std::string AudioDevice::ToString() const { @@ -67,8 +106,8 @@ std::string AudioDevice::ToString() const { "is_input = %s ", is_input ? "true" : "false"); base::StringAppendF(&result, - "id = %"PRIu64" ", - id); + "id = %s ", + base::Uint64ToString(id).c_str()); base::StringAppendF(&result, "display_name = %s ", UTF16ToUTF8(display_name).c_str()); @@ -78,6 +117,9 @@ std::string AudioDevice::ToString() const { base::StringAppendF(&result, "active = %s ", active ? "true" : "false"); + base::StringAppendF(&result, + "plugged_time= %s ", + base::Uint64ToString(plugged_time).c_str()); return result; } diff --git a/chromeos/audio/audio_device.h b/chromeos/audio/audio_device.h index 891f030..fbbd3fc 100644 --- a/chromeos/audio/audio_device.h +++ b/chromeos/audio/audio_device.h @@ -15,12 +15,15 @@ namespace chromeos { +// Ordered from the highest priority to the lowest. enum AudioDeviceType { - AUDIO_TYPE_INTERNAL, AUDIO_TYPE_HEADPHONE, + AUDIO_TYPE_MIC, AUDIO_TYPE_USB, AUDIO_TYPE_BLUETOOTH, AUDIO_TYPE_HDMI, + AUDIO_TYPE_INTERNAL_SPEAKER, + AUDIO_TYPE_INTERNAL_MIC, AUDIO_TYPE_OTHER, }; @@ -29,7 +32,9 @@ struct CHROMEOS_EXPORT AudioDevice { uint64 id; base::string16 display_name; AudioDeviceType type; + uint8 priority; bool active; + uint64 plugged_time; AudioDevice(); explicit AudioDevice(const AudioNode& node); @@ -38,6 +43,27 @@ struct CHROMEOS_EXPORT AudioDevice { typedef std::vector<AudioDevice> AudioDeviceList; +struct AudioDeviceCompare { + // Rules used to discern which device is higher, + // 1.) Device Type: + // [Headphones/USB/Bluetooh > HDMI > Internal Speakers] + // [External Mic/USB Mic/Bluetooth > Internal Mic] + // 2.) Device Plugged in Time: + // [Later > Earlier] + bool operator()(const chromeos::AudioDevice& a, + const chromeos::AudioDevice& b) const { + 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; + } + } +}; + } // namespace chromeos #endif // CHROMEOS_AUDIO_AUDIO_DEVICE_H_ diff --git a/chromeos/audio/cras_audio_handler.cc b/chromeos/audio/cras_audio_handler.cc index 96f3a45..b374402 100644 --- a/chromeos/audio/cras_audio_handler.cc +++ b/chromeos/audio/cras_audio_handler.cc @@ -323,11 +323,11 @@ void CrasAudioHandler::HandleGetNodes(const chromeos::AudioNodeList& node_list, audio_devices_.push_back(device); if (!has_alternative_input_ && device.is_input && - device.type != AUDIO_TYPE_INTERNAL) { + device.type != AUDIO_TYPE_INTERNAL_MIC) { has_alternative_input_ = true; } else if (!has_alternative_output_ && !device.is_input && - device.type != AUDIO_TYPE_INTERNAL) { + device.type != AUDIO_TYPE_INTERNAL_SPEAKER) { has_alternative_output_ = true; } } diff --git a/chromeos/audio/cras_audio_switch_handler.cc b/chromeos/audio/cras_audio_switch_handler.cc new file mode 100644 index 0000000..dea7016 --- /dev/null +++ b/chromeos/audio/cras_audio_switch_handler.cc @@ -0,0 +1,161 @@ +// 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_switch_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "chromeos/audio/audio_pref_handler.h" +#include "chromeos/dbus/dbus_thread_manager.h" + +using std::max; +using std::min; + +namespace chromeos { + +namespace { + +static CrasAudioSwitchHandler* g_cras_audio_handler = NULL; + +} // namespace + +// static +void CrasAudioSwitchHandler::Initialize() { + CHECK(!g_cras_audio_handler); + g_cras_audio_handler = new CrasAudioSwitchHandler(); +} + +// static +void CrasAudioSwitchHandler::Shutdown() { + CHECK(g_cras_audio_handler); + delete g_cras_audio_handler; + g_cras_audio_handler = NULL; +} + +// static +CrasAudioSwitchHandler* CrasAudioSwitchHandler::Get() { + CHECK(g_cras_audio_handler) + << "CrasAudioSwitchHandler::Get() called before Initialize()."; + return g_cras_audio_handler; +} + +CrasAudioSwitchHandler::CrasAudioSwitchHandler() + : muted_device_id_(0), + weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->AddObserver(this); + GetNodes(); +} + +CrasAudioSwitchHandler::~CrasAudioSwitchHandler() { + chromeos::DBusThreadManager::Get()->GetCrasAudioClient()-> + RemoveObserver(this); +} + +void CrasAudioSwitchHandler::AudioClientRestarted() { + // Get the initial audio data. + GetNodes(); +} + +void CrasAudioSwitchHandler::NodesChanged() { + // Refresh audio nodes data. + GetNodes(); +} + +void CrasAudioSwitchHandler::ActiveOutputNodeChanged(uint64 node_id) { + if (muted_device_id_ == node_id) { + LOG(WARNING) << "Active output device switched to: " << node_id; + muted_device_id_ = 0; + DBusThreadManager::Get()->GetCrasAudioClient()->SetOutputMute(false); + } +} + +void CrasAudioSwitchHandler::ActiveInputNodeChanged(uint64 node_id) { + if (muted_device_id_ == node_id) { + LOG(WARNING) << "Active input device switched to: " << node_id; + muted_device_id_ = 0; + DBusThreadManager::Get()->GetCrasAudioClient()->SetInputMute(false); + } +} + +void CrasAudioSwitchHandler::GetNodes() { + chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->GetNodes( + base::Bind(&CrasAudioSwitchHandler::HandleGetNodes, + weak_ptr_factory_.GetWeakPtr())); +} + +void CrasAudioSwitchHandler::SwitchToDevice(const AudioDevice& device) { + // Mute -> Switch active device -> (on active device changed event) Unmute. + muted_device_id_ = device.id; + LOG(WARNING) << "Switching active device to: " << device.ToString(); + if (device.is_input) { + DBusThreadManager::Get()->GetCrasAudioClient()->SetInputMute(true); + DBusThreadManager::Get()->GetCrasAudioClient()->SetActiveInputNode( + device.id); + } else { + DBusThreadManager::Get()->GetCrasAudioClient()->SetOutputMute(true); + DBusThreadManager::Get()->GetCrasAudioClient()->SetActiveOutputNode( + device.id); + } +} + +void CrasAudioSwitchHandler::UpdateDevicesAndSwitch( + const AudioNodeList& nodes) { + bool input_device_removed = false; + bool output_device_removed = false; + bool have_active_input_device = false; + bool have_active_output_device = false; + + size_t num_previous_input_devices = input_devices_.size(); + size_t num_previous_output_devices = output_devices_.size(); + + while (!input_devices_.empty()) + input_devices_.pop(); + while (!output_devices_.empty()) + output_devices_.pop(); + + for (size_t i = 0; i < nodes.size(); ++i) { + AudioDevice dev(nodes[i]); + if (dev.is_input) + input_devices_.push(dev); + else + output_devices_.push(dev); + + if (dev.is_input && dev.active) + have_active_input_device = true; + if (!dev.is_input && dev.active) + have_active_output_device = true; + } + + if (num_previous_input_devices > input_devices_.size()) + input_device_removed = true; + if (num_previous_output_devices > output_devices_.size()) + output_device_removed = true; + + // If either, + // .) the top input/output device is already active, or, + // .) an input/output device was removed but not the active device, + // then we don't need to switch the device, otherwise we do need to switch. + if (!(input_devices_.top().active || + (input_device_removed && have_active_input_device))) { + SwitchToDevice(input_devices_.top()); + } + + if (!(output_devices_.top().active || + (output_device_removed && have_active_output_device))) { + SwitchToDevice(output_devices_.top()); + } +} + +void CrasAudioSwitchHandler::HandleGetNodes( + const AudioNodeList& node_list, bool success) { + if (!success) { + LOG(ERROR) << "Failed to retrieve audio nodes data"; + return; + } + + UpdateDevicesAndSwitch(node_list); +} + +} // namespace chromeos diff --git a/chromeos/audio/cras_audio_switch_handler.h b/chromeos/audio/cras_audio_switch_handler.h new file mode 100644 index 0000000..a31ddf3d --- /dev/null +++ b/chromeos/audio/cras_audio_switch_handler.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef CHROMEOS_AUDIO_CRAS_AUDIO_SWITCH_HANDLER_H_ +#define CHROMEOS_AUDIO_CRAS_AUDIO_SWITCH_HANDLER_H_ + +#include <queue> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "chromeos/audio/audio_device.h" +#include "chromeos/dbus/audio_node.h" +#include "chromeos/dbus/cras_audio_client.h" + +class PrefRegistrySimple; +class PrefService; + +namespace chromeos { + +class AudioPrefHandler; + +typedef std::priority_queue<AudioDevice, + std::vector<AudioDevice>, + AudioDeviceCompare> AudioDevicePriorityQueue; + +// Class which handles switching between audio devices when a new device is +// plugged in or an active device is plugged out. +class CHROMEOS_EXPORT CrasAudioSwitchHandler + : public CrasAudioClient::Observer { + public: + // Sets the global instance. Must be called before any calls to Get(). + static void Initialize(); + + // Destroys the global instance. + static void Shutdown(); + + // Gets the global instance. Initialize must be called first. + static CrasAudioSwitchHandler* Get(); + + private: + explicit CrasAudioSwitchHandler(); + virtual ~CrasAudioSwitchHandler(); + + // Overriden from CrasAudioSwitchHandler::Observer. + virtual void AudioClientRestarted() OVERRIDE; + virtual void NodesChanged() OVERRIDE; + virtual void ActiveOutputNodeChanged(uint64 node_id) OVERRIDE; + virtual void ActiveInputNodeChanged(uint64 node_id) OVERRIDE; + + // Sets up the initial audio device state based on audio policy and + // audio settings saved in prefs. + void SetupInitialAudioState(); + + // Calling dbus to get nodes data. + void GetNodes(); + + // Updates the current audio nodes list and switches our active device + // if needed. + void UpdateDevicesAndSwitch(const AudioNodeList& nodes); + + void SwitchToDevice(const AudioDevice& device); + + // Handles dbus callback for GetNodes. + void HandleGetNodes(const chromeos::AudioNodeList& node_list, bool success); + + // Audio data and state. + AudioDevicePriorityQueue input_devices_; + AudioDevicePriorityQueue output_devices_; + uint64 muted_device_id_; + + base::WeakPtrFactory<CrasAudioSwitchHandler> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(CrasAudioSwitchHandler); +}; + +} // namespace chromeos + +#endif // CHROMEOS_AUDIO_CRAS_AUDIO_SWITCH_HANDLER_H_ |