blob: dcac87114cf9bc55379379a73c6b8ec657d4c959 [file] [log] [blame]
// Copyright 2015 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 "base/debug/stack_trace.h"
#include "base/logging.h"
#include "mojo/services/media/common/cpp/local_time.h"
#include "services/media/common/rate_control_base.h"
namespace mojo {
namespace media {
static inline int64_t LocalTimeNow() {
return LocalClock::now().time_since_epoch().count();
}
RateControlBase::RateControlBase()
: binding_(this)
, current_transform_(0, 1) {
}
RateControlBase::~RateControlBase() {
Reset();
}
bool RateControlBase::Bind(InterfaceRequest<RateControl> request) {
Reset();
binding_.Bind(request.Pass());
binding_.set_connection_error_handler([this]() -> void {
Reset();
});
return true;
}
void RateControlBase::SnapshotCurrentTransform(LinearTransform* out,
uint32_t* generation) {
DCHECK(out);
base::AutoLock lock(transform_lock_);
ApplyPendingChangesLocked(LocalTimeNow());
*out = current_transform_;
if (generation) {
*generation = generation_;
}
}
void RateControlBase::GetCurrentTransform(
const GetCurrentTransformCallback& cbk) {
TimelineTransformPtr ret(TimelineTransform::New());
ret->quad = TimelineQuad::New();
LinearTransform trans;
SnapshotCurrentTransform(&trans);
ret->quad->target_offset = trans.a_zero;
ret->quad->reference_offset = trans.b_zero;
ret->quad->target_delta = trans.scale.denominator;
ret->quad->reference_delta = trans.scale.numerator;
ret->reference_timeline_id = TimelineTransform::kContextual;
ret->target_timeline_id = TimelineTransform::kLocalTimeID;
cbk.Run(ret.Pass());
}
// TODO(johngro): implement or remove. Until we have the ability to query the
// clock in the target timeline (or at least, transform local time to the target
// timeline), we have no way to apply scheduled changes.
void RateControlBase::SetTargetTimelineID(uint32_t id) {
if (id != TimelineTransform::kLocalTimeID) {
LOG(ERROR) << "Unsupported target timeline id ("
<< id << ") during SetTargetTimelineID";
Reset();
}
}
void RateControlBase::SetCurrentQuad(TimelineQuadPtr quad) {
// A target delta of zero means that the transformation from the target
// timeline to the media timeline is singular. This is not permitted, log an
// error and close the connection if someone attempts to do this.
if (!quad->target_delta) {
OnIllegalRateChange(quad->reference_delta, quad->target_delta);
return;
} else {
base::AutoLock lock(transform_lock_);
reference_pending_changes_.clear();
target_pending_changes_.clear();
current_transform_.a_zero = quad->target_offset;
current_transform_.b_zero = quad->reference_offset;
if (quad->reference_delta) {
current_transform_.scale =
LinearTransform::Ratio(quad->reference_delta, quad->target_delta);
} else {
current_transform_.scale.numerator = 0;
current_transform_.scale.denominator = 1;
}
AdvanceGenerationLocked();
}
}
void RateControlBase::SetRate(int32_t reference_delta, uint32_t target_delta) {
// Only rate changes with a non-zero target_delta are permitted. See comment
// in SetCurrentQuad.
if (!target_delta) {
OnIllegalRateChange(reference_delta, target_delta);
return;
} else {
base::AutoLock lock(transform_lock_);
// Make sure we are up to date.
int64_t target_now = LocalTimeNow();
ApplyPendingChangesLocked(target_now);
DCHECK(current_transform_.scale.denominator);
int64_t reference_now;
if (!current_transform_.DoForwardTransform(target_now, &reference_now)) {
// TODO(johngro): we cannot apply this transformation because of
// overflow, so we are forced to skip it. Should we introduce a callback
// to allow the user to know that their transformation was skipped?
// Alternatively, should we log something about how the transformation was
// skipped?
return;
}
current_transform_.a_zero = target_now;
current_transform_.b_zero = reference_now;
current_transform_.scale.numerator = reference_delta;
current_transform_.scale.denominator = target_delta;
AdvanceGenerationLocked();
}
}
void RateControlBase::SetRateAtReferenceTime(int32_t reference_delta,
uint32_t target_delta,
int64_t reference_time) {
// Only rate changes with a non-zero target_delta are permitted. See comment
// in SetCurrentQuad.
if (!target_delta) {
OnIllegalRateChange(reference_delta, target_delta);
return;
} else {
base::AutoLock lock(transform_lock_);
// If the user tries to schedule a change which takes place before any
// already scheduled change, ignore it.
if (reference_pending_changes_.size() &&
reference_pending_changes_.back().b_zero >= reference_time) {
return;
}
reference_pending_changes_.emplace_back(0,
reference_delta,
target_delta,
reference_time);
}
}
void RateControlBase::SetRateAtTargetTime(int32_t reference_delta,
uint32_t target_delta,
int64_t target_time) {
// Only rate changes with a non-zero target_delta are permitted. See comment
// in SetCurrentQuad.
if (!target_delta) {
OnIllegalRateChange(reference_delta, target_delta);
return;
} else {
base::AutoLock lock(transform_lock_);
// If the user tries to schedule a change which takes place before any
// already scheduled change, ignore it.
if (target_pending_changes_.size() &&
target_pending_changes_.back().a_zero >= target_time) {
return;
}
target_pending_changes_.emplace_back(target_time,
reference_delta,
target_delta,
0);
}
}
void RateControlBase::CancelPendingChanges() {
base::AutoLock lock(transform_lock_);
reference_pending_changes_.clear();
target_pending_changes_.clear();
}
void RateControlBase::ApplyPendingChangesLocked(int64_t target_now) {
bool advance_generation = false;
do {
// Grab a pointer to the next pending target scheduled transform which is
// not in the future, if any.
int64_t target_age;
const LinearTransform* target_trans = nullptr;
if (target_pending_changes_.size() &&
(target_now >= target_pending_changes_.front().a_zero)) {
target_trans = &target_pending_changes_.front();
target_age = target_now - target_trans->a_zero;
}
// Grab a pointer to the next pending reference scheduled transform which is
// not in the future, if any.
//
// TODO(johngro): Optimize this. When we have pending reference scheduled
// transformations, we don't have to compute this each and every time. We
// could just keep the time of the next reference scheduled change
// (expressed in target time) pre-computed, and only update it when the
// current transformation actually changes.
int64_t reference_age;
int64_t next_reference_change_target_time;
const LinearTransform* reference_trans = nullptr;
if (reference_pending_changes_.size()) {
if (current_transform_.DoReverseTransform(
reference_pending_changes_.front().b_zero,
&next_reference_change_target_time)) {
if (target_now >= next_reference_change_target_time) {
reference_age = target_now - next_reference_change_target_time;
reference_trans = &reference_pending_changes_.front();
}
}
}
if (target_trans && (!reference_trans || (reference_age <= target_age))) {
// If we have a target scheduled transform which should be applied, and we
// either have no reference scheduled transform which should be applied,
// or we have a reference scheduled transform which should be applied
// after the pending target scheduled transform, go ahead and apply the
// target transform.
//
// Note: if we cannot apply this transformation due to overflow, we have a
// serious problem. For now, we just purge the scheduled transformation
// and move on, but this is something which should never happen. We
// should probably signal an error up to the user somehow.
int64_t next_target_change_reference_time;
if (current_transform_.DoForwardTransform(
target_trans->a_zero,
&next_target_change_reference_time)) {
current_transform_.a_zero = target_trans->a_zero;
current_transform_.b_zero = next_target_change_reference_time;
current_transform_.scale.numerator = target_trans->scale.numerator;
current_transform_.scale.denominator = target_trans->scale.denominator;
DCHECK(current_transform_.scale.denominator);
}
advance_generation = true;
target_pending_changes_.pop_front();
} else if (reference_trans) {
// We have a reference scheduled transformation which should be applied
// before any pending target scheduled transformation. Do so now. No
// need to compute the splice point for the function, we have already done
// so when determining if we should apply this transformation or not.
current_transform_.a_zero = next_reference_change_target_time;
current_transform_.b_zero = reference_trans->a_zero;
current_transform_.scale.numerator = reference_trans->scale.numerator;
current_transform_.scale.denominator = reference_trans->scale.denominator;
DCHECK(current_transform_.scale.denominator);
advance_generation = true;
reference_pending_changes_.pop_front();
} else {
// We have no transformations which need to be applied at the moment. We
// are done for now.
break;
}
} while (true);
// If we have applied any changes, advance the transformation generation
if (advance_generation) {
AdvanceGenerationLocked();
}
}
void RateControlBase::OnIllegalRateChange(int32_t numerator,
uint32_t denominator) {
LOG(ERROR) << "Illegal rate change requested ("
<< numerator << "/" << denominator << ")";
Reset();
}
void RateControlBase::Reset() {
CancelPendingChanges();
SetRate(0, 1);
if (binding_.is_bound()) {
binding_.set_connection_error_handler(mojo::Closure());
binding_.Close();
}
}
} // namespace media
} // namespace mojo