| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/browser/renderer_host/media/video_capture_oracle.h" | |
| 6 | |
| 7 #include "base/debug/trace_event.h" | |
| 8 | |
| 9 namespace content { | |
| 10 | |
| 11 namespace { | |
| 12 | |
| 13 // This value controls how many redundant, timer-base captures occur when the | |
| 14 // content is static. Redundantly capturing the same frame allows iterative | |
| 15 // quality enhancement, and also allows the buffer to fill in "buffered mode". | |
| 16 // | |
| 17 // TODO(nick): Controlling this here is a hack and a layering violation, since | |
| 18 // it's a strategy specific to the WebRTC consumer, and probably just papers | |
| 19 // over some frame dropping and quality bugs. It should either be controlled at | |
| 20 // a higher level, or else redundant frame generation should be pushed down | |
| 21 // further into the WebRTC encoding stack. | |
| 22 const int kNumRedundantCapturesOfStaticContent = 200; | |
| 23 | |
| 24 } // anonymous namespace | |
| 25 | |
| 26 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta capture_period, | |
| 27 bool events_are_reliable) | |
| 28 : capture_period_(capture_period), | |
| 29 frame_number_(0), | |
| 30 last_delivered_frame_number_(0), | |
| 31 sampler_(capture_period_, | |
| 32 events_are_reliable, | |
| 33 kNumRedundantCapturesOfStaticContent) {} | |
| 34 | |
| 35 bool VideoCaptureOracle::ObserveEventAndDecideCapture( | |
| 36 Event event, | |
| 37 base::TimeTicks event_time) { | |
| 38 // Record |event| and decide whether it's a good time to capture. | |
| 39 const bool content_is_dirty = (event == kCompositorUpdate || | |
| 40 event == kSoftwarePaint); | |
| 41 bool should_sample; | |
| 42 if (content_is_dirty) { | |
| 43 frame_number_++; | |
| 44 should_sample = sampler_.AddEventAndConsiderSampling(event_time); | |
| 45 } else { | |
| 46 should_sample = sampler_.IsOverdueForSamplingAt(event_time); | |
| 47 } | |
| 48 return should_sample; | |
| 49 } | |
| 50 | |
| 51 int VideoCaptureOracle::RecordCapture() { | |
| 52 sampler_.RecordSample(); | |
| 53 return frame_number_; | |
| 54 } | |
| 55 | |
| 56 bool VideoCaptureOracle::CompleteCapture(int frame_number, | |
| 57 base::TimeTicks timestamp) { | |
| 58 // Drop frame if previous frame number is higher or we're trying to deliver | |
| 59 // a frame with the same timestamp. | |
| 60 if (last_delivered_frame_number_ > frame_number || | |
| 61 last_delivered_frame_timestamp_ == timestamp) { | |
| 62 LOG(ERROR) << "Frame with same timestamp or out of order delivery. " | |
| 63 << "Dropping frame."; | |
| 64 return false; | |
| 65 } | |
| 66 | |
| 67 if (last_delivered_frame_timestamp_ > timestamp) { | |
| 68 // We should not get here unless time was adjusted backwards. | |
| 69 LOG(ERROR) << "Frame with past timestamp (" << timestamp.ToInternalValue() | |
| 70 << ") was delivered"; | |
| 71 } | |
| 72 | |
| 73 last_delivered_frame_number_ = frame_number; | |
| 74 last_delivered_frame_timestamp_ = timestamp; | |
| 75 | |
| 76 return true; | |
| 77 } | |
| 78 | |
| 79 SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period, | |
| 80 bool events_are_reliable, | |
| 81 int redundant_capture_goal) | |
| 82 : events_are_reliable_(events_are_reliable), | |
| 83 capture_period_(capture_period), | |
| 84 redundant_capture_goal_(redundant_capture_goal), | |
| 85 token_bucket_capacity_(capture_period + capture_period / 2), | |
| 86 overdue_sample_count_(0), | |
| 87 token_bucket_(token_bucket_capacity_) { | |
| 88 DCHECK_GT(capture_period_.InMicroseconds(), 0); | |
| 89 } | |
| 90 | |
| 91 bool SmoothEventSampler::AddEventAndConsiderSampling( | |
| 92 base::TimeTicks event_time) { | |
| 93 DCHECK(!event_time.is_null()); | |
| 94 | |
| 95 // Add tokens to the bucket based on advancement in time. Then, re-bound the | |
| 96 // number of tokens in the bucket. Overflow occurs when there is too much | |
| 97 // time between events (a common case), or when RecordSample() is not being | |
| 98 // called often enough (a bug). On the other hand, if RecordSample() is being | |
| 99 // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the | |
| 100 // bucket will underflow. | |
| 101 if (!current_event_.is_null()) { | |
| 102 if (current_event_ < event_time) { | |
| 103 token_bucket_ += event_time - current_event_; | |
| 104 if (token_bucket_ > token_bucket_capacity_) | |
| 105 token_bucket_ = token_bucket_capacity_; | |
| 106 } | |
| 107 // Side note: If the system clock is reset, causing |current_event_| to be | |
| 108 // greater than |event_time|, everything here will simply gracefully adjust. | |
| 109 if (token_bucket_ < base::TimeDelta()) | |
| 110 token_bucket_ = base::TimeDelta(); | |
| 111 TRACE_COUNTER1("mirroring", | |
| 112 "MirroringTokenBucketUsec", token_bucket_.InMicroseconds()); | |
| 113 } | |
| 114 current_event_ = event_time; | |
| 115 | |
| 116 // Return true if one capture period's worth of tokens are in the bucket. | |
| 117 return token_bucket_ >= capture_period_; | |
| 118 } | |
| 119 | |
| 120 void SmoothEventSampler::RecordSample() { | |
| 121 token_bucket_ -= capture_period_; | |
| 122 TRACE_COUNTER1("mirroring", | |
| 123 "MirroringTokenBucketUsec", token_bucket_.InMicroseconds()); | |
| 124 | |
| 125 bool was_paused = overdue_sample_count_ == redundant_capture_goal_; | |
| 126 if (HasUnrecordedEvent()) { | |
| 127 last_sample_ = current_event_; | |
| 128 overdue_sample_count_ = 0; | |
| 129 } else { | |
| 130 ++overdue_sample_count_; | |
| 131 } | |
| 132 bool is_paused = overdue_sample_count_ == redundant_capture_goal_; | |
| 133 | |
| 134 VLOG_IF(0, !was_paused && is_paused) | |
| 135 << "Tab content unchanged for " << redundant_capture_goal_ | |
| 136 << " frames; capture will halt until content changes."; | |
| 137 VLOG_IF(0, was_paused && !is_paused) | |
| 138 << "Content changed; capture will resume."; | |
| 139 } | |
| 140 | |
| 141 bool SmoothEventSampler::IsOverdueForSamplingAt(base::TimeTicks event_time) | |
| 142 const { | |
| 143 DCHECK(!event_time.is_null()); | |
| 144 | |
| 145 // If we don't get events on compositor updates on this platform, then we | |
| 146 // don't reliably know whether we're dirty. | |
| 147 if (events_are_reliable_) { | |
| 148 if (!HasUnrecordedEvent() && | |
| 149 overdue_sample_count_ >= redundant_capture_goal_) { | |
| 150 return false; // Not dirty. | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 if (last_sample_.is_null()) | |
| 155 return true; | |
| 156 | |
| 157 // If we're dirty but not yet old, then we've recently gotten updates, so we | |
| 158 // won't request a sample just yet. | |
| 159 base::TimeDelta dirty_interval = event_time - last_sample_; | |
| 160 if (dirty_interval < capture_period_ * 4) | |
| 161 return false; | |
| 162 else | |
| 163 return true; | |
| 164 } | |
| 165 | |
| 166 bool SmoothEventSampler::HasUnrecordedEvent() const { | |
| 167 return !current_event_.is_null() && current_event_ != last_sample_; | |
| 168 } | |
| 169 | |
| 170 } // namespace content | |
| OLD | NEW |