// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/common/media_pipeline_backend_manager.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/chromecast_buildflags.h"
#include "chromecast/media/api/cma_backend.h"
#include "chromecast/media/common/audio_decoder_wrapper.h"
#include "chromecast/media/common/media_pipeline_backend_wrapper.h"
#include "chromecast/public/volume_control.h"
#define RUN_ON_MEDIA_THREAD(method, ...) \
media_task_runner_->PostTask( \
FROM_HERE, base::BindOnce(&MediaPipelineBackendManager::method, \
weak_factory_.GetWeakPtr(), ##__VA_ARGS__));
#define MAKE_SURE_MEDIA_THREAD(method, ...) \
if (!media_task_runner_->BelongsToCurrentThread()) { \
RUN_ON_MEDIA_THREAD(method, ##__VA_ARGS__) \
return; \
}
namespace chromecast {
namespace media {
namespace {
constexpr int kAudioDecoderLimit = std::numeric_limits<int>::max();
constexpr int kVideoDecoderLimit = 1;
constexpr base::TimeDelta kPowerSaveWaitTime = base::Seconds(5);
} // namespace
MediaPipelineBackendManager::MediaPipelineBackendManager(
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
MediaResourceTracker* media_resource_tracker)
: media_task_runner_(std::move(media_task_runner)),
media_resource_tracker_(media_resource_tracker),
playing_audio_streams_count_({{AudioContentType::kMedia, 0},
{AudioContentType::kAlarm, 0},
{AudioContentType::kCommunication, 0},
{AudioContentType::kOther, 0}}),
playing_noneffects_audio_streams_count_(
{{AudioContentType::kMedia, 0},
{AudioContentType::kAlarm, 0},
{AudioContentType::kCommunication, 0},
{AudioContentType::kOther, 0}}),
active_audio_stream_observers_(
base::MakeRefCounted<
base::ObserverListThreadSafe<ActiveAudioStreamObserver>>()),
backend_wrapper_using_video_decoder_(nullptr),
buffer_delegate_(nullptr),
weak_factory_(this) {
DCHECK(media_task_runner_);
DCHECK_EQ(playing_audio_streams_count_.size(),
static_cast<size_t>(AudioContentType::kNumTypes));
DCHECK_EQ(playing_noneffects_audio_streams_count_.size(),
static_cast<size_t>(AudioContentType::kNumTypes));
for (int i = 0; i < NUM_DECODER_TYPES; ++i) {
decoder_count_[i] = 0;
}
RUN_ON_MEDIA_THREAD(CreateMixerConnection);
}
MediaPipelineBackendManager::~MediaPipelineBackendManager() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
}
std::unique_ptr<CmaBackend> MediaPipelineBackendManager::CreateBackend(
const media::MediaPipelineDeviceParams& params) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
return std::make_unique<MediaPipelineBackendWrapper>(params, this,
media_resource_tracker_);
}
scoped_refptr<base::SequencedTaskRunner>
MediaPipelineBackendManager::GetMediaTaskRunner() {
return media_task_runner_;
}
void MediaPipelineBackendManager::BackendDestroyed(
MediaPipelineBackendWrapper* backend_wrapper) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (backend_wrapper_using_video_decoder_ == backend_wrapper) {
backend_wrapper_using_video_decoder_ = nullptr;
}
}
void MediaPipelineBackendManager::BackendUseVideoDecoder(
MediaPipelineBackendWrapper* backend_wrapper) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(backend_wrapper);
if (backend_wrapper_using_video_decoder_ &&
backend_wrapper_using_video_decoder_ != backend_wrapper) {
LOG(INFO) << __func__ << " revoke old backend : "
<< backend_wrapper_using_video_decoder_;
backend_wrapper_using_video_decoder_->Revoke();
}
backend_wrapper_using_video_decoder_ = backend_wrapper;
}
bool MediaPipelineBackendManager::IncrementDecoderCount(DecoderType type) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(type < NUM_DECODER_TYPES);
const int limit =
(type == VIDEO_DECODER) ? kVideoDecoderLimit : kAudioDecoderLimit;
if (decoder_count_[type] >= limit) {
LOG(WARNING) << "Decoder limit reached for type " << type;
return false;
}
++decoder_count_[type];
return true;
}
void MediaPipelineBackendManager::DecrementDecoderCount(DecoderType type) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(type < NUM_DECODER_TYPES);
DCHECK_GT(decoder_count_[type], 0);
decoder_count_[type]--;
}
void MediaPipelineBackendManager::UpdatePlayingAudioCount(
bool sfx,
const AudioContentType type,
int change) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(change == -1 || change == 1) << "bad count change: " << change;
bool had_playing_audio_streams = (TotalPlayingAudioStreamsCount() > 0);
bool had_playing_primary_streams =
(TotalPlayingNoneffectsAudioStreamsCount() > 0);
playing_audio_streams_count_[type] += change;
DCHECK_GE(playing_audio_streams_count_[type], 0);
if (!sfx) {
playing_noneffects_audio_streams_count_[type] += change;
DCHECK_GE(playing_noneffects_audio_streams_count_[type], 0);
}
HandlePlayingAudioStreamsChange(had_playing_audio_streams,
had_playing_primary_streams);
}
void MediaPipelineBackendManager::OnMixerStreamCountChange(int primary_streams,
int sfx_streams) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
bool had_playing_audio_streams = (TotalPlayingAudioStreamsCount() > 0);
bool had_playing_primary_streams =
(TotalPlayingNoneffectsAudioStreamsCount() > 0);
mixer_primary_stream_count_ = primary_streams;
mixer_sfx_stream_count_ = sfx_streams;
HandlePlayingAudioStreamsChange(had_playing_audio_streams,
had_playing_primary_streams);
}
void MediaPipelineBackendManager::HandlePlayingAudioStreamsChange(
bool had_playing_audio_streams,
bool had_playing_primary_streams) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
int new_playing_audio_streams = TotalPlayingAudioStreamsCount();
if (new_playing_audio_streams == 0) {
power_save_timer_.Start(FROM_HERE, kPowerSaveWaitTime, this,
&MediaPipelineBackendManager::EnterPowerSaveMode);
} else if (!had_playing_audio_streams && new_playing_audio_streams > 0) {
if (power_save_timer_.IsRunning()) {
metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
"Cast.Platform.VolumeControl.PowerSaveTimerCancelled");
}
power_save_timer_.Stop();
if (VolumeControl::SetPowerSaveMode) {
metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
"Cast.Platform.VolumeControl.PowerSaveOff");
VolumeControl::SetPowerSaveMode(false);
}
}
bool new_playing_primary_streams =
(TotalPlayingNoneffectsAudioStreamsCount() > 0);
if (new_playing_primary_streams != had_playing_primary_streams) {
active_audio_stream_observers_->Notify(
FROM_HERE, &ActiveAudioStreamObserver::OnActiveAudioStreamChange,
new_playing_primary_streams);
}
}
int MediaPipelineBackendManager::TotalPlayingAudioStreamsCount() {
int total = 0;
for (auto entry : playing_audio_streams_count_) {
total += entry.second;
}
return std::max(total, mixer_primary_stream_count_ + mixer_sfx_stream_count_);
}
int MediaPipelineBackendManager::TotalPlayingNoneffectsAudioStreamsCount() {
int total = 0;
for (auto entry : playing_noneffects_audio_streams_count_) {
total += entry.second;
}
return std::max(total, mixer_primary_stream_count_);
}
void MediaPipelineBackendManager::EnterPowerSaveMode() {
DCHECK_EQ(TotalPlayingAudioStreamsCount(), 0);
if (!VolumeControl::SetPowerSaveMode || !power_save_enabled_) {
return;
}
metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
"Cast.Platform.VolumeControl.PowerSaveOn");
VolumeControl::SetPowerSaveMode(true);
}
void MediaPipelineBackendManager::AddActiveAudioStreamObserver(
ActiveAudioStreamObserver* observer) {
active_audio_stream_observers_->AddObserver(observer);
}
void MediaPipelineBackendManager::RemoveActiveAudioStreamObserver(
ActiveAudioStreamObserver* observer) {
active_audio_stream_observers_->RemoveObserver(observer);
}
void MediaPipelineBackendManager::AddExtraPlayingStream(
bool sfx,
const AudioContentType type) {
MAKE_SURE_MEDIA_THREAD(AddExtraPlayingStream, sfx, type);
UpdatePlayingAudioCount(sfx, type, 1);
}
void MediaPipelineBackendManager::RemoveExtraPlayingStream(
bool sfx,
const AudioContentType type) {
MAKE_SURE_MEDIA_THREAD(RemoveExtraPlayingStream, sfx, type);
UpdatePlayingAudioCount(sfx, type, -1);
}
void MediaPipelineBackendManager::SetBufferDelegate(
BufferDelegate* buffer_delegate) {
MAKE_SURE_MEDIA_THREAD(SetBufferDelegate, buffer_delegate);
DCHECK(buffer_delegate);
DCHECK(!buffer_delegate_);
buffer_delegate_ = buffer_delegate;
}
void MediaPipelineBackendManager::SetPowerSaveEnabled(bool power_save_enabled) {
MAKE_SURE_MEDIA_THREAD(SetPowerSaveEnabled, power_save_enabled);
power_save_enabled_ = power_save_enabled;
if (!VolumeControl::SetPowerSaveMode) {
return;
}
if (!power_save_enabled_) {
VolumeControl::SetPowerSaveMode(false);
} else if (!power_save_timer_.IsRunning() &&
TotalPlayingAudioStreamsCount() == 0) {
EnterPowerSaveMode();
}
}
void MediaPipelineBackendManager::TemporaryDisablePowerSave() {
MAKE_SURE_MEDIA_THREAD(TemporaryDisablePowerSave);
int playing_audio_streams = TotalPlayingAudioStreamsCount();
if (playing_audio_streams == 0) {
if (VolumeControl::SetPowerSaveMode) {
LOG(INFO) << "Temporarily disable power save";
VolumeControl::SetPowerSaveMode(false);
power_save_timer_.Start(FROM_HERE, kPowerSaveWaitTime, this,
&MediaPipelineBackendManager::EnterPowerSaveMode);
}
}
}
} // namespace media
} // namespace chromecast