| 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/media/capture/video_capture_oracle.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/format_macros.h" | |
| 10 #include "base/strings/stringprintf.h" | |
| 11 | |
| 12 namespace content { | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // This value controls how many redundant, timer-base captures occur when the | |
| 17 // content is static. Redundantly capturing the same frame allows iterative | |
| 18 // quality enhancement, and also allows the buffer to fill in "buffered mode". | |
| 19 // | |
| 20 // TODO(nick): Controlling this here is a hack and a layering violation, since | |
| 21 // it's a strategy specific to the WebRTC consumer, and probably just papers | |
| 22 // over some frame dropping and quality bugs. It should either be controlled at | |
| 23 // a higher level, or else redundant frame generation should be pushed down | |
| 24 // further into the WebRTC encoding stack. | |
| 25 const int kNumRedundantCapturesOfStaticContent = 200; | |
| 26 | |
| 27 // Given the amount of time between frames, compare to the expected amount of | |
| 28 // time between frames at |frame_rate| and return the fractional difference. | |
| 29 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) { | |
| 30 DCHECK_GT(frame_rate, 0); | |
| 31 const base::TimeDelta expected_delta = | |
| 32 base::TimeDelta::FromSeconds(1) / frame_rate; | |
| 33 return (delta - expected_delta).InMillisecondsF() / | |
| 34 expected_delta.InMillisecondsF(); | |
| 35 } | |
| 36 | |
| 37 } // anonymous namespace | |
| 38 | |
| 39 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta min_capture_period) | |
| 40 : next_frame_number_(0), | |
| 41 last_successfully_delivered_frame_number_(-1), | |
| 42 num_frames_pending_(0), | |
| 43 smoothing_sampler_(min_capture_period, | |
| 44 kNumRedundantCapturesOfStaticContent), | |
| 45 content_sampler_(min_capture_period) { | |
| 46 } | |
| 47 | |
| 48 VideoCaptureOracle::~VideoCaptureOracle() {} | |
| 49 | |
| 50 bool VideoCaptureOracle::ObserveEventAndDecideCapture( | |
| 51 Event event, | |
| 52 const gfx::Rect& damage_rect, | |
| 53 base::TimeTicks event_time) { | |
| 54 DCHECK_GE(event, 0); | |
| 55 DCHECK_LT(event, kNumEvents); | |
| 56 if (event_time < last_event_time_[event]) { | |
| 57 LOG(WARNING) << "Event time is not monotonically non-decreasing. " | |
| 58 << "Deciding not to capture this frame."; | |
| 59 return false; | |
| 60 } | |
| 61 last_event_time_[event] = event_time; | |
| 62 | |
| 63 bool should_sample = false; | |
| 64 duration_of_next_frame_ = base::TimeDelta(); | |
| 65 switch (event) { | |
| 66 case kCompositorUpdate: | |
| 67 smoothing_sampler_.ConsiderPresentationEvent(event_time); | |
| 68 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time); | |
| 69 if (content_sampler_.HasProposal()) { | |
| 70 should_sample = content_sampler_.ShouldSample(); | |
| 71 if (should_sample) { | |
| 72 event_time = content_sampler_.frame_timestamp(); | |
| 73 duration_of_next_frame_ = content_sampler_.sampling_period(); | |
| 74 } | |
| 75 } else { | |
| 76 should_sample = smoothing_sampler_.ShouldSample(); | |
| 77 if (should_sample) | |
| 78 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); | |
| 79 } | |
| 80 break; | |
| 81 case kTimerPoll: | |
| 82 // While the timer is firing, only allow a sampling if there are none | |
| 83 // currently in-progress. | |
| 84 if (num_frames_pending_ == 0) { | |
| 85 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time); | |
| 86 if (should_sample) | |
| 87 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); | |
| 88 } | |
| 89 break; | |
| 90 case kNumEvents: | |
| 91 NOTREACHED(); | |
| 92 break; | |
| 93 } | |
| 94 | |
| 95 SetFrameTimestamp(next_frame_number_, event_time); | |
| 96 return should_sample; | |
| 97 } | |
| 98 | |
| 99 int VideoCaptureOracle::RecordCapture() { | |
| 100 smoothing_sampler_.RecordSample(); | |
| 101 content_sampler_.RecordSample(GetFrameTimestamp(next_frame_number_)); | |
| 102 num_frames_pending_++; | |
| 103 return next_frame_number_++; | |
| 104 } | |
| 105 | |
| 106 bool VideoCaptureOracle::CompleteCapture(int frame_number, | |
| 107 bool capture_was_successful, | |
| 108 base::TimeTicks* frame_timestamp) { | |
| 109 num_frames_pending_--; | |
| 110 | |
| 111 // Drop frame if previously delivered frame number is higher. | |
| 112 if (last_successfully_delivered_frame_number_ > frame_number) { | |
| 113 LOG_IF(WARNING, capture_was_successful) | |
| 114 << "Out of order frame delivery detected (have #" << frame_number | |
| 115 << ", last was #" << last_successfully_delivered_frame_number_ | |
| 116 << "). Dropping frame."; | |
| 117 return false; | |
| 118 } | |
| 119 | |
| 120 if (!capture_was_successful) { | |
| 121 VLOG(2) << "Capture of frame #" << frame_number << " was not successful."; | |
| 122 return false; | |
| 123 } | |
| 124 | |
| 125 DCHECK_NE(last_successfully_delivered_frame_number_, frame_number); | |
| 126 last_successfully_delivered_frame_number_ = frame_number; | |
| 127 | |
| 128 *frame_timestamp = GetFrameTimestamp(frame_number); | |
| 129 | |
| 130 // If enabled, log a measurement of how this frame timestamp has incremented | |
| 131 // in relation to an ideal increment. | |
| 132 if (VLOG_IS_ON(2) && frame_number > 0) { | |
| 133 const base::TimeDelta delta = | |
| 134 *frame_timestamp - GetFrameTimestamp(frame_number - 1); | |
| 135 if (content_sampler_.HasProposal()) { | |
| 136 const double estimated_frame_rate = | |
| 137 1000000.0 / content_sampler_.detected_period().InMicroseconds(); | |
| 138 const int rounded_frame_rate = | |
| 139 static_cast<int>(estimated_frame_rate + 0.5); | |
| 140 VLOG(2) << base::StringPrintf( | |
| 141 "Captured #%d: delta=%" PRId64 " usec" | |
| 142 ", now locked into {%s}, %+0.1f%% slower than %d FPS", | |
| 143 frame_number, | |
| 144 delta.InMicroseconds(), | |
| 145 content_sampler_.detected_region().ToString().c_str(), | |
| 146 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate), | |
| 147 rounded_frame_rate); | |
| 148 } else { | |
| 149 VLOG(2) << base::StringPrintf( | |
| 150 "Captured #%d: delta=%" PRId64 " usec" | |
| 151 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%", | |
| 152 frame_number, | |
| 153 delta.InMicroseconds(), | |
| 154 100.0 * FractionFromExpectedFrameRate(delta, 30), | |
| 155 100.0 * FractionFromExpectedFrameRate(delta, 25), | |
| 156 100.0 * FractionFromExpectedFrameRate(delta, 24)); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 return !frame_timestamp->is_null(); | |
| 161 } | |
| 162 | |
| 163 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const { | |
| 164 DCHECK_LE(frame_number, next_frame_number_); | |
| 165 DCHECK_LT(next_frame_number_ - frame_number, kMaxFrameTimestamps); | |
| 166 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; | |
| 167 } | |
| 168 | |
| 169 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, | |
| 170 base::TimeTicks timestamp) { | |
| 171 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; | |
| 172 } | |
| 173 | |
| 174 } // namespace content | |
| OLD | NEW |