[go: nahoru, domu]

blob: bd0a5255a3e4e946529db1bbc93b10ce69557681 [file] [log] [blame]
// Copyright 2020 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 "chromecast/media/cma/backend/proxy/media_pipeline_buffer_extension.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/time/time.h"
#include "chromecast/media/api/decoder_buffer_base.h"
#include "chromecast/public/media/decoder_config.h"
#include "chromecast/public/media/media_pipeline_device_params.h"
#include "chromecast/public/task_runner.h"
namespace chromecast {
namespace media {
namespace {
// TODO(b/180725465): Make this a BUILD flag for embedders to specify.
constexpr int kMicrosecondsDataToBuffer =
5 * base::Time::kMicrosecondsPerSecond;
} // namespace
MediaPipelineBufferExtension::PendingCommand::PendingCommand(
scoped_refptr<DecoderBufferBase> buf)
: buffer(std::move(buf)) {}
MediaPipelineBufferExtension::PendingCommand::PendingCommand(
const AudioConfig& cfg)
: config(cfg) {}
MediaPipelineBufferExtension::PendingCommand::PendingCommand(
const PendingCommand& other) = default;
MediaPipelineBufferExtension::PendingCommand::PendingCommand(
PendingCommand&& other) = default;
MediaPipelineBufferExtension::PendingCommand::~PendingCommand() = default;
MediaPipelineBufferExtension::PendingCommand&
MediaPipelineBufferExtension::PendingCommand::operator=(
const PendingCommand& other) = default;
MediaPipelineBufferExtension::PendingCommand&
MediaPipelineBufferExtension::PendingCommand::operator=(
PendingCommand&& other) = default;
MediaPipelineBufferExtension::MediaPipelineBufferExtension(
TaskRunner* task_runner,
CmaBackend::AudioDecoder* delegated_decoder)
: AudioDecoderPipelineNode(delegated_decoder),
task_runner_(task_runner),
weak_factory_(this) {
DCHECK(task_runner_);
}
MediaPipelineBufferExtension::MediaPipelineBufferExtension(
TaskRunner* task_runner,
std::unique_ptr<AudioDecoderPipelineNode> owned_delegated_decoder)
: MediaPipelineBufferExtension(task_runner, owned_delegated_decoder.get()) {
SetOwnedDecoder(std::move(owned_delegated_decoder));
}
MediaPipelineBufferExtension::~MediaPipelineBufferExtension() = default;
CmaBackend::BufferStatus
MediaPipelineBufferExtension::PushBufferToDelegatedDecoder(
scoped_refptr<DecoderBufferBase> buffer) {
last_buffer_pts_ = buffer->timestamp();
return AudioDecoderPipelineNode::PushBuffer(std::move(buffer));
}
bool MediaPipelineBufferExtension::IsBufferFull() const {
CheckCalledOnCorrectThread();
return GetBufferDuration() >= kMicrosecondsDataToBuffer;
}
bool MediaPipelineBufferExtension::IsBufferEmpty() const {
CheckCalledOnCorrectThread();
return command_queue_.empty();
}
int64_t MediaPipelineBufferExtension::GetBufferDuration() const {
if (IsBufferEmpty()) {
return 0;
}
DCHECK_GE(last_buffer_pts_, 0);
DCHECK_GE(most_recent_buffer_pts_, last_buffer_pts_);
return most_recent_buffer_pts_ - last_buffer_pts_;
}
bool MediaPipelineBufferExtension::IsDelegatedDecoderHealthy() const {
return delegated_decoder_buffer_status_ != BufferStatus::kBufferFailed &&
delegated_decoder_set_config_status_;
}
void MediaPipelineBufferExtension::OnPushBufferComplete(BufferStatus status) {
CheckCalledOnCorrectThread();
delegated_decoder_buffer_status_ = status;
// If the buffer was full and the call failed, inform the caller via callback
// per method contract.
if (status == BufferStatus::kBufferFailed && IsBufferFull()) {
DCHECK(!IsBufferEmpty());
AudioDecoderPipelineNode::OnPushBufferComplete(status);
return;
}
// Dequeue and push buffers to the underlying AudioDecoder until a Pending or
// Failure signal is returned. Rather than doing this in a loop, it is done
// by posting sequential tasks to the task runner, to ensure that the current
// thread is not blocked for the duration of this process.
SchedulePushToDecoder();
}
bool MediaPipelineBufferExtension::TryPushToDecoder() {
CheckCalledOnCorrectThread();
if (IsBufferEmpty()) {
return true;
}
// Pull the front element off this instance's queue and process it.
const bool is_buffer_full_before_push = IsBufferFull();
PendingCommand& next_command = command_queue_.front();
// Only one of the config or buffer may be set.
DCHECK_NE(next_command.buffer.has_value(), next_command.config.has_value());
// If the next command in the queue can be processed, do. Else, return true.
if (next_command.buffer.has_value() &&
delegated_decoder_buffer_status_ == BufferStatus::kBufferSuccess) {
delegated_decoder_buffer_status_ =
PushBufferToDelegatedDecoder(std::move(next_command.buffer.value()));
// If this queue is no longer blocked, inform the caller per method
// contract.
if (is_buffer_full_before_push && !IsBufferFull()) {
AudioDecoderPipelineNode::OnPushBufferComplete(
delegated_decoder_buffer_status_ != BufferStatus::kBufferFailed
? BufferStatus::kBufferSuccess
: BufferStatus::kBufferFailed);
}
if (delegated_decoder_buffer_status_ == BufferStatus::kBufferFailed) {
return false;
}
} else if (next_command.config.has_value() &&
delegated_decoder_set_config_status_) {
delegated_decoder_set_config_status_ =
AudioDecoderPipelineNode::SetConfig(next_command.config.value());
if (!delegated_decoder_set_config_status_) {
return false;
}
} else {
return true;
}
// Pop the processed item from the queue and iterate as needed.
command_queue_.pop();
if (!IsBufferEmpty()) {
SchedulePushToDecoder();
}
return true;
}
void MediaPipelineBufferExtension::SchedulePushToDecoder() {
auto* task = new TaskRunner::CallbackTask<base::OnceClosure>(base::BindOnce(
base::IgnoreResult(&MediaPipelineBufferExtension::TryPushToDecoder),
weak_factory_.GetWeakPtr()));
task_runner_->PostTask(task, 0);
}
bool MediaPipelineBufferExtension::TryProcessCommand(PendingCommand command) {
// If the most recent call was a failure, inform the user.
if (!IsDelegatedDecoderHealthy()) {
return false;
}
// Queue up the item to be processed in the queue. Then, try to push the top
// item of the queue to the underlying decoder. This may or may not be the
// item that was just pushed, leading to two cases:
// - If so, clearly the result of this call provides enough information to
// determine the correct response to the user's call.
// - If not, then this result is true by assumption. But if the push failed,
// then the buffer is now in an unhealthy state and false should be
// returned.
command_queue_.push(std::move(command));
return TryPushToDecoder();
}
CmaBackend::BufferStatus MediaPipelineBufferExtension::PushBuffer(
scoped_refptr<DecoderBufferBase> buffer) {
CheckCalledOnCorrectThread();
if (IsBufferFull()) {
return BufferStatus::kBufferFailed;
}
most_recent_buffer_pts_ = buffer->timestamp();
if (!TryProcessCommand(PendingCommand(std::move(buffer)))) {
return BufferStatus::kBufferFailed;
}
return IsBufferFull() ? BufferStatus::kBufferPending
: BufferStatus::kBufferSuccess;
}
bool MediaPipelineBufferExtension::SetConfig(const AudioConfig& config) {
CheckCalledOnCorrectThread();
return TryProcessCommand(PendingCommand(config));
}
CmaBackend::AudioDecoder::RenderingDelay
MediaPipelineBufferExtension::GetRenderingDelay() {
CheckCalledOnCorrectThread();
auto delegated_decoder_delay = AudioDecoderPipelineNode::GetRenderingDelay();
delegated_decoder_delay.delay_microseconds += GetBufferDuration();
return delegated_decoder_delay;
}
} // namespace media
} // namespace chromecast