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

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

Issue 136763009: Add VideoProcessor interface. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 11 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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/rectangle_update_decoder.h" 5 #include "remoting/client/rectangle_update_decoder.h"
6 6
7 #include <list>
8
7 #include "base/bind.h" 9 #include "base/bind.h"
8 #include "base/callback.h" 10 #include "base/callback.h"
9 #include "base/callback_helpers.h" 11 #include "base/callback_helpers.h"
10 #include "base/location.h" 12 #include "base/location.h"
11 #include "base/logging.h" 13 #include "base/logging.h"
12 #include "base/single_thread_task_runner.h" 14 #include "base/single_thread_task_runner.h"
13 #include "remoting/base/util.h" 15 #include "remoting/base/util.h"
14 #include "remoting/client/frame_consumer.h" 16 #include "remoting/client/frame_consumer.h"
15 #include "remoting/codec/video_decoder.h" 17 #include "remoting/codec/video_decoder.h"
16 #include "remoting/codec/video_decoder_verbatim.h" 18 #include "remoting/codec/video_decoder_verbatim.h"
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 } 69 }
68 70
69 virtual const webrtc::DesktopRegion* GetImageShape() OVERRIDE { 71 virtual const webrtc::DesktopRegion* GetImageShape() OVERRIDE {
70 return parent_->GetImageShape(); 72 return parent_->GetImageShape();
71 } 73 }
72 74
73 private: 75 private:
74 scoped_ptr<VideoDecoder> parent_; 76 scoped_ptr<VideoDecoder> parent_;
75 }; 77 };
76 78
77 RectangleUpdateDecoder::RectangleUpdateDecoder( 79 class RectangleUpdateDecoder::Core : public base::RefCountedThreadSafe<Core> {
80 public:
81 Core(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
82 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner,
83 scoped_refptr<FrameConsumerProxy> consumer);
84
85 // VideoProcessor implementation.
86 void Initialize(const protocol::SessionConfig& config);
87 ChromotingStats* GetStats();
88 void ProcessVideoPacket(scoped_ptr<VideoPacket> packet,
89 const base::Closure& done);
90
91 // FrameProducer implementation. These methods may be called before we are
92 // Initialize()d, or we know the source screen size.
93 void DrawBuffer(webrtc::DesktopFrame* buffer);
94 void InvalidateRegion(const webrtc::DesktopRegion& region);
95 void RequestReturnBuffers(const base::Closure& done);
96 void SetOutputSizeAndClip(
97 const webrtc::DesktopSize& view_size,
98 const webrtc::DesktopRect& clip_area);
99 const webrtc::DesktopRegion* GetBufferShape();
100
101 private:
102 friend class base::RefCountedThreadSafe<Core>;
103 virtual ~Core();
104
105 // Paints the invalidated region to the next available buffer and returns it
106 // to the consumer.
107 void SchedulePaint();
108 void DoPaint();
109
110 // Decodes the contents of |packet|. DecodePacket may keep a reference to
111 // |packet| so the |packet| must remain alive and valid until |done| is
112 // executed.
113 void DecodePacket(scoped_ptr<VideoPacket> packet, const base::Closure& done);
114
115 // Callback method when a VideoPacket is processed. |decode_start| contains
116 // the timestamp when the packet will start to be processed.
117 void OnPacketDone(base::Time decode_start, const base::Closure& done);
118
119 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
120 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner_;
121 scoped_refptr<FrameConsumerProxy> consumer_;
122 scoped_ptr<VideoDecoder> decoder_;
123
124 // Remote screen size in pixels.
125 webrtc::DesktopSize source_size_;
126
127 // Vertical and horizontal DPI of the remote screen.
128 webrtc::DesktopVector source_dpi_;
129
130 // The current dimensions of the frame consumer view.
131 webrtc::DesktopSize view_size_;
132 webrtc::DesktopRect clip_area_;
133
134 // The drawing buffers supplied by the frame consumer.
135 std::list<webrtc::DesktopFrame*> buffers_;
136
137 // Flag used to coalesce runs of SchedulePaint()s into a single DoPaint().
138 bool paint_scheduled_;
139
140 ChromotingStats stats_;
Wez 2014/01/14 16:23:14 If you move ProcessVideoPacket and OnDecodeDone ba
Sergey Ulanov 2014/01/15 00:58:17 Done.
141
142 // Keep track of the most recent sequence number bounced back from the host.
143 int64 latest_sequence_number_;
144 };
145
146 RectangleUpdateDecoder::Core::Core(
78 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, 147 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
79 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner, 148 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner,
80 scoped_refptr<FrameConsumerProxy> consumer) 149 scoped_refptr<FrameConsumerProxy> consumer)
81 : main_task_runner_(main_task_runner), 150 : main_task_runner_(main_task_runner),
82 decode_task_runner_(decode_task_runner), 151 decode_task_runner_(decode_task_runner),
83 consumer_(consumer), 152 consumer_(consumer),
84 paint_scheduled_(false), 153 paint_scheduled_(false),
85 latest_sequence_number_(0) { 154 latest_sequence_number_(0) {
86 } 155 }
87 156
88 RectangleUpdateDecoder::~RectangleUpdateDecoder() { 157 RectangleUpdateDecoder::Core::~Core() {
89 } 158 }
90 159
91 void RectangleUpdateDecoder::Initialize(const SessionConfig& config) { 160 void RectangleUpdateDecoder::Core::Initialize(const SessionConfig& config) {
92 if (!decode_task_runner_->BelongsToCurrentThread()) { 161 if (!decode_task_runner_->BelongsToCurrentThread()) {
Wez 2014/01/14 16:23:14 Since RUD runs on main thread and RUD::Core on dec
Sergey Ulanov 2014/01/15 00:58:17 Done.
93 decode_task_runner_->PostTask( 162 decode_task_runner_->PostTask(
94 FROM_HERE, base::Bind(&RectangleUpdateDecoder::Initialize, this, 163 FROM_HERE, base::Bind(&RectangleUpdateDecoder::Core::Initialize, this,
95 config)); 164 config));
96 return; 165 return;
97 } 166 }
98 167
99 // Initialize decoder based on the selected codec. 168 // Initialize decoder based on the selected codec.
100 ChannelConfig::Codec codec = config.video_config().codec; 169 ChannelConfig::Codec codec = config.video_config().codec;
101 if (codec == ChannelConfig::CODEC_VERBATIM) { 170 if (codec == ChannelConfig::CODEC_VERBATIM) {
102 decoder_.reset(new VideoDecoderVerbatim()); 171 decoder_.reset(new VideoDecoderVerbatim());
103 } else if (codec == ChannelConfig::CODEC_VP8) { 172 } else if (codec == ChannelConfig::CODEC_VP8) {
104 decoder_ = VideoDecoderVpx::CreateForVP8(); 173 decoder_ = VideoDecoderVpx::CreateForVP8();
105 } else if (codec == ChannelConfig::CODEC_VP9) { 174 } else if (codec == ChannelConfig::CODEC_VP9) {
106 decoder_ = VideoDecoderVpx::CreateForVP9(); 175 decoder_ = VideoDecoderVpx::CreateForVP9();
107 } else { 176 } else {
108 NOTREACHED() << "Invalid Encoding found: " << codec; 177 NOTREACHED() << "Invalid Encoding found: " << codec;
109 } 178 }
110 179
111 if (consumer_->GetPixelFormat() == FrameConsumer::FORMAT_RGBA) { 180 if (consumer_->GetPixelFormat() == FrameConsumer::FORMAT_RGBA) {
112 scoped_ptr<VideoDecoder> wrapper( 181 scoped_ptr<VideoDecoder> wrapper(
113 new RgbToBgrVideoDecoderFilter(decoder_.Pass())); 182 new RgbToBgrVideoDecoderFilter(decoder_.Pass()));
114 decoder_ = wrapper.Pass(); 183 decoder_ = wrapper.Pass();
115 } 184 }
116 } 185 }
117 186
118 void RectangleUpdateDecoder::DecodePacket(scoped_ptr<VideoPacket> packet, 187 void RectangleUpdateDecoder::Core::DecodePacket(scoped_ptr<VideoPacket> packet,
119 const base::Closure& done) { 188 const base::Closure& done) {
120 DCHECK(decode_task_runner_->BelongsToCurrentThread()); 189 DCHECK(decode_task_runner_->BelongsToCurrentThread());
121 190
122 base::ScopedClosureRunner done_runner(done); 191 base::ScopedClosureRunner done_runner(done);
123 192
124 bool decoder_needs_reset = false; 193 bool decoder_needs_reset = false;
125 bool notify_size_or_dpi_change = false; 194 bool notify_size_or_dpi_change = false;
126 195
127 // If the packet includes screen size or DPI information, store them. 196 // If the packet includes screen size or DPI information, store them.
128 if (packet->format().has_screen_width() && 197 if (packet->format().has_screen_width() &&
(...skipping 24 matching lines...) Expand all
153 if (notify_size_or_dpi_change) 222 if (notify_size_or_dpi_change)
154 consumer_->SetSourceSize(source_size_, source_dpi_); 223 consumer_->SetSourceSize(source_size_, source_dpi_);
155 224
156 if (decoder_->DecodePacket(*packet.get())) { 225 if (decoder_->DecodePacket(*packet.get())) {
157 SchedulePaint(); 226 SchedulePaint();
158 } else { 227 } else {
159 LOG(ERROR) << "DecodePacket() failed."; 228 LOG(ERROR) << "DecodePacket() failed.";
160 } 229 }
161 } 230 }
162 231
163 void RectangleUpdateDecoder::SchedulePaint() { 232 void RectangleUpdateDecoder::Core::SchedulePaint() {
164 if (paint_scheduled_) 233 if (paint_scheduled_)
165 return; 234 return;
166 paint_scheduled_ = true; 235 paint_scheduled_ = true;
167 decode_task_runner_->PostTask( 236 decode_task_runner_->PostTask(
168 FROM_HERE, base::Bind(&RectangleUpdateDecoder::DoPaint, this)); 237 FROM_HERE, base::Bind(&RectangleUpdateDecoder::Core::DoPaint, this));
169 } 238 }
170 239
171 void RectangleUpdateDecoder::DoPaint() { 240 void RectangleUpdateDecoder::Core::DoPaint() {
172 DCHECK(paint_scheduled_); 241 DCHECK(paint_scheduled_);
173 paint_scheduled_ = false; 242 paint_scheduled_ = false;
174 243
175 // If the view size is empty or we have no output buffers ready, return. 244 // If the view size is empty or we have no output buffers ready, return.
176 if (buffers_.empty() || view_size_.is_empty()) 245 if (buffers_.empty() || view_size_.is_empty())
177 return; 246 return;
178 247
179 // If no Decoder is initialized, or the host dimensions are empty, return. 248 // If no Decoder is initialized, or the host dimensions are empty, return.
180 if (!decoder_.get() || source_size_.is_empty()) 249 if (!decoder_.get() || source_size_.is_empty())
181 return; 250 return;
182 251
183 // Draw the invalidated region to the buffer. 252 // Draw the invalidated region to the buffer.
184 webrtc::DesktopFrame* buffer = buffers_.front(); 253 webrtc::DesktopFrame* buffer = buffers_.front();
185 webrtc::DesktopRegion output_region; 254 webrtc::DesktopRegion output_region;
186 decoder_->RenderFrame(view_size_, clip_area_, 255 decoder_->RenderFrame(view_size_, clip_area_,
187 buffer->data(), 256 buffer->data(), buffer->stride(), &output_region);
188 buffer->stride(),
189 &output_region);
190 257
191 // Notify the consumer that painting is done. 258 // Notify the consumer that painting is done.
192 if (!output_region.is_empty()) { 259 if (!output_region.is_empty()) {
193 buffers_.pop_front(); 260 buffers_.pop_front();
194 consumer_->ApplyBuffer(view_size_, clip_area_, buffer, output_region); 261 consumer_->ApplyBuffer(view_size_, clip_area_, buffer, output_region);
195 } 262 }
196 } 263 }
197 264
198 void RectangleUpdateDecoder::RequestReturnBuffers(const base::Closure& done) { 265 void RectangleUpdateDecoder::Core::RequestReturnBuffers(
266 const base::Closure& done) {
199 if (!decode_task_runner_->BelongsToCurrentThread()) { 267 if (!decode_task_runner_->BelongsToCurrentThread()) {
200 decode_task_runner_->PostTask( 268 decode_task_runner_->PostTask(
201 FROM_HERE, base::Bind(&RectangleUpdateDecoder::RequestReturnBuffers, 269 FROM_HERE, base::Bind(
202 this, done)); 270 &RectangleUpdateDecoder::Core::RequestReturnBuffers, this, done));
203 return; 271 return;
204 } 272 }
205 273
206 while (!buffers_.empty()) { 274 while (!buffers_.empty()) {
207 consumer_->ReturnBuffer(buffers_.front()); 275 consumer_->ReturnBuffer(buffers_.front());
208 buffers_.pop_front(); 276 buffers_.pop_front();
209 } 277 }
210 278
211 if (!done.is_null()) 279 if (!done.is_null())
212 done.Run(); 280 done.Run();
213 } 281 }
214 282
215 void RectangleUpdateDecoder::DrawBuffer(webrtc::DesktopFrame* buffer) { 283 void RectangleUpdateDecoder::Core::DrawBuffer(webrtc::DesktopFrame* buffer) {
216 if (!decode_task_runner_->BelongsToCurrentThread()) { 284 if (!decode_task_runner_->BelongsToCurrentThread()) {
217 decode_task_runner_->PostTask( 285 decode_task_runner_->PostTask(
218 FROM_HERE, base::Bind(&RectangleUpdateDecoder::DrawBuffer, 286 FROM_HERE, base::Bind(&RectangleUpdateDecoder::Core::DrawBuffer,
219 this, buffer)); 287 this, buffer));
220 return; 288 return;
221 } 289 }
222 290
223 DCHECK(clip_area_.width() <= buffer->size().width() && 291 DCHECK(clip_area_.width() <= buffer->size().width() &&
224 clip_area_.height() <= buffer->size().height()); 292 clip_area_.height() <= buffer->size().height());
225 293
226 buffers_.push_back(buffer); 294 buffers_.push_back(buffer);
227 SchedulePaint(); 295 SchedulePaint();
228 } 296 }
229 297
230 void RectangleUpdateDecoder::InvalidateRegion( 298 void RectangleUpdateDecoder::Core::InvalidateRegion(
231 const webrtc::DesktopRegion& region) { 299 const webrtc::DesktopRegion& region) {
232 if (!decode_task_runner_->BelongsToCurrentThread()) { 300 if (!decode_task_runner_->BelongsToCurrentThread()) {
233 decode_task_runner_->PostTask( 301 decode_task_runner_->PostTask(
234 FROM_HERE, base::Bind(&RectangleUpdateDecoder::InvalidateRegion, 302 FROM_HERE, base::Bind(&RectangleUpdateDecoder::Core::InvalidateRegion,
235 this, region)); 303 this, region));
236 return; 304 return;
237 } 305 }
238 306
239 if (decoder_.get()) { 307 if (decoder_.get()) {
240 decoder_->Invalidate(view_size_, region); 308 decoder_->Invalidate(view_size_, region);
241 SchedulePaint(); 309 SchedulePaint();
242 } 310 }
243 } 311 }
244 312
245 void RectangleUpdateDecoder::SetOutputSizeAndClip( 313 void RectangleUpdateDecoder::Core::SetOutputSizeAndClip(
246 const webrtc::DesktopSize& view_size, 314 const webrtc::DesktopSize& view_size,
247 const webrtc::DesktopRect& clip_area) { 315 const webrtc::DesktopRect& clip_area) {
248 if (!decode_task_runner_->BelongsToCurrentThread()) { 316 if (!decode_task_runner_->BelongsToCurrentThread()) {
249 decode_task_runner_->PostTask( 317 decode_task_runner_->PostTask(
250 FROM_HERE, base::Bind(&RectangleUpdateDecoder::SetOutputSizeAndClip, 318 FROM_HERE,
251 this, view_size, clip_area)); 319 base::Bind(&RectangleUpdateDecoder::Core::SetOutputSizeAndClip,
320 this, view_size, clip_area));
252 return; 321 return;
253 } 322 }
254 323
255 // The whole frame needs to be repainted if the scaling factor has changed. 324 // The whole frame needs to be repainted if the scaling factor has changed.
256 if (!view_size_.equals(view_size) && decoder_.get()) { 325 if (!view_size_.equals(view_size) && decoder_.get()) {
257 webrtc::DesktopRegion region; 326 webrtc::DesktopRegion region;
258 region.AddRect(webrtc::DesktopRect::MakeSize(view_size)); 327 region.AddRect(webrtc::DesktopRect::MakeSize(view_size));
259 decoder_->Invalidate(view_size, region); 328 decoder_->Invalidate(view_size, region);
260 } 329 }
261 330
(...skipping 12 matching lines...) Expand all
274 i = buffers_.erase(i); 343 i = buffers_.erase(i);
275 } else { 344 } else {
276 ++i; 345 ++i;
277 } 346 }
278 } 347 }
279 348
280 SchedulePaint(); 349 SchedulePaint();
281 } 350 }
282 } 351 }
283 352
284 const webrtc::DesktopRegion* RectangleUpdateDecoder::GetBufferShape() { 353 const webrtc::DesktopRegion* RectangleUpdateDecoder::Core::GetBufferShape() {
285 return decoder_->GetImageShape(); 354 return decoder_->GetImageShape();
286 } 355 }
287 356
288 void RectangleUpdateDecoder::ProcessVideoPacket(scoped_ptr<VideoPacket> packet, 357 void RectangleUpdateDecoder::Core::ProcessVideoPacket(
289 const base::Closure& done) { 358 scoped_ptr<VideoPacket> packet,
359 const base::Closure& done) {
290 DCHECK(main_task_runner_->BelongsToCurrentThread()); 360 DCHECK(main_task_runner_->BelongsToCurrentThread());
291 361
292 // If the video packet is empty then drop it. Empty packets are used to 362 // If the video packet is empty then drop it. Empty packets are used to
293 // maintain activity on the network. 363 // maintain activity on the network.
294 if (!packet->has_data() || packet->data().size() == 0) { 364 if (!packet->has_data() || packet->data().size() == 0) {
295 done.Run(); 365 done.Run();
296 return; 366 return;
297 } 367 }
298 368
299 // Add one frame to the counter. 369 // Add one frame to the counter.
(...skipping 11 matching lines...) Expand all
311 base::TimeDelta round_trip_latency = 381 base::TimeDelta round_trip_latency =
312 base::Time::Now() - 382 base::Time::Now() -
313 base::Time::FromInternalValue(packet->client_sequence_number()); 383 base::Time::FromInternalValue(packet->client_sequence_number());
314 stats_.round_trip_ms()->Record(round_trip_latency.InMilliseconds()); 384 stats_.round_trip_ms()->Record(round_trip_latency.InMilliseconds());
315 } 385 }
316 386
317 // Measure the latency between the last packet being received and presented. 387 // Measure the latency between the last packet being received and presented.
318 base::Time decode_start = base::Time::Now(); 388 base::Time decode_start = base::Time::Now();
319 389
320 base::Closure decode_done = base::Bind( 390 base::Closure decode_done = base::Bind(
321 &RectangleUpdateDecoder::OnPacketDone, this, decode_start, done); 391 &RectangleUpdateDecoder::Core::OnPacketDone, this, decode_start, done);
Wez 2014/01/14 16:23:14 You can put all this code in RectangleUpdateDecode
Sergey Ulanov 2014/01/15 00:58:17 Done.
322 392
323 decode_task_runner_->PostTask(FROM_HERE, base::Bind( 393 decode_task_runner_->PostTask(FROM_HERE, base::Bind(
324 &RectangleUpdateDecoder::DecodePacket, this, 394 &RectangleUpdateDecoder::Core::DecodePacket, this,
325 base::Passed(&packet), decode_done)); 395 base::Passed(&packet), decode_done));
326 } 396 }
327 397
328 void RectangleUpdateDecoder::OnPacketDone(base::Time decode_start, 398 void RectangleUpdateDecoder::Core::OnPacketDone(base::Time decode_start,
329 const base::Closure& done) { 399 const base::Closure& done) {
330 if (!main_task_runner_->BelongsToCurrentThread()) { 400 if (!main_task_runner_->BelongsToCurrentThread()) {
Wez 2014/01/14 16:23:14 Since this method just thread-hops back to |main_t
Sergey Ulanov 2014/01/15 00:58:17 Done.
331 main_task_runner_->PostTask(FROM_HERE, base::Bind( 401 main_task_runner_->PostTask(FROM_HERE, base::Bind(
332 &RectangleUpdateDecoder::OnPacketDone, this, 402 &RectangleUpdateDecoder::Core::OnPacketDone, this,
333 decode_start, done)); 403 decode_start, done));
334 return; 404 return;
335 } 405 }
336 406
337 // Record the latency between the packet being received and presented. 407 // Record the latency between the packet being received and presented.
338 stats_.video_decode_ms()->Record( 408 stats_.video_decode_ms()->Record(
339 (base::Time::Now() - decode_start).InMilliseconds()); 409 (base::Time::Now() - decode_start).InMilliseconds());
340 410
341 done.Run(); 411 done.Run();
342 } 412 }
343 413
344 ChromotingStats* RectangleUpdateDecoder::GetStats() { 414 ChromotingStats* RectangleUpdateDecoder::Core::GetStats() {
345 DCHECK(main_task_runner_->BelongsToCurrentThread()); 415 DCHECK(main_task_runner_->BelongsToCurrentThread());
346 return &stats_; 416 return &stats_;
347 } 417 }
348 418
419 RectangleUpdateDecoder::RectangleUpdateDecoder(
420 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
421 scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner,
422 scoped_refptr<FrameConsumerProxy> consumer)
423 : core_(new Core(main_task_runner, decode_task_runner, consumer)) {
424 }
425
426 void RectangleUpdateDecoder::Initialize(const protocol::SessionConfig& config) {
427 core_->Initialize(config);
428 }
429
430 ChromotingStats* RectangleUpdateDecoder::GetStats() {
431 return core_->GetStats();
432 }
433
434 void RectangleUpdateDecoder::ProcessVideoPacket(scoped_ptr<VideoPacket> packet,
435 const base::Closure& done) {
436 core_->ProcessVideoPacket(packet.Pass(), done);
Wez 2014/01/14 16:23:14 As with Initialize, where we're just proxying call
Sergey Ulanov 2014/01/15 00:58:17 Done.
437 }
438
439 void RectangleUpdateDecoder::DrawBuffer(webrtc::DesktopFrame* buffer) {
440 core_->DrawBuffer(buffer);
441 }
442
443 void RectangleUpdateDecoder::InvalidateRegion(
444 const webrtc::DesktopRegion& region) {
445 core_->InvalidateRegion(region);
446 }
447
448 void RectangleUpdateDecoder::RequestReturnBuffers(const base::Closure& done) {
449 core_->RequestReturnBuffers(done);
450 }
451
452 void RectangleUpdateDecoder::SetOutputSizeAndClip(
453 const webrtc::DesktopSize& view_size,
454 const webrtc::DesktopRect& clip_area) {
455 core_->SetOutputSizeAndClip(view_size, clip_area);
456 }
457
458 const webrtc::DesktopRegion* RectangleUpdateDecoder::GetBufferShape() {
459 return core_->GetBufferShape();
460 }
461
462
349 } // namespace remoting 463 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698