|  | // 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 |