Commit bfcb7d68 authored by Ken MacKay's avatar Ken MacKay Committed by Commit Bot

[Chromecast] Add support for power-save mode

After no streams have been playing out audio for a few seconds, trigger
power-save mode. Note that paused streams do not count as playing out
for this purpose.

Bug: internal b/64462358
Change-Id: I6ed4910490705d39e499b5f5e63432f8534993ff
Reviewed-on: https://chromium-review.googlesource.com/807749
Commit-Queue: Kenneth MacKay <kmackay@chromium.org>
Reviewed-by: default avatarLuke Halliwell <halliwell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#522030}
parent 3fc597bc
......@@ -89,6 +89,14 @@ const char kAlsaMuteElementName[] = "alsa-mute-element-name";
// specified it will default to the same device as kAlsaVolumeDeviceName.
const char kAlsaMuteDeviceName[] = "alsa-mute-device-name";
// Name of the simple mixer control element that the ALSA-based media library
// should use to toggle powersave mode on the system.
const char kAlsaAmpElementName[] = "alsa-amp-element-name";
// Name of the device the amp mixer should be opened on. If this flag is not
// specified it will default to the same device as kAlsaVolumeDeviceName.
const char kAlsaAmpDeviceName[] = "alsa-amp-device-name";
// Calibrated max output volume dBa for voice content at 1 meter, if known.
const char kMaxOutputVolumeDba1m[] = "max-output-volume-dba1m";
......
......@@ -52,6 +52,8 @@ extern const char kAlsaVolumeDeviceName[];
extern const char kAlsaVolumeElementName[];
extern const char kAlsaMuteDeviceName[];
extern const char kAlsaMuteElementName[];
extern const char kAlsaAmpDeviceName[];
extern const char kAlsaAmpElementName[];
extern const char kMaxOutputVolumeDba1m[];
extern const char kAudioOutputChannels[];
extern const char kAudioOutputSampleRate[];
......
......@@ -37,6 +37,7 @@ cast_source_set("backend") {
deps = [
"//base",
"//chromecast:chromecast_features",
"//chromecast/base/metrics:metrics",
"//chromecast/media:libcast_media",
"//chromecast/media/cma/base",
]
......
......@@ -11,7 +11,6 @@
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "chromecast/base/chromecast_switches.h"
#include "media/base/media_switches.h"
......@@ -168,9 +167,34 @@ std::string AlsaVolumeControl::GetMuteDeviceName() {
return GetVolumeDeviceName();
}
// static
std::string AlsaVolumeControl::GetAmpElementName() {
std::string mixer_element_name =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAlsaAmpElementName);
if (!mixer_element_name.empty()) {
return mixer_element_name;
}
return std::string();
}
// static
std::string AlsaVolumeControl::GetAmpDeviceName() {
auto* command_line = base::CommandLine::ForCurrentProcess();
std::string mixer_device_name =
command_line->GetSwitchValueASCII(switches::kAlsaAmpDeviceName);
if (!mixer_device_name.empty()) {
return mixer_device_name;
}
// If the amp mixer device was not specified directly, use the same device as
// the volume mixer.
return GetVolumeDeviceName();
}
AlsaVolumeControl::AlsaVolumeControl(Delegate* delegate)
: delegate_(delegate),
alsa_(base::MakeUnique<::media::AlsaWrapper>()),
alsa_(std::make_unique<::media::AlsaWrapper>()),
volume_mixer_device_name_(GetVolumeDeviceName()),
volume_mixer_element_name_(GetVolumeElementName()),
mute_mixer_device_name_(GetMuteDeviceName()),
......@@ -178,6 +202,8 @@ AlsaVolumeControl::AlsaVolumeControl(Delegate* delegate)
volume_mixer_device_name_,
volume_mixer_element_name_,
mute_mixer_device_name_)),
amp_mixer_device_name_(GetAmpDeviceName()),
amp_mixer_element_name_(GetAmpElementName()),
volume_range_min_(0),
volume_range_max_(0),
mute_mixer_ptr_(nullptr) {
......@@ -186,8 +212,10 @@ AlsaVolumeControl::AlsaVolumeControl(Delegate* delegate)
<< ", element = " << volume_mixer_element_name_;
VLOG(1) << "Mute device = " << mute_mixer_device_name_
<< ", element = " << mute_mixer_element_name_;
VLOG(1) << "Idle device = " << amp_mixer_device_name_
<< ", element = " << amp_mixer_element_name_;
volume_mixer_ = base::MakeUnique<ScopedAlsaMixer>(
volume_mixer_ = std::make_unique<ScopedAlsaMixer>(
alsa_.get(), volume_mixer_device_name_, volume_mixer_element_name_);
if (volume_mixer_->element) {
ALSA_ASSERT(MixerSelemGetPlaybackVolumeRange, volume_mixer_->element,
......@@ -200,7 +228,7 @@ AlsaVolumeControl::AlsaVolumeControl(Delegate* delegate)
}
if (mute_mixer_element_name_ != volume_mixer_element_name_) {
mute_mixer_ = base::MakeUnique<ScopedAlsaMixer>(
mute_mixer_ = std::make_unique<ScopedAlsaMixer>(
alsa_.get(), mute_mixer_device_name_, mute_mixer_element_name_);
if (mute_mixer_->element) {
mute_mixer_ptr_ = mute_mixer_.get();
......@@ -214,6 +242,14 @@ AlsaVolumeControl::AlsaVolumeControl(Delegate* delegate)
} else {
mute_mixer_ptr_ = volume_mixer_.get();
}
if (!amp_mixer_element_name_.empty()) {
amp_mixer_ = std::make_unique<ScopedAlsaMixer>(
alsa_.get(), amp_mixer_device_name_, amp_mixer_element_name_);
if (amp_mixer_->element) {
RefreshMixerFds(amp_mixer_.get());
}
}
}
AlsaVolumeControl::~AlsaVolumeControl() = default;
......@@ -271,16 +307,28 @@ bool AlsaVolumeControl::IsMuted() {
}
void AlsaVolumeControl::SetMuted(bool muted) {
if (!mute_mixer_ptr_->element ||
!alsa_->MixerSelemHasPlaybackSwitch(mute_mixer_ptr_->element)) {
if (!SetElementMuted(mute_mixer_ptr_, muted)) {
LOG(ERROR) << "Mute failed: no mute switch on mixer element.";
return;
}
}
void AlsaVolumeControl::SetPowerSave(bool power_save_on) {
if (!SetElementMuted(amp_mixer_.get(), power_save_on)) {
LOG(INFO) << "Amp toggle failed: no amp switch on mixer element.";
}
}
bool AlsaVolumeControl::SetElementMuted(ScopedAlsaMixer* mixer, bool muted) {
if (!mixer || !mixer->element ||
!alsa_->MixerSelemHasPlaybackSwitch(mixer->element)) {
return false;
}
for (int32_t channel = 0; channel <= SND_MIXER_SCHN_LAST; ++channel) {
alsa_->MixerSelemSetPlaybackSwitch(
mute_mixer_ptr_->element,
static_cast<snd_mixer_selem_channel_id_t>(channel), !muted);
mixer->element, static_cast<snd_mixer_selem_channel_id_t>(channel),
!muted);
}
return true;
}
void AlsaVolumeControl::RefreshMixerFds(ScopedAlsaMixer* mixer) {
......@@ -291,7 +339,7 @@ void AlsaVolumeControl::RefreshMixerFds(ScopedAlsaMixer* mixer) {
DCHECK_GT(num_fds, 0);
for (int i = 0; i < num_fds; ++i) {
auto watcher =
base::MakeUnique<base::MessageLoopForIO::FileDescriptorWatcher>(
std::make_unique<base::MessageLoopForIO::FileDescriptorWatcher>(
FROM_HERE);
base::MessageLoopForIO::current()->WatchFileDescriptor(
pfds[i].fd, true /* persistent */, base::MessageLoopForIO::WATCH_READ,
......@@ -305,6 +353,10 @@ void AlsaVolumeControl::OnFileCanReadWithoutBlocking(int fd) {
if (mute_mixer_) {
alsa_->MixerHandleEvents(mute_mixer_->mixer);
}
if (amp_mixer_) {
// amixer locks up if we don't call this for unknown reasons.
alsa_->MixerHandleEvents(amp_mixer_->mixer);
}
}
void AlsaVolumeControl::OnFileCanWriteWithoutBlocking(int fd) {
......
......@@ -30,6 +30,7 @@ class AlsaVolumeControl : public SystemVolumeControl,
void SetVolume(float level) override;
bool IsMuted() override;
void SetMuted(bool muted) override;
void SetPowerSave(bool power_save_on) override;
private:
class ScopedAlsaMixer;
......@@ -41,10 +42,14 @@ class AlsaVolumeControl : public SystemVolumeControl,
const std::string& mixer_element_name,
const std::string& mute_card_name);
static std::string GetMuteDeviceName();
static std::string GetAmpElementName();
static std::string GetAmpDeviceName();
static int VolumeOrMuteChangeCallback(snd_mixer_elem_t* elem,
unsigned int mask);
bool SetElementMuted(ScopedAlsaMixer* mixer, bool muted);
void RefreshMixerFds(ScopedAlsaMixer* mixer);
// base::MessageLoopForIO::Watcher implementation:
......@@ -52,6 +57,7 @@ class AlsaVolumeControl : public SystemVolumeControl,
void OnFileCanWriteWithoutBlocking(int fd) override;
void OnVolumeOrMuteChanged();
Delegate* const delegate_;
const std::unique_ptr<::media::AlsaWrapper> alsa_;
......@@ -59,6 +65,8 @@ class AlsaVolumeControl : public SystemVolumeControl,
const std::string volume_mixer_element_name_;
const std::string mute_mixer_device_name_;
const std::string mute_mixer_element_name_;
const std::string amp_mixer_device_name_;
const std::string amp_mixer_element_name_;
long volume_range_min_; // NOLINT(runtime/int)
long volume_range_max_; // NOLINT(runtime/int)
......@@ -66,6 +74,7 @@ class AlsaVolumeControl : public SystemVolumeControl,
std::unique_ptr<ScopedAlsaMixer> volume_mixer_;
std::unique_ptr<ScopedAlsaMixer> mute_mixer_;
ScopedAlsaMixer* mute_mixer_ptr_;
std::unique_ptr<ScopedAlsaMixer> amp_mixer_;
std::vector<std::unique_ptr<base::MessageLoopForIO::FileDescriptorWatcher>>
file_descriptor_watchers_;
......
......@@ -41,5 +41,9 @@ void FuchsiaVolumeControl::SetMuted(bool muted) {
NOTIMPLEMENTED();
}
void FuchsiaVolumeControl::SetPowerSave(bool power_save_on) {
NOTIMPLEMENTED();
}
} // namespace media
} // namespace chromecast
......@@ -24,6 +24,7 @@ class FuchsiaVolumeControl : public SystemVolumeControl {
void SetVolume(float level) override;
bool IsMuted() override;
void SetMuted(bool muted) override;
void SetPowerSave(bool power_save_on) override;
private:
DISALLOW_COPY_AND_ASSIGN(FuchsiaVolumeControl);
......
......@@ -10,6 +10,8 @@
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/chromecast_features.h"
#include "chromecast/media/cma/backend/audio_decoder_wrapper.h"
#include "chromecast/media/cma/backend/media_pipeline_backend_wrapper.h"
......@@ -28,17 +30,23 @@
namespace chromecast {
namespace media {
namespace {
#if BUILDFLAG(IS_CAST_AUDIO_ONLY)
constexpr int kAudioDecoderLimit = std::numeric_limits<int>::max();
#else
constexpr int kAudioDecoderLimit = 1;
#endif
constexpr base::TimeDelta kPowerSaveWaitTime = base::TimeDelta::FromSeconds(5);
} // namespace
MediaPipelineBackendManager::MediaPipelineBackendManager(
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner)
: media_task_runner_(std::move(media_task_runner)),
playing_audio_streams_count_(0),
playing_noneffects_audio_streams_count_(0),
allow_volume_feedback_observers_(
new base::ObserverListThreadSafe<AllowVolumeFeedbackObserver>()),
......@@ -86,9 +94,29 @@ void MediaPipelineBackendManager::DecrementDecoderCount(DecoderType type) {
decoder_count_[type]--;
}
void MediaPipelineBackendManager::UpdatePlayingAudioCount(int change) {
void MediaPipelineBackendManager::UpdatePlayingAudioCount(bool sfx,
int change) {
DCHECK(change == -1 || change == 1) << "bad count change: " << change;
bool had_playing_audio_streams = (playing_audio_streams_count_ > 0);
playing_audio_streams_count_ += change;
DCHECK_GE(playing_audio_streams_count_, 0);
if (VolumeControl::SetPowerSaveMode) {
if (playing_audio_streams_count_ == 0) {
power_save_timer_.Start(FROM_HERE, kPowerSaveWaitTime, this,
&MediaPipelineBackendManager::EnterPowerSaveMode);
} else if (!had_playing_audio_streams && playing_audio_streams_count_ > 0) {
power_save_timer_.Stop();
metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
"Cast.Platform.VolumeControl.PowerSaveOff");
VolumeControl::SetPowerSaveMode(false);
}
}
if (sfx) {
return;
}
// Volume feedback sounds are only allowed when there are no non-effects
// audio streams playing.
bool prev_allow_feedback = (playing_noneffects_audio_streams_count_ == 0);
......@@ -103,6 +131,14 @@ void MediaPipelineBackendManager::UpdatePlayingAudioCount(int change) {
}
}
void MediaPipelineBackendManager::EnterPowerSaveMode() {
DCHECK_EQ(playing_audio_streams_count_, 0);
DCHECK(VolumeControl::SetPowerSaveMode);
metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
"Cast.Platform.VolumeControl.PowerSaveOn");
VolumeControl::SetPowerSaveMode(true);
}
void MediaPipelineBackendManager::AddAllowVolumeFeedbackObserver(
AllowVolumeFeedbackObserver* observer) {
allow_volume_feedback_observers_->AddObserver(observer);
......
......@@ -16,6 +16,7 @@
#include "base/memory/weak_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/single_thread_task_runner.h"
#include "base/timer/timer.h"
#include "chromecast/public/media/media_pipeline_backend.h"
#include "chromecast/public/media/media_pipeline_device_params.h"
......@@ -129,13 +130,18 @@ class MediaPipelineBackendManager {
void DecrementDecoderCount(DecoderType type);
// Update the count of playing non-effects audio streams.
void UpdatePlayingAudioCount(int change);
void UpdatePlayingAudioCount(bool sfx, int change);
void EnterPowerSaveMode();
const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
// Total count of decoders created
int decoder_count_[NUM_DECODER_TYPES];
// Total number of playing audio streams.
int playing_audio_streams_count_;
// Total number of playing non-effects streams.
int playing_noneffects_audio_streams_count_;
......@@ -147,6 +153,8 @@ class MediaPipelineBackendManager {
BufferDelegate* buffer_delegate_;
base::OneShotTimer power_save_timer_;
base::WeakPtrFactory<MediaPipelineBackendManager> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaPipelineBackendManager);
......
......@@ -39,8 +39,8 @@ MediaPipelineBackendWrapper::~MediaPipelineBackendWrapper() {
if (playing_) {
LOG(WARNING) << "Destroying media backend while still in 'playing' state";
if (audio_decoder_ && !IsSfx()) {
backend_manager_->UpdatePlayingAudioCount(-1);
if (audio_decoder_) {
backend_manager_->UpdatePlayingAudioCount(IsSfx(), -1);
}
}
}
......@@ -136,8 +136,8 @@ void MediaPipelineBackendWrapper::SetPlaying(bool playing) {
return;
}
playing_ = playing;
if (audio_decoder_ && !IsSfx()) {
backend_manager_->UpdatePlayingAudioCount(playing_ ? 1 : -1);
if (audio_decoder_) {
backend_manager_->UpdatePlayingAudioCount(IsSfx(), (playing_ ? 1 : -1));
}
}
......
......@@ -53,6 +53,9 @@ class SystemVolumeControl {
// Sets the system mute state to |muted|.
virtual void SetMuted(bool muted) = 0;
// Sets the system power save state to |power_save_on|.
virtual void SetPowerSave(bool power_save_on) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(SystemVolumeControl);
};
......
......@@ -125,8 +125,8 @@ class VolumeControlInternal : public SystemVolumeControl::Delegate {
thread_.StartWithOptions(options);
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&VolumeControlInternal::InitializeOnThread,
base::Unretained(this)));
FROM_HERE, base::BindOnce(&VolumeControlInternal::InitializeOnThread,
base::Unretained(this)));
initialize_complete_event_.Wait();
}
......@@ -152,9 +152,9 @@ class VolumeControlInternal : public SystemVolumeControl::Delegate {
void SetVolume(AudioContentType type, float level) {
level = std::max(0.0f, std::min(level, 1.0f));
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&VolumeControlInternal::SetVolumeOnThread,
base::Unretained(this), type, level,
false /* from_system */));
FROM_HERE, base::BindOnce(&VolumeControlInternal::SetVolumeOnThread,
base::Unretained(this), type, level,
false /* from_system */));
}
bool IsMuted(AudioContentType type) {
......@@ -164,9 +164,9 @@ class VolumeControlInternal : public SystemVolumeControl::Delegate {
void SetMuted(AudioContentType type, bool muted) {
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&VolumeControlInternal::SetMutedOnThread,
base::Unretained(this), type, muted,
false /* from_system */));
FROM_HERE, base::BindOnce(&VolumeControlInternal::SetMutedOnThread,
base::Unretained(this), type, muted,
false /* from_system */));
}
void SetOutputLimit(AudioContentType type, float limit) {
......@@ -178,6 +178,13 @@ class VolumeControlInternal : public SystemVolumeControl::Delegate {
type, DbFsToScale(VolumeControl::VolumeToDbFS(limit)));
}
void SetPowerSaveMode(bool power_save_on) {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VolumeControlInternal::SetPowerSaveModeOnThread,
base::Unretained(this), power_save_on));
}
private:
void InitializeOnThread() {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
......@@ -276,8 +283,15 @@ class VolumeControlInternal : public SystemVolumeControl::Delegate {
}
}
void SetPowerSaveModeOnThread(bool power_save_on) {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
system_volume_control_->SetPowerSave(power_save_on);
}
// SystemVolumeControl::Delegate implementation:
void OnSystemVolumeOrMuteChange(float new_volume, bool new_mute) override {
LOG(INFO) << "System volume/mute change, new volume = " << new_volume
<< ", new mute = " << new_mute;
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
SetVolumeOnThread(AudioContentType::kMedia, new_volume,
true /* from_system */);
......@@ -364,5 +378,10 @@ float VolumeControl::DbFSToVolume(float db) {
return g_volume_map.Get().DbFSToVolume(db);
}
// static
void VolumeControl::SetPowerSaveMode(bool power_save_on) {
g_volume_control.Get().SetPowerSaveMode(power_save_on);
}
} // namespace media
} // namespace chromecast
......@@ -87,6 +87,11 @@ class CHROMECAST_EXPORT VolumeControl {
// May be called from multiple processes.
static float VolumeToDbFS(float volume) __attribute__((__weak__));
static float DbFSToVolume(float dbfs) __attribute__((__weak__));
// Called to enable power save mode when no audio is being played
// (|power_save_on| will be true in this case), and to disable power save mode
// when audio playback resumes (|power_save_on| will be false).
static void SetPowerSaveMode(bool power_save_on) __attribute__((__weak__));
};
} // namespace media
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment