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