Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(140)

Side by Side Diff: remoting/client/software_video_renderer.cc

Issue 1288063004: Simplify FrameConsumer interface. Remove FrameProducer interface. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/client/software_video_renderer.h" 5 #include "remoting/client/software_video_renderer.h"
6 6
7 #include <list> 7 #include <list>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
11 #include "base/callback_helpers.h" 11 #include "base/callback_helpers.h"
12 #include "base/location.h" 12 #include "base/location.h"
13 #include "base/logging.h" 13 #include "base/logging.h"
14 #include "base/single_thread_task_runner.h" 14 #include "base/single_thread_task_runner.h"
15 #include "base/task_runner_util.h"
15 #include "remoting/base/util.h" 16 #include "remoting/base/util.h"
16 #include "remoting/client/frame_consumer.h" 17 #include "remoting/client/frame_consumer.h"
17 #include "remoting/codec/video_decoder.h" 18 #include "remoting/codec/video_decoder.h"
18 #include "remoting/codec/video_decoder_verbatim.h" 19 #include "remoting/codec/video_decoder_verbatim.h"
19 #include "remoting/codec/video_decoder_vpx.h" 20 #include "remoting/codec/video_decoder_vpx.h"
21 #include "remoting/proto/video.pb.h"
20 #include "remoting/protocol/session_config.h" 22 #include "remoting/protocol/session_config.h"
21 #include "third_party/libyuv/include/libyuv/convert_argb.h" 23 #include "third_party/libyuv/include/libyuv/convert_argb.h"
22 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" 24 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
23 25
24 using base::Passed;
25 using remoting::protocol::ChannelConfig; 26 using remoting::protocol::ChannelConfig;
26 using remoting::protocol::SessionConfig; 27 using remoting::protocol::SessionConfig;
27 28
28 namespace remoting { 29 namespace remoting {
29 30
31 namespace {
32
30 // This class wraps a VideoDecoder and byte-swaps the pixels for compatibility 33 // This class wraps a VideoDecoder and byte-swaps the pixels for compatibility
31 // with the android.graphics.Bitmap class. 34 // with the android.graphics.Bitmap class.
32 // TODO(lambroslambrou): Refactor so that the VideoDecoder produces data 35 // TODO(lambroslambrou): Refactor so that the VideoDecoder produces data
33 // in the right byte-order, instead of swapping it here. 36 // in the right byte-order, instead of swapping it here.
34 class RgbToBgrVideoDecoderFilter : public VideoDecoder { 37 class RgbToBgrVideoDecoderFilter : public VideoDecoder {
35 public: 38 public:
36 RgbToBgrVideoDecoderFilter(scoped_ptr<VideoDecoder> parent) 39 RgbToBgrVideoDecoderFilter(scoped_ptr<VideoDecoder> parent)
37 : parent_(parent.Pass()) { 40 : parent_(parent.Pass()) {}
38 }
39
40 void Initialize(const webrtc::DesktopSize& screen_size) override {
41 parent_->Initialize(screen_size);
42 }
43 41
44 bool DecodePacket(const VideoPacket& packet) override { 42 bool DecodePacket(const VideoPacket& packet) override {
45 return parent_->DecodePacket(packet); 43 return parent_->DecodePacket(packet);
46 } 44 }
47 45
48 void Invalidate(const webrtc::DesktopSize& view_size, 46 void Invalidate(const webrtc::DesktopSize& view_size,
49 const webrtc::DesktopRegion& region) override { 47 const webrtc::DesktopRegion& region) override {
50 return parent_->Invalidate(view_size, region); 48 return parent_->Invalidate(view_size, region);
51 } 49 }
52 50
53 void RenderFrame(const webrtc::DesktopSize& view_size, 51 void RenderFrame(const webrtc::DesktopSize& view_size,
54 const webrtc::DesktopRect& clip_area, 52 const webrtc::DesktopRect& clip_area,
55 uint8* image_buffer, 53 uint8* image_buffer,
56 int image_stride, 54 int image_stride,
57 webrtc::DesktopRegion* output_region) override { 55 webrtc::DesktopRegion* output_region) override {
58 parent_->RenderFrame(view_size, clip_area, image_buffer, image_stride, 56 parent_->RenderFrame(view_size, clip_area, image_buffer, image_stride,
59 output_region); 57 output_region);
60 58
61 for (webrtc::DesktopRegion::Iterator i(*output_region); !i.IsAtEnd(); 59 for (webrtc::DesktopRegion::Iterator i(*output_region); !i.IsAtEnd();
62 i.Advance()) { 60 i.Advance()) {
63 webrtc::DesktopRect rect = i.rect(); 61 webrtc::DesktopRect rect = i.rect();
64 uint8* pixels = image_buffer + (rect.top() * image_stride) + 62 uint8* pixels = image_buffer + (rect.top() * image_stride) +
65 (rect.left() * kBytesPerPixel); 63 (rect.left() * kBytesPerPixel);
66 libyuv::ABGRToARGB(pixels, image_stride, pixels, image_stride, 64 libyuv::ABGRToARGB(pixels, image_stride, pixels, image_stride,
67 rect.width(), rect.height()); 65 rect.width(), rect.height());
68 } 66 }
69 } 67 }
70 68
71 const webrtc::DesktopRegion* GetImageShape() override { 69 const webrtc::DesktopRegion* GetImageShape() override {
72 return parent_->GetImageShape(); 70 return parent_->GetImageShape();
73 } 71 }
74 72
75 private: 73 private:
76 scoped_ptr<VideoDecoder> parent_; 74 scoped_ptr<VideoDecoder> parent_;
77 }; 75 };
78 76
79 class SoftwareVideoRenderer::Core { 77 scoped_ptr<webrtc::DesktopFrame> DoDecodeFrame(
80 public: 78 VideoDecoder* decoder,
81 Core(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, 79 scoped_ptr<VideoPacket> packet,
82 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner, 80 scoped_ptr<webrtc::DesktopFrame> frame) {
83 scoped_ptr<FrameConsumerProxy> consumer); 81 if (!decoder->DecodePacket(*packet))
84 ~Core(); 82 frame.reset();
85 83
86 void OnSessionConfig(const protocol::SessionConfig& config); 84 decoder->RenderFrame(
87 void DrawBuffer(webrtc::DesktopFrame* buffer); 85 frame->size(), webrtc::DesktopRect::MakeSize(frame->size()),
88 void InvalidateRegion(const webrtc::DesktopRegion& region); 86 frame->data(), frame->stride(), frame->mutable_updated_region());
89 void RequestReturnBuffers(const base::Closure& done);
90 void SetOutputSizeAndClip(
91 const webrtc::DesktopSize& view_size,
92 const webrtc::DesktopRect& clip_area);
93 87
94 // Decodes the contents of |packet|. DecodePacket may keep a reference to 88 const webrtc::DesktopRegion* shape = decoder->GetImageShape();
95 // |packet| so the |packet| must remain alive and valid until |done| is 89 if (shape)
96 // executed. 90 frame->set_shape(new webrtc::DesktopRegion(*shape));
97 void DecodePacket(scoped_ptr<VideoPacket> packet, const base::Closure& done);
98 91
99 private: 92 return frame.Pass();
100 // Paints the invalidated region to the next available buffer and returns it 93 }
101 // to the consumer.
102 void SchedulePaint();
103 void DoPaint();
104 94
105 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; 95 } // namespace
106 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner_;
107 scoped_ptr<FrameConsumerProxy> consumer_;
108 scoped_ptr<VideoDecoder> decoder_;
109 96
110 // Remote screen size in pixels. 97 SoftwareVideoRenderer::SoftwareVideoRenderer(
111 webrtc::DesktopSize source_size_;
112
113 // Vertical and horizontal DPI of the remote screen.
114 webrtc::DesktopVector source_dpi_;
115
116 // The current dimensions of the frame consumer view.
117 webrtc::DesktopSize view_size_;
118 webrtc::DesktopRect clip_area_;
119
120 // The drawing buffers supplied by the frame consumer.
121 std::list<webrtc::DesktopFrame*> buffers_;
122
123 // Flag used to coalesce runs of SchedulePaint()s into a single DoPaint().
124 bool paint_scheduled_;
125
126 base::WeakPtrFactory<Core> weak_factory_;
127 };
128
129 SoftwareVideoRenderer::Core::Core(
130 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
131 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner, 98 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner,
132 scoped_ptr<FrameConsumerProxy> consumer) 99 FrameConsumer* consumer)
133 : main_task_runner_(main_task_runner), 100 : decode_task_runner_(decode_task_runner),
134 decode_task_runner_(decode_task_runner), 101 consumer_(consumer),
135 consumer_(consumer.Pass()),
136 paint_scheduled_(false),
137 weak_factory_(this) {} 102 weak_factory_(this) {}
138 103
139 SoftwareVideoRenderer::Core::~Core() { 104 SoftwareVideoRenderer::~SoftwareVideoRenderer() {
105 if (decoder_)
106 decode_task_runner_->DeleteSoon(FROM_HERE, decoder_.release());
140 } 107 }
141 108
142 void SoftwareVideoRenderer::Core::OnSessionConfig(const SessionConfig& config) { 109 void SoftwareVideoRenderer::OnSessionConfig(
143 DCHECK(decode_task_runner_->BelongsToCurrentThread()); 110 const protocol::SessionConfig& config) {
111 DCHECK(thread_checker_.CalledOnValidThread());
144 112
145 // Initialize decoder based on the selected codec. 113 // Initialize decoder based on the selected codec.
146 ChannelConfig::Codec codec = config.video_config().codec; 114 ChannelConfig::Codec codec = config.video_config().codec;
147 if (codec == ChannelConfig::CODEC_VERBATIM) { 115 if (codec == ChannelConfig::CODEC_VERBATIM) {
148 decoder_.reset(new VideoDecoderVerbatim()); 116 decoder_.reset(new VideoDecoderVerbatim());
149 } else if (codec == ChannelConfig::CODEC_VP8) { 117 } else if (codec == ChannelConfig::CODEC_VP8) {
150 decoder_ = VideoDecoderVpx::CreateForVP8(); 118 decoder_ = VideoDecoderVpx::CreateForVP8();
151 } else if (codec == ChannelConfig::CODEC_VP9) { 119 } else if (codec == ChannelConfig::CODEC_VP9) {
152 decoder_ = VideoDecoderVpx::CreateForVP9(); 120 decoder_ = VideoDecoderVpx::CreateForVP9();
153 } else { 121 } else {
154 NOTREACHED() << "Invalid Encoding found: " << codec; 122 NOTREACHED() << "Invalid Encoding found: " << codec;
155 } 123 }
156 124
157 if (consumer_->GetPixelFormat() == FrameConsumer::FORMAT_RGBA) { 125 if (consumer_->GetPixelFormat() == FrameConsumer::FORMAT_RGBA) {
158 scoped_ptr<VideoDecoder> wrapper( 126 scoped_ptr<VideoDecoder> wrapper(
159 new RgbToBgrVideoDecoderFilter(decoder_.Pass())); 127 new RgbToBgrVideoDecoderFilter(decoder_.Pass()));
160 decoder_ = wrapper.Pass(); 128 decoder_ = wrapper.Pass();
161 } 129 }
162 } 130 }
163 131
164 void SoftwareVideoRenderer::Core::DecodePacket(scoped_ptr<VideoPacket> packet,
165 const base::Closure& done) {
166 DCHECK(decode_task_runner_->BelongsToCurrentThread());
167
168 bool decoder_needs_reset = false;
169 bool notify_size_or_dpi_change = false;
170
171 // If the packet includes screen size or DPI information, store them.
172 if (packet->format().has_screen_width() &&
173 packet->format().has_screen_height()) {
174 webrtc::DesktopSize source_size(packet->format().screen_width(),
175 packet->format().screen_height());
176 if (!source_size_.equals(source_size)) {
177 source_size_ = source_size;
178 decoder_needs_reset = true;
179 notify_size_or_dpi_change = true;
180 }
181 }
182 if (packet->format().has_x_dpi() && packet->format().has_y_dpi()) {
183 webrtc::DesktopVector source_dpi(packet->format().x_dpi(),
184 packet->format().y_dpi());
185 if (!source_dpi.equals(source_dpi_)) {
186 source_dpi_ = source_dpi;
187 notify_size_or_dpi_change = true;
188 }
189 }
190
191 // If we've never seen a screen size, ignore the packet.
192 if (source_size_.is_empty()) {
193 main_task_runner_->PostTask(FROM_HERE, base::Bind(done));
194 return;
195 }
196
197 if (decoder_needs_reset)
198 decoder_->Initialize(source_size_);
199 if (notify_size_or_dpi_change)
200 consumer_->SetSourceSize(source_size_, source_dpi_);
201
202 if (decoder_->DecodePacket(*packet.get())) {
203 SchedulePaint();
204 } else {
205 LOG(ERROR) << "DecodePacket() failed.";
206 }
207
208 main_task_runner_->PostTask(FROM_HERE, base::Bind(done));
209 }
210
211 void SoftwareVideoRenderer::Core::SchedulePaint() {
212 DCHECK(decode_task_runner_->BelongsToCurrentThread());
213 if (paint_scheduled_)
214 return;
215 paint_scheduled_ = true;
216 decode_task_runner_->PostTask(
217 FROM_HERE, base::Bind(&SoftwareVideoRenderer::Core::DoPaint,
218 weak_factory_.GetWeakPtr()));
219 }
220
221 void SoftwareVideoRenderer::Core::DoPaint() {
222 DCHECK(decode_task_runner_->BelongsToCurrentThread());
223 DCHECK(paint_scheduled_);
224 paint_scheduled_ = false;
225
226 // If the view size is empty or we have no output buffers ready, return.
227 if (buffers_.empty() || view_size_.is_empty())
228 return;
229
230 // If no Decoder is initialized, or the host dimensions are empty, return.
231 if (!decoder_.get() || source_size_.is_empty())
232 return;
233
234 // Draw the invalidated region to the buffer.
235 webrtc::DesktopFrame* buffer = buffers_.front();
236 webrtc::DesktopRegion output_region;
237 decoder_->RenderFrame(view_size_, clip_area_,
238 buffer->data(), buffer->stride(), &output_region);
239
240 // Notify the consumer that painting is done.
241 if (!output_region.is_empty()) {
242 buffers_.pop_front();
243 consumer_->ApplyBuffer(view_size_, clip_area_, buffer, output_region,
244 decoder_->GetImageShape());
245 }
246 }
247
248 void SoftwareVideoRenderer::Core::RequestReturnBuffers(
249 const base::Closure& done) {
250 DCHECK(decode_task_runner_->BelongsToCurrentThread());
251
252 while (!buffers_.empty()) {
253 consumer_->ReturnBuffer(buffers_.front());
254 buffers_.pop_front();
255 }
256
257 if (!done.is_null())
258 done.Run();
259 }
260
261 void SoftwareVideoRenderer::Core::DrawBuffer(webrtc::DesktopFrame* buffer) {
262 DCHECK(decode_task_runner_->BelongsToCurrentThread());
263 DCHECK(clip_area_.width() <= buffer->size().width() &&
264 clip_area_.height() <= buffer->size().height());
265
266 buffers_.push_back(buffer);
267 SchedulePaint();
268 }
269
270 void SoftwareVideoRenderer::Core::InvalidateRegion(
271 const webrtc::DesktopRegion& region) {
272 DCHECK(decode_task_runner_->BelongsToCurrentThread());
273
274 if (decoder_.get()) {
275 decoder_->Invalidate(view_size_, region);
276 SchedulePaint();
277 }
278 }
279
280 void SoftwareVideoRenderer::Core::SetOutputSizeAndClip(
281 const webrtc::DesktopSize& view_size,
282 const webrtc::DesktopRect& clip_area) {
283 DCHECK(decode_task_runner_->BelongsToCurrentThread());
284
285 // The whole frame needs to be repainted if the scaling factor has changed.
286 if (!view_size_.equals(view_size) && decoder_.get()) {
287 webrtc::DesktopRegion region;
288 region.AddRect(webrtc::DesktopRect::MakeSize(view_size));
289 decoder_->Invalidate(view_size, region);
290 }
291
292 if (!view_size_.equals(view_size) ||
293 !clip_area_.equals(clip_area)) {
294 view_size_ = view_size;
295 clip_area_ = clip_area;
296
297 // Return buffers that are smaller than needed to the consumer for
298 // reuse/reallocation.
299 std::list<webrtc::DesktopFrame*>::iterator i = buffers_.begin();
300 while (i != buffers_.end()) {
301 if ((*i)->size().width() < clip_area_.width() ||
302 (*i)->size().height() < clip_area_.height()) {
303 consumer_->ReturnBuffer(*i);
304 i = buffers_.erase(i);
305 } else {
306 ++i;
307 }
308 }
309
310 SchedulePaint();
311 }
312 }
313
314 SoftwareVideoRenderer::SoftwareVideoRenderer(
315 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
316 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner,
317 scoped_ptr<FrameConsumerProxy> consumer)
318 : decode_task_runner_(decode_task_runner),
319 core_(new Core(main_task_runner, decode_task_runner, consumer.Pass())),
320 weak_factory_(this) {
321 DCHECK(CalledOnValidThread());
322 }
323
324 SoftwareVideoRenderer::~SoftwareVideoRenderer() {
325 DCHECK(CalledOnValidThread());
326 bool result = decode_task_runner_->DeleteSoon(FROM_HERE, core_.release());
327 DCHECK(result);
328 }
329
330 void SoftwareVideoRenderer::OnSessionConfig(
331 const protocol::SessionConfig& config) {
332 DCHECK(CalledOnValidThread());
333 decode_task_runner_->PostTask(
334 FROM_HERE, base::Bind(&SoftwareVideoRenderer::Core::OnSessionConfig,
335 base::Unretained(core_.get()), config));
336 }
337
338 ChromotingStats* SoftwareVideoRenderer::GetStats() { 132 ChromotingStats* SoftwareVideoRenderer::GetStats() {
339 DCHECK(CalledOnValidThread()); 133 DCHECK(thread_checker_.CalledOnValidThread());
340 return &stats_; 134 return &stats_;
341 } 135 }
342 136
343 protocol::VideoStub* SoftwareVideoRenderer::GetVideoStub() { 137 protocol::VideoStub* SoftwareVideoRenderer::GetVideoStub() {
138 DCHECK(thread_checker_.CalledOnValidThread());
344 return this; 139 return this;
345 } 140 }
346 141
347 void SoftwareVideoRenderer::ProcessVideoPacket(scoped_ptr<VideoPacket> packet, 142 void SoftwareVideoRenderer::ProcessVideoPacket(scoped_ptr<VideoPacket> packet,
348 const base::Closure& done) { 143 const base::Closure& done) {
349 DCHECK(CalledOnValidThread()); 144 DCHECK(thread_checker_.CalledOnValidThread());
145
146 base::ScopedClosureRunner done_runner(done);
350 147
351 stats_.RecordVideoPacketStats(*packet); 148 stats_.RecordVideoPacketStats(*packet);
352 149
353 // If the video packet is empty then drop it. Empty packets are used to 150 // If the video packet is empty then drop it. Empty packets are used to
354 // maintain activity on the network. 151 // maintain activity on the network.
355 if (!packet->has_data() || packet->data().size() == 0) { 152 if (!packet->has_data() || packet->data().size() == 0) {
356 decode_task_runner_->PostTask(FROM_HERE, done);
357 return; 153 return;
358 } 154 }
359 155
360 // Measure the latency between the last packet being received and presented. 156 if (packet->format().has_screen_width() &&
361 base::Time decode_start = base::Time::Now(); 157 packet->format().has_screen_height()) {
158 current_size_.set(packet->format().screen_width(),
159 packet->format().screen_height());
160 }
362 161
363 base::Closure decode_done = base::Bind(&SoftwareVideoRenderer::OnPacketDone, 162 if (current_size_.is_empty()) {
364 weak_factory_.GetWeakPtr(), 163 LOG(ERROR) << "Received VideoPacket with unknown size.";
365 decode_start, done); 164 return;
165 }
366 166
367 decode_task_runner_->PostTask(FROM_HERE, base::Bind( 167 scoped_ptr<webrtc::DesktopFrame> frame =
368 &SoftwareVideoRenderer::Core::DecodePacket, 168 consumer_->AllocateFrame(current_size_);
369 base::Unretained(core_.get()), base::Passed(&packet), decode_done)); 169 base::PostTaskAndReplyWithResult(
170 decode_task_runner_.get(), FROM_HERE,
171 base::Bind(&DoDecodeFrame, decoder_.get(), base::Passed(&packet),
172 base::Passed(&frame)),
173 base::Bind(&SoftwareVideoRenderer::RenderFrame,
174 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(),
175 done_runner.Release()));
370 } 176 }
371 177
372 void SoftwareVideoRenderer::DrawBuffer(webrtc::DesktopFrame* buffer) { 178 void SoftwareVideoRenderer::RenderFrame(
373 decode_task_runner_->PostTask( 179 base::TimeTicks decode_start_time,
374 FROM_HERE, base::Bind(&SoftwareVideoRenderer::Core::DrawBuffer, 180 const base::Closure& done,
375 base::Unretained(core_.get()), buffer)); 181 scoped_ptr<webrtc::DesktopFrame> frame) {
182 DCHECK(thread_checker_.CalledOnValidThread());
183
184 stats_.RecordDecodeTime(
185 (base::TimeTicks::Now() - decode_start_time).InMilliseconds());
186
187 if (!frame) {
188 if (!done.is_null())
189 done.Run();
190 return;
191 }
192
193 consumer_->DrawFrame(
194 frame.Pass(),
195 base::Bind(&SoftwareVideoRenderer::OnFrameRendered,
196 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), done));
376 } 197 }
377 198
378 void SoftwareVideoRenderer::InvalidateRegion( 199 void SoftwareVideoRenderer::OnFrameRendered(base::TimeTicks paint_start_time,
379 const webrtc::DesktopRegion& region) { 200 const base::Closure& done) {
380 decode_task_runner_->PostTask( 201 DCHECK(thread_checker_.CalledOnValidThread());
381 FROM_HERE, base::Bind(&SoftwareVideoRenderer::Core::InvalidateRegion,
382 base::Unretained(core_.get()), region));
383 }
384 202
385 void SoftwareVideoRenderer::RequestReturnBuffers(const base::Closure& done) { 203 stats_.RecordPaintTime(
386 decode_task_runner_->PostTask( 204 (base::TimeTicks::Now() - paint_start_time).InMilliseconds());
387 FROM_HERE,
388 base::Bind(&SoftwareVideoRenderer::Core::RequestReturnBuffers,
389 base::Unretained(core_.get()), done));
390 }
391 205
392 void SoftwareVideoRenderer::SetOutputSizeAndClip( 206 if (!done.is_null())
393 const webrtc::DesktopSize& view_size, 207 done.Run();
394 const webrtc::DesktopRect& clip_area) {
395 decode_task_runner_->PostTask(
396 FROM_HERE,
397 base::Bind(&SoftwareVideoRenderer::Core::SetOutputSizeAndClip,
398 base::Unretained(core_.get()), view_size, clip_area));
399 }
400
401 void SoftwareVideoRenderer::OnPacketDone(base::Time decode_start,
402 const base::Closure& done) {
403 DCHECK(CalledOnValidThread());
404
405 // Record the latency between the packet being received and presented.
406 base::TimeDelta decode_time = base::Time::Now() - decode_start;
407 stats_.RecordDecodeTime(decode_time.InMilliseconds());
408
409 decode_task_runner_->PostTask(FROM_HERE, done);
410 } 208 }
411 209
412 } // namespace remoting 210 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698