summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/base/decoder.h24
-rw-r--r--remoting/base/protocol/chromotocol.proto2
-rw-r--r--remoting/client/frame_consumer.h59
-rw-r--r--remoting/client/rectangle_update_decoder.cc218
-rw-r--r--remoting/client/rectangle_update_decoder.h60
-rw-r--r--remoting/remoting.gyp3
6 files changed, 365 insertions, 1 deletions
diff --git a/remoting/base/decoder.h b/remoting/base/decoder.h
index 75ffa03..c8c77a7 100644
--- a/remoting/base/decoder.h
+++ b/remoting/base/decoder.h
@@ -90,6 +90,30 @@ class Decoder {
// of BeginDecode() / EndDecode().
virtual bool IsStarted() { return started_; }
+ // --- NEW API ---
+ // TODO(ajwong): This API is incorrect in the face of a streaming decode
+ // protocol like VP8. However, it breaks the layering abstraction by
+ // depending on the network packet protocol buffer type. I'm going to go
+ // forward with it as is, and then refactor again to support streaming
+ // decodes.
+
+ // Initializes the decoder to draw into the given |frame|. The |clip|
+ // specifies the region to draw into. The clip region must fit inside
+ // the dimensions of frame. Failure to do so will CHECK Fail.
+ virtual void Initialize(scoped_refptr<media::VideoFrame> frame,
+ const gfx::Rect& clip) {}
+
+ // Reset the decoder to an uninitialized state. Release all references to
+ // the initialized |frame|. Initialize() must be called before the decoder
+ // is used again.
+ virtual void Reset() {}
+
+ // Feeds more data into the decoder.
+ virtual void DecodeBytes(const std::string& encoded_bytes) {}
+
+ // Returns true if decoder is ready to accept data via ProcessRectangleData.
+ virtual bool IsReadyForData() { return false; }
+
protected:
// Every decoder will have two internal states because there are three
// kinds of messages send to PartialDecode().
diff --git a/remoting/base/protocol/chromotocol.proto b/remoting/base/protocol/chromotocol.proto
index b3fa848..865fd3f 100644
--- a/remoting/base/protocol/chromotocol.proto
+++ b/remoting/base/protocol/chromotocol.proto
@@ -138,7 +138,7 @@ message RectangleUpdatePacket {
optional int32 sequence_number = 2 [default = 0];
// This is provided on the first packet of the rectangle data, when
- // the sequence_number is 0.
+ // the flags has FIRST_PACKET set.
optional RectangleFormat format = 3;
optional bytes encoded_rect = 4;
diff --git a/remoting/client/frame_consumer.h b/remoting/client/frame_consumer.h
new file mode 100644
index 0000000..05c4448
--- /dev/null
+++ b/remoting/client/frame_consumer.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 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.
+
+#ifndef REMOTING_CLIENT_FRAME_CONSUMER_H_
+#define REMOTING_CLIENT_FRAME_CONSUMER_H_
+
+namespace remoting {
+
+class FrameConsumer {
+ public:
+ FrameConsumer() {}
+ virtual ~FrameConsumer() {}
+
+ // Request a frame be allocated from the FrameConsumer.
+ //
+ // If a frame cannot be allocated to fit the format, and height/width
+ // requirements, |frame_out| will be set to NULL.
+ //
+ // An allocated frame will have at least the width and height requested, but
+ // may be bigger. Query the retrun frame for the actual frame size, stride,
+ // etc.
+ //
+ // The AllocateFrame call is asynchronous. From invocation, until when the
+ // |done| callback is invoked, |frame_out| should be considered to be locked
+ // by the FrameConsumer, must remain a valid pointer, and should not be
+ // examined or modified. After |done| is called, the |frame_out| will
+ // contain a result of the allocation. If a frame could not be allocated,
+ // |frame_out| will be NULL.
+ //
+ // All frames retrieved via the AllocateFrame call must be released by a
+ // corresponding call ReleaseFrame(scoped_refptr<VideoFrame>* frame_out.
+ virtual void AllocateFrame(media::VideoFrame::Format format,
+ size_t width,
+ size_t height,
+ base::TimeDelta timestamp,
+ base::TimeDelta duration,
+ scoped_refptr<media::VideoFrame>* frame_out,
+ Task* done) = 0;
+
+ virtual void ReleaseFrame(media::VideoFrame* frame) = 0;
+
+ // OnPartialFrameOutput() is called every time at least one rectangle of
+ // output is produced. The |frame| is guaranteed to have valid data for
+ // every region included in the |rects| list.
+ //
+ // Both |frame| and |rects| are guaranteed to be valid until the |done|
+ // callback is invoked.
+ virtual void OnPartialFrameOutput(media::VideoFrame* frame,
+ UpdatedRects* rects,
+ Task* done) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FrameConsumer);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_CLIENT_FRAME_CONSUMER_H_
diff --git a/remoting/client/rectangle_update_decoder.cc b/remoting/client/rectangle_update_decoder.cc
new file mode 100644
index 0000000..9d34e2a
--- /dev/null
+++ b/remoting/client/rectangle_update_decoder.cc
@@ -0,0 +1,218 @@
+// Copyright (c) 2010 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 "remoting/client/rectangle_update_decoder.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "media/base/callback.h"
+#include "remoting/base/decoder.h"
+#include "remoting/base/decoder_verbatim.h"
+#include "remoting/base/decoder_zlib.h"
+#include "remoting/base/protocol/chromotocol.pb.h"
+#include "remoting/base/tracer.h"
+#include "remoting/client/frame_consumer.h"
+
+using media::AutoTaskRunner;
+
+namespace remoting {
+
+namespace {
+
+class PartialFrameCleanup : public Task {
+ public:
+ PartialFrameCleanup(media::VideoFrame* frame, UpdatedRects* rects)
+ : frame_(frame), rects_(rects) {
+ }
+
+ virtual void Run() {
+ delete rects_;
+ frame_ = NULL;
+ }
+
+ private:
+ scoped_refptr<media::VideoFrame> frame_;
+ UpdatedRects* rects_;
+};
+
+} // namespace
+
+RectangleUpdateDecoder::RectangleUpdateDecoder(MessageLoop* message_loop,
+ FrameConsumer* consumer)
+ : message_loop_(message_loop),
+ consumer_(consumer) {
+}
+
+RectangleUpdateDecoder::~RectangleUpdateDecoder() {
+}
+
+void RectangleUpdateDecoder::DecodePacket(const RectangleUpdatePacket& packet,
+ Task* done) {
+ if (message_loop_ != MessageLoop::current()) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewTracedMethod(this,
+ &RectangleUpdateDecoder::DecodePacket, packet,
+ done));
+ return;
+ }
+ AutoTaskRunner done_runner(done);
+
+ TraceContext::tracer()->PrintString("Decode Packet called.");
+
+ if (!IsValidPacket(packet)) {
+ LOG(ERROR) << "Received invalid packet.";
+ return;
+ }
+
+ Task* process_packet_data =
+ NewTracedMethod(this,
+ &RectangleUpdateDecoder::ProcessPacketData,
+ packet, done_runner.release());
+
+ if (packet.flags() | RectangleUpdatePacket::FIRST_PACKET) {
+ const RectangleFormat& format = packet.format();
+
+ InitializeDecoder(format, process_packet_data);
+ } else {
+ process_packet_data->Run();
+ delete process_packet_data;
+ }
+}
+
+void RectangleUpdateDecoder::ProcessPacketData(
+ const RectangleUpdatePacket& packet,
+ Task* done) {
+ AutoTaskRunner done_runner(done);
+
+ if (!decoder_->IsReadyForData()) {
+ // TODO(ajwong): This whole thing should move into an invalid state.
+ LOG(ERROR) << "Decoder is unable to process data. Dropping packet.";
+ return;
+ }
+
+ TraceContext::tracer()->PrintString("Executing Decode.");
+ decoder_->DecodeBytes(packet.encoded_rect());
+
+ if (packet.flags() | RectangleUpdatePacket::LAST_PACKET) {
+ decoder_->Reset();
+
+ UpdatedRects* rects = new UpdatedRects();
+
+ // Empty out the list of current updated rects so the decoder can keep
+ // writing new ones while these are processed.
+ rects->swap(updated_rects_);
+
+ consumer_->OnPartialFrameOutput(frame_, rects,
+ new PartialFrameCleanup(frame_, rects));
+ }
+}
+
+// static
+bool RectangleUpdateDecoder::IsValidPacket(
+ const RectangleUpdatePacket& packet) {
+ if (!packet.IsInitialized()) {
+ LOG(WARNING) << "Protobuf consistency checks fail.";
+ return false;
+ }
+
+ // First packet must have a format.
+ if (packet.flags() | RectangleUpdatePacket::FIRST_PACKET) {
+ if (!packet.has_format()) {
+ LOG(WARNING) << "First packet must have format.";
+ return false;
+ }
+
+ // TODO(ajwong): Verify that we don't need to whitelist encodings.
+ const RectangleFormat& format = packet.format();
+ if (!format.has_encoding() ||
+ format.encoding() == EncodingInvalid) {
+ LOG(WARNING) << "Invalid encoding specified.";
+ return false;
+ }
+ }
+
+ // We shouldn't generate null packets.
+ if (!packet.has_encoded_rect()) {
+ LOG(WARNING) << "Packet w/o an encoded rectangle received.";
+ return false;
+ }
+
+ return true;
+}
+
+void RectangleUpdateDecoder::InitializeDecoder(const RectangleFormat& format,
+ Task* done) {
+ if (message_loop_ != MessageLoop::current()) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewTracedMethod(this,
+ &RectangleUpdateDecoder::InitializeDecoder,
+ format, done));
+ return;
+ }
+ AutoTaskRunner done_runner(done);
+
+ // Check if we need to request a new frame.
+ if (!frame_ ||
+ frame_->width() < static_cast<size_t>(format.width()) ||
+ frame_->height() < static_cast<size_t>(format.height())) {
+ if (frame_) {
+ TraceContext::tracer()->PrintString("Releasing old frame.");
+ consumer_->ReleaseFrame(frame_);
+ frame_ = NULL;
+ }
+ TraceContext::tracer()->PrintString("Requesting new frame.");
+ consumer_->AllocateFrame(media::VideoFrame::RGB32,
+ format.width(), format.height(),
+ base::TimeDelta(), base::TimeDelta(),
+ &frame_,
+ NewTracedMethod(
+ this,
+ &RectangleUpdateDecoder::InitializeDecoder,
+ format,
+ done_runner.release()));
+ return;
+ }
+
+ // TODO(ajwong): We need to handle the allocator failing to create a frame
+ // and properly disable this class.
+ CHECK(frame_);
+
+ if (decoder_.get()) {
+ // TODO(ajwong): We need to handle changing decoders midstream correctly
+ // since someone may feed us a corrupt stream, or manually create a
+ // stream that changes decoders. At the very leask, we should not
+ // crash the process.
+ //
+ // For now though, assume that only one encoding is used throughout.
+ //
+ // Note, this may be as simple as just deleting the current decoder.
+ // However, we need to verify the flushing semantics of the decoder first.
+ CHECK(decoder_->Encoding() == format.encoding());
+ } else {
+ // Initialize a new decoder based on this message encoding.
+ if (format.encoding() == EncodingNone) {
+ TraceContext::tracer()->PrintString("Creating Verbatim decoder.");
+ decoder_.reset(new DecoderVerbatim());
+ } else if (format.encoding() == EncodingZlib) {
+ TraceContext::tracer()->PrintString("Creating Zlib decoder");
+ decoder_.reset(new DecoderZlib());
+ } else {
+ NOTREACHED() << "Invalid Encoding found: " << format.encoding();
+ }
+ }
+
+ // TODO(ajwong): This can happen in the face of corrupt input data. Figure
+ // out the right behavior and make this more resilient.
+ CHECK(updated_rects_.empty());
+
+ gfx::Rect rectangle_size(format.x(), format.y(),
+ format.width(), format.height());
+ updated_rects_.push_back(rectangle_size);
+ decoder_->Initialize(frame_, rectangle_size);
+ TraceContext::tracer()->PrintString("Decoder is Initialized");
+}
+
+} // namespace remoting
diff --git a/remoting/client/rectangle_update_decoder.h b/remoting/client/rectangle_update_decoder.h
new file mode 100644
index 0000000..5f18e12
--- /dev/null
+++ b/remoting/client/rectangle_update_decoder.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2010 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.
+
+#ifndef REMOTING_CLIENT_RECTANGLE_UPDATE_DECODER_H
+#define REMOTING_CLIENT_RECTANGLE_UPDATE_DECODER_H
+
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+#include "media/base/video_frame.h"
+#include "remoting/base/decoder.h" // For UpdatedRects.
+
+class MessageLoop;
+
+namespace remoting {
+
+class FrameConsumer;
+class RectangleFormat;
+class RectangleUpdatePacket;
+
+// TODO(ajwong): Re-examine this API, especially with regards to how error
+// conditions on each step are reported. Should they be CHECKs? Logs? Other?
+class RectangleUpdateDecoder {
+ public:
+ RectangleUpdateDecoder(MessageLoop* message_loop,
+ FrameConsumer* consumer);
+ ~RectangleUpdateDecoder();
+
+ // Decodes the contents of |packet| calling OnPartialFrameOutput() in the
+ // regsitered as data is avaialable. DecodePacket may keep a reference to
+ // |packet| so the |packet| must remain alive and valid until |done| is
+ // executed.
+ //
+ // TODO(ajwong): Should packet be a const pointer to make the lifetime
+ // more clear?
+ void DecodePacket(const RectangleUpdatePacket& packet, Task* done);
+
+ private:
+ static bool IsValidPacket(const RectangleUpdatePacket& packet);
+
+ void InitializeDecoder(const RectangleFormat& format, Task* done);
+
+ void ProcessPacketData(const RectangleUpdatePacket& packet, Task* done);
+
+ // Pointers to infrastructure objects. Not owned.
+ MessageLoop* message_loop_;
+ FrameConsumer* consumer_;
+
+ scoped_ptr<Decoder> decoder_;
+ UpdatedRects updated_rects_;
+
+ // Framebuffer for the decoder.
+ scoped_refptr<media::VideoFrame> frame_;
+};
+
+} // namespace remoting
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::RectangleUpdateDecoder);
+
+#endif // REMOTING_CLIENT_RECTANGLE_UPDATE_DECODER_H
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 486526e..9f524ac 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -253,11 +253,14 @@
'client/client_context.h',
'client/client_util.cc',
'client/client_util.h',
+ 'client/frame_consumer.h',
'client/host_connection.h',
'client/input_handler.cc',
'client/input_handler.h',
'client/jingle_host_connection.cc',
'client/jingle_host_connection.h',
+ 'client/rectangle_update_decoder.cc',
+ 'client/rectangle_update_decoder.h',
],
}, # end of target 'chromoting_client'