File: video_capture_oracle.cc

package info (click to toggle)
chromium-browser 37.0.2062.120-1~deb7u1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 1,707,260 kB
  • sloc: cpp: 8,976,677; ansic: 3,473,199; python: 586,578; asm: 449,013; xml: 184,195; java: 142,924; sh: 118,496; perl: 81,467; makefile: 27,557; yacc: 10,506; objc: 8,886; tcl: 3,186; cs: 2,252; lex: 2,213; sql: 1,198; pascal: 1,170; lisp: 790; awk: 407; ruby: 155; php: 83; sed: 52; exp: 11
file content (172 lines) | stat: -rw-r--r-- 6,336 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (c) 2013 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 "content/browser/media/capture/video_capture_oracle.h"

#include "base/debug/trace_event.h"

namespace content {

namespace {

// This value controls how many redundant, timer-base captures occur when the
// content is static. Redundantly capturing the same frame allows iterative
// quality enhancement, and also allows the buffer to fill in "buffered mode".
//
// TODO(nick): Controlling this here is a hack and a layering violation, since
// it's a strategy specific to the WebRTC consumer, and probably just papers
// over some frame dropping and quality bugs. It should either be controlled at
// a higher level, or else redundant frame generation should be pushed down
// further into the WebRTC encoding stack.
const int kNumRedundantCapturesOfStaticContent = 200;

}  // anonymous namespace

VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta capture_period,
                                       bool events_are_reliable)
    : capture_period_(capture_period),
      frame_number_(0),
      last_delivered_frame_number_(0),
      sampler_(capture_period_,
               events_are_reliable,
               kNumRedundantCapturesOfStaticContent) {}

bool VideoCaptureOracle::ObserveEventAndDecideCapture(
    Event event,
    base::TimeTicks event_time) {
  // Record |event| and decide whether it's a good time to capture.
  const bool content_is_dirty = (event == kCompositorUpdate ||
                                 event == kSoftwarePaint);
  bool should_sample;
  if (content_is_dirty) {
    frame_number_++;
    should_sample = sampler_.AddEventAndConsiderSampling(event_time);
  } else {
    should_sample = sampler_.IsOverdueForSamplingAt(event_time);
  }
  return should_sample;
}

int VideoCaptureOracle::RecordCapture() {
  sampler_.RecordSample();
  return frame_number_;
}

bool VideoCaptureOracle::CompleteCapture(int frame_number,
                                         base::TimeTicks timestamp) {
  // Drop frame if previous frame number is higher or we're trying to deliver
  // a frame with the same timestamp.
  if (last_delivered_frame_number_ > frame_number ||
      last_delivered_frame_timestamp_ == timestamp) {
    LOG(ERROR) << "Frame with same timestamp or out of order delivery. "
               << "Dropping frame.";
    return false;
  }

  if (last_delivered_frame_timestamp_ > timestamp) {
    // We should not get here unless time was adjusted backwards.
    LOG(ERROR) << "Frame with past timestamp (" << timestamp.ToInternalValue()
               << ") was delivered";
  }

  last_delivered_frame_number_ = frame_number;
  last_delivered_frame_timestamp_ = timestamp;

  return true;
}

SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period,
                                       bool events_are_reliable,
                                       int redundant_capture_goal)
    :  events_are_reliable_(events_are_reliable),
       capture_period_(capture_period),
       redundant_capture_goal_(redundant_capture_goal),
       token_bucket_capacity_(capture_period + capture_period / 2),
       overdue_sample_count_(0),
       token_bucket_(token_bucket_capacity_) {
  DCHECK_GT(capture_period_.InMicroseconds(), 0);
}

bool SmoothEventSampler::AddEventAndConsiderSampling(
    base::TimeTicks event_time) {
  DCHECK(!event_time.is_null());

  // Add tokens to the bucket based on advancement in time.  Then, re-bound the
  // number of tokens in the bucket.  Overflow occurs when there is too much
  // time between events (a common case), or when RecordSample() is not being
  // called often enough (a bug).  On the other hand, if RecordSample() is being
  // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the
  // bucket will underflow.
  if (!current_event_.is_null()) {
    if (current_event_ < event_time) {
      token_bucket_ += event_time - current_event_;
      if (token_bucket_ > token_bucket_capacity_)
        token_bucket_ = token_bucket_capacity_;
    }
    // Side note: If the system clock is reset, causing |current_event_| to be
    // greater than |event_time|, everything here will simply gracefully adjust.
    if (token_bucket_ < base::TimeDelta())
      token_bucket_ = base::TimeDelta();
    TRACE_COUNTER1("mirroring",
                   "MirroringTokenBucketUsec",
                   std::max<int64>(0, token_bucket_.InMicroseconds()));
  }
  current_event_ = event_time;

  // Return true if one capture period's worth of tokens are in the bucket.
  return token_bucket_ >= capture_period_;
}

void SmoothEventSampler::RecordSample() {
  token_bucket_ -= capture_period_;
  TRACE_COUNTER1("mirroring",
                 "MirroringTokenBucketUsec",
                 std::max<int64>(0, token_bucket_.InMicroseconds()));

  bool was_paused = overdue_sample_count_ == redundant_capture_goal_;
  if (HasUnrecordedEvent()) {
    last_sample_ = current_event_;
    overdue_sample_count_ = 0;
  } else {
    ++overdue_sample_count_;
  }
  bool is_paused = overdue_sample_count_ == redundant_capture_goal_;

  VLOG_IF(0, !was_paused && is_paused)
      << "Tab content unchanged for " << redundant_capture_goal_
      << " frames; capture will halt until content changes.";
  VLOG_IF(0, was_paused && !is_paused)
      << "Content changed; capture will resume.";
}

bool SmoothEventSampler::IsOverdueForSamplingAt(base::TimeTicks event_time)
    const {
  DCHECK(!event_time.is_null());

  // If we don't get events on compositor updates on this platform, then we
  // don't reliably know whether we're dirty.
  if (events_are_reliable_) {
    if (!HasUnrecordedEvent() &&
        overdue_sample_count_ >= redundant_capture_goal_) {
      return false;  // Not dirty.
    }
  }

  if (last_sample_.is_null())
    return true;

  // If we're dirty but not yet old, then we've recently gotten updates, so we
  // won't request a sample just yet.
  base::TimeDelta dirty_interval = event_time - last_sample_;
  if (dirty_interval < capture_period_ * 4)
    return false;
  else
    return true;
}

bool SmoothEventSampler::HasUnrecordedEvent() const {
  return !current_event_.is_null() && current_event_ != last_sample_;
}

}  // namespace content