summaryrefslogtreecommitdiffstats
path: root/remoting/host/session_manager.cc
diff options
context:
space:
mode:
authorgarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
committergarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
commitcb3b1f93130040a150e7fcc57cd4d5a75569685a (patch)
treeff93665e3c1478c61663d1107cd42dc25b31448d /remoting/host/session_manager.cc
parentb0110e822ac2b2db56d1b1542aad06da573cd544 (diff)
downloadchromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.zip
chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.gz
chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.bz2
Copy the (early prototype of) remoting in Chrome into the public tree.
At the moment, this is a semi-functional demo. BUG=none TEST=build/run all unittests on linux Review URL: http://codereview.chromium.org/2690003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49087 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/host/session_manager.cc')
-rw-r--r--remoting/host/session_manager.cc401
1 files changed, 401 insertions, 0 deletions
diff --git a/remoting/host/session_manager.cc b/remoting/host/session_manager.cc
new file mode 100644
index 0000000..da16a55
--- /dev/null
+++ b/remoting/host/session_manager.cc
@@ -0,0 +1,401 @@
+// 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/host/session_manager.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "media/base/data_buffer.h"
+#include "remoting/base/protocol_decoder.h"
+#include "remoting/host/client_connection.h"
+#include "remoting/host/encoder.h"
+
+namespace remoting {
+
+// By default we capture 20 times a second. This number is obtained by
+// experiment to provide good latency.
+static const double kDefaultCaptureRate = 20.0;
+
+// Interval that we perform rate regulation.
+static const base::TimeDelta kRateControlInterval =
+ base::TimeDelta::FromSeconds(1);
+
+// We divide the pending update stream number by this value to determine the
+// rate divider.
+static const int kSlowDownFactor = 10;
+
+// A list of dividers used to divide the max rate to determine the current
+// capture rate.
+static const int kRateDividers[] = {1, 2, 4, 8, 16};
+
+SessionManager::SessionManager(
+ MessageLoop* capture_loop,
+ MessageLoop* encode_loop,
+ MessageLoop* network_loop,
+ Capturer* capturer,
+ Encoder* encoder)
+ : capture_loop_(capture_loop),
+ encode_loop_(encode_loop),
+ network_loop_(network_loop),
+ capturer_(capturer),
+ encoder_(encoder),
+ rate_(kDefaultCaptureRate),
+ max_rate_(kDefaultCaptureRate),
+ started_(false),
+ recordings_(0),
+ rate_control_started_(false),
+ capture_width_(0),
+ capture_height_(0),
+ capture_pixel_format_(chromotocol_pb::PixelFormatInvalid),
+ encode_stream_started_(false),
+ encode_done_(false) {
+ DCHECK(capture_loop_);
+ DCHECK(encode_loop_);
+ DCHECK(network_loop_);
+}
+
+SessionManager::~SessionManager() {
+ clients_.clear();
+ DCHECK_EQ(0u, clients_.size());
+}
+
+void SessionManager::Start() {
+ capture_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &SessionManager::DoStart));
+}
+
+void SessionManager::DoStart() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ if (started_) {
+ NOTREACHED() << "Record session already started";
+ return;
+ }
+
+ started_ = true;
+ DoCapture();
+
+ // Starts the rate regulation.
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoStartRateControl));
+}
+
+void SessionManager::DoStartRateControl() {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ if (rate_control_started_) {
+ NOTREACHED() << "Rate regulation already started";
+ return;
+ }
+ rate_control_started_ = true;
+ ScheduleNextRateControl();
+}
+
+void SessionManager::Pause() {
+ capture_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &SessionManager::DoPause));
+}
+
+void SessionManager::DoPause() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ if (!started_) {
+ NOTREACHED() << "Record session not started";
+ return;
+ }
+
+ started_ = false;
+
+ // Pause the rate regulation.
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoPauseRateControl));
+}
+
+void SessionManager::DoPauseRateControl() {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ if (!rate_control_started_) {
+ NOTREACHED() << "Rate regulation not started";
+ return;
+ }
+ rate_control_started_ = false;
+}
+
+void SessionManager::SetMaxRate(double rate) {
+ capture_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &SessionManager::DoSetMaxRate, rate));
+}
+
+void SessionManager::AddClient(scoped_refptr<ClientConnection> client) {
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoAddClient, client));
+}
+
+void SessionManager::RemoveClient(scoped_refptr<ClientConnection> client) {
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoRemoveClient, client));
+}
+
+void SessionManager::DoCapture() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ // Make sure we have at most two oustanding recordings. We can simply return
+ // if we can't make a capture now, the next capture will be started by the
+ // end of an encode operation.
+ if (recordings_ >= 2 || !started_)
+ return;
+
+ base::Time now = base::Time::Now();
+ base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
+ static_cast<int>(base::Time::kMillisecondsPerSecond / rate_));
+ base::TimeDelta elapsed = now - last_capture_time_;
+
+ // If this method is called sonner than the required interval we return
+ // immediately
+ if (elapsed < interval)
+ return;
+
+ // At this point we are going to perform one capture so save the current time.
+ last_capture_time_ = now;
+ ++recordings_;
+
+ // Before we actually do a capture, schedule the next one.
+ ScheduleNextCapture();
+
+ // And finally perform one capture.
+ DCHECK(capturer_.get());
+ capturer_->CaptureDirtyRects(
+ NewRunnableMethod(this, &SessionManager::CaptureDoneTask));
+}
+
+void SessionManager::DoFinishEncode() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ // Decrement the number of recording in process since we have completed
+ // one cycle.
+ --recordings_;
+
+ // Try to do a capture again. Note that the following method may do nothing
+ // if it is too early to perform a capture.
+ if (rate_ > 0)
+ DoCapture();
+}
+
+void SessionManager::DoEncode() {
+ DCHECK_EQ(encode_loop_, MessageLoop::current());
+
+ // Reset states about the encode stream.
+ encode_done_ = false;
+ encode_stream_started_ = false;
+
+ DCHECK(!encoded_data_.get());
+ DCHECK(encoder_.get());
+
+ // TODO(hclam): Enable |force_refresh| if a new client was
+ // added.
+ encoder_->SetSize(capture_width_, capture_height_);
+ encoder_->SetPixelFormat(capture_pixel_format_);
+ encoder_->Encode(
+ capture_dirty_rects_,
+ capture_data_,
+ capture_data_strides_,
+ false,
+ &encoded_data_header_,
+ &encoded_data_,
+ &encode_done_,
+ NewRunnableMethod(this, &SessionManager::EncodeDataAvailableTask));
+}
+
+void SessionManager::DoSendUpdate(
+ chromotocol_pb::UpdateStreamPacketHeader* header,
+ scoped_refptr<media::DataBuffer> encoded_data,
+ bool begin_update, bool end_update) {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ for (size_t i = 0; i < clients_.size(); ++i) {
+ if (begin_update)
+ clients_[i]->SendBeginUpdateStreamMessage();
+
+ // This will pass the ownership of the DataBuffer to the ClientConnection.
+ clients_[i]->SendUpdateStreamPacketMessage(header, encoded_data);
+
+ if (end_update)
+ clients_[i]->SendEndUpdateStreamMessage();
+ }
+}
+
+void SessionManager::DoSendInit(scoped_refptr<ClientConnection> client,
+ int width, int height) {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ // Sends the client init information.
+ client->SendInitClientMessage(width, height);
+}
+
+void SessionManager::DoGetInitInfo(scoped_refptr<ClientConnection> client) {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoSendInit, client,
+ capturer_->GetWidth(), capturer_->GetHeight()));
+}
+
+void SessionManager::DoSetRate(double rate) {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+ if (rate == rate_)
+ return;
+
+ // Change the current capture rate.
+ rate_ = rate;
+
+ // If we have already started then schedule the next capture with the new
+ // rate.
+ if (started_)
+ ScheduleNextCapture();
+}
+
+void SessionManager::DoSetMaxRate(double max_rate) {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ // TODO(hclam): Should also check for small epsilon.
+ if (max_rate != 0) {
+ max_rate_ = max_rate;
+ DoSetRate(max_rate);
+ } else {
+ NOTREACHED() << "Rate is too small.";
+ }
+}
+
+void SessionManager::DoAddClient(scoped_refptr<ClientConnection> client) {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ // TODO(hclam): Force a full frame for next encode.
+ clients_.push_back(client);
+
+ // Gets the init information for the client.
+ capture_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoGetInitInfo, client));
+}
+
+void SessionManager::DoRemoveClient(scoped_refptr<ClientConnection> client) {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ // TODO(hclam): Is it correct to do to a scoped_refptr?
+ ClientConnectionList::iterator it
+ = std::find(clients_.begin(), clients_.end(), client);
+ if (it != clients_.end())
+ clients_.erase(it);
+}
+
+void SessionManager::DoRateControl() {
+ DCHECK_EQ(network_loop_, MessageLoop::current());
+
+ // If we have been paused then shutdown the rate regulation loop.
+ if (!rate_control_started_)
+ return;
+
+ int max_pending_update_streams = 0;
+ for (size_t i = 0; i < clients_.size(); ++i) {
+ max_pending_update_streams =
+ std::max(max_pending_update_streams,
+ clients_[i]->GetPendingUpdateStreamMessages());
+ }
+
+ // If |slow_down| equals zero, we have no slow down.
+ int slow_down = max_pending_update_streams / kSlowDownFactor;
+ // Set new_rate to -1 for checking later.
+ double new_rate = -1;
+ // If the slow down is too large.
+ if (slow_down >= arraysize(kRateDividers)) {
+ // Then we stop the capture completely.
+ new_rate = 0;
+ } else {
+ // Slow down the capture rate using the divider.
+ new_rate = max_rate_ / kRateDividers[slow_down];
+ }
+ DCHECK_NE(new_rate, -1.0);
+
+ // Then set the rate.
+ capture_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoSetRate, new_rate));
+ ScheduleNextRateControl();
+}
+
+void SessionManager::ScheduleNextCapture() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ if (rate_ == 0)
+ return;
+
+ base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
+ static_cast<int>(base::Time::kMillisecondsPerSecond / rate_));
+ capture_loop_->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoCapture),
+ interval.InMilliseconds());
+}
+
+void SessionManager::ScheduleNextRateControl() {
+ network_loop_->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &SessionManager::DoRateControl),
+ kRateControlInterval.InMilliseconds());
+}
+
+void SessionManager::CaptureDoneTask() {
+ DCHECK_EQ(capture_loop_, MessageLoop::current());
+
+ // Save results of the capture.
+ capturer_->GetData(capture_data_);
+ capturer_->GetDataStride(capture_data_strides_);
+ capture_dirty_rects_.clear();
+ capturer_->GetDirtyRects(&capture_dirty_rects_);
+ capture_pixel_format_ = capturer_->GetPixelFormat();
+ capture_width_ = capturer_->GetWidth();
+ capture_height_ = capturer_->GetHeight();
+
+ encode_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &SessionManager::DoEncode));
+}
+
+void SessionManager::EncodeDataAvailableTask() {
+ DCHECK_EQ(encode_loop_, MessageLoop::current());
+
+ // Before a new encode task starts, notify clients a new update
+ // stream is coming.
+ // Notify this will keep a reference to the DataBuffer in the
+ // task. The ownership will eventually pass to the ClientConnections.
+ network_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &SessionManager::DoSendUpdate,
+ &encoded_data_header_,
+ encoded_data_,
+ !encode_stream_started_,
+ encode_done_));
+
+ // Since we have received data from the Encoder, mark the encode
+ // stream has started.
+ encode_stream_started_ = true;
+
+ // Give up the ownership of DataBuffer since it is passed to
+ // the ClientConnections.
+ encoded_data_ = NULL;
+
+ if (encode_done_) {
+ capture_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &SessionManager::DoFinishEncode));
+ }
+}
+
+} // namespace remoting