Home | History | Annotate | Download | only in host
      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
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/desktop_session_agent.h"
      6 
      7 #include "base/files/file_util.h"
      8 #include "base/logging.h"
      9 #include "base/memory/shared_memory.h"
     10 #include "ipc/ipc_channel_proxy.h"
     11 #include "ipc/ipc_message.h"
     12 #include "ipc/ipc_message_macros.h"
     13 #include "remoting/base/auto_thread_task_runner.h"
     14 #include "remoting/base/constants.h"
     15 #include "remoting/host/audio_capturer.h"
     16 #include "remoting/host/chromoting_messages.h"
     17 #include "remoting/host/desktop_environment.h"
     18 #include "remoting/host/input_injector.h"
     19 #include "remoting/host/ipc_util.h"
     20 #include "remoting/host/remote_input_filter.h"
     21 #include "remoting/host/screen_controls.h"
     22 #include "remoting/host/screen_resolution.h"
     23 #include "remoting/proto/audio.pb.h"
     24 #include "remoting/proto/control.pb.h"
     25 #include "remoting/proto/event.pb.h"
     26 #include "remoting/protocol/clipboard_stub.h"
     27 #include "remoting/protocol/input_event_tracker.h"
     28 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
     29 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
     30 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
     31 #include "third_party/webrtc/modules/desktop_capture/shared_memory.h"
     32 
     33 namespace remoting {
     34 
     35 namespace {
     36 
     37 // Routes local clipboard events though the IPC channel to the network process.
     38 class DesktopSesssionClipboardStub : public protocol::ClipboardStub {
     39  public:
     40   explicit DesktopSesssionClipboardStub(
     41       scoped_refptr<DesktopSessionAgent> desktop_session_agent);
     42   virtual ~DesktopSesssionClipboardStub();
     43 
     44   // protocol::ClipboardStub implementation.
     45   virtual void InjectClipboardEvent(
     46       const protocol::ClipboardEvent& event) OVERRIDE;
     47 
     48  private:
     49   scoped_refptr<DesktopSessionAgent> desktop_session_agent_;
     50 
     51   DISALLOW_COPY_AND_ASSIGN(DesktopSesssionClipboardStub);
     52 };
     53 
     54 DesktopSesssionClipboardStub::DesktopSesssionClipboardStub(
     55     scoped_refptr<DesktopSessionAgent> desktop_session_agent)
     56     : desktop_session_agent_(desktop_session_agent) {
     57 }
     58 
     59 DesktopSesssionClipboardStub::~DesktopSesssionClipboardStub() {
     60 }
     61 
     62 void DesktopSesssionClipboardStub::InjectClipboardEvent(
     63     const protocol::ClipboardEvent& event) {
     64   desktop_session_agent_->InjectClipboardEvent(event);
     65 }
     66 
     67 }  // namespace
     68 
     69 // webrtc::SharedMemory implementation that notifies creating
     70 // DesktopSessionAgent when it's deleted.
     71 class DesktopSessionAgent::SharedBuffer : public webrtc::SharedMemory {
     72  public:
     73   static scoped_ptr<SharedBuffer> Create(DesktopSessionAgent* agent,
     74                                          size_t size,
     75                                          int id) {
     76     scoped_ptr<base::SharedMemory> memory(new base::SharedMemory());
     77     if (!memory->CreateAndMapAnonymous(size))
     78       return scoped_ptr<SharedBuffer>();
     79     return scoped_ptr<SharedBuffer>(
     80         new SharedBuffer(agent, memory.Pass(), size, id));
     81   }
     82 
     83   virtual ~SharedBuffer() {
     84     agent_->OnSharedBufferDeleted(id());
     85   }
     86 
     87  private:
     88   SharedBuffer(DesktopSessionAgent* agent,
     89                scoped_ptr<base::SharedMemory> memory,
     90                size_t size,
     91                int id)
     92       : SharedMemory(memory->memory(), size,
     93 #if defined(OS_WIN)
     94                      memory->handle(),
     95 #else
     96                      memory->handle().fd,
     97 #endif
     98                      id),
     99         agent_(agent),
    100         shared_memory_(memory.Pass()) {
    101   }
    102 
    103   DesktopSessionAgent* agent_;
    104   scoped_ptr<base::SharedMemory> shared_memory_;
    105 
    106   DISALLOW_COPY_AND_ASSIGN(SharedBuffer);
    107 };
    108 
    109 DesktopSessionAgent::Delegate::~Delegate() {
    110 }
    111 
    112 DesktopSessionAgent::DesktopSessionAgent(
    113     scoped_refptr<AutoThreadTaskRunner> audio_capture_task_runner,
    114     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    115     scoped_refptr<AutoThreadTaskRunner> input_task_runner,
    116     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    117     scoped_refptr<AutoThreadTaskRunner> video_capture_task_runner)
    118     : audio_capture_task_runner_(audio_capture_task_runner),
    119       caller_task_runner_(caller_task_runner),
    120       input_task_runner_(input_task_runner),
    121       io_task_runner_(io_task_runner),
    122       video_capture_task_runner_(video_capture_task_runner),
    123       next_shared_buffer_id_(1),
    124       shared_buffers_(0),
    125       started_(false),
    126       weak_factory_(this) {
    127   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    128 }
    129 
    130 bool DesktopSessionAgent::OnMessageReceived(const IPC::Message& message) {
    131   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    132 
    133   bool handled = true;
    134   if (started_) {
    135     IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
    136       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_CaptureFrame,
    137                           OnCaptureFrame)
    138       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectClipboardEvent,
    139                           OnInjectClipboardEvent)
    140       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectKeyEvent,
    141                           OnInjectKeyEvent)
    142       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectTextEvent,
    143                           OnInjectTextEvent)
    144       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectMouseEvent,
    145                           OnInjectMouseEvent)
    146       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_SetScreenResolution,
    147                           SetScreenResolution)
    148       IPC_MESSAGE_UNHANDLED(handled = false)
    149     IPC_END_MESSAGE_MAP()
    150   } else {
    151     IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
    152       IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_StartSessionAgent,
    153                           OnStartSessionAgent)
    154       IPC_MESSAGE_UNHANDLED(handled = false)
    155     IPC_END_MESSAGE_MAP()
    156   }
    157 
    158   CHECK(handled) << "Received unexpected IPC type: " << message.type();
    159   return handled;
    160 }
    161 
    162 void DesktopSessionAgent::OnChannelConnected(int32 peer_pid) {
    163   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    164 
    165   VLOG(1) << "IPC: desktop <- network (" << peer_pid << ")";
    166 
    167   desktop_pipe_.Close();
    168 }
    169 
    170 void DesktopSessionAgent::OnChannelError() {
    171   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    172 
    173   // Make sure the channel is closed.
    174   network_channel_.reset();
    175   desktop_pipe_.Close();
    176 
    177   // Notify the caller that the channel has been disconnected.
    178   if (delegate_.get())
    179     delegate_->OnNetworkProcessDisconnected();
    180 }
    181 
    182 webrtc::SharedMemory* DesktopSessionAgent::CreateSharedMemory(size_t size) {
    183   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    184 
    185   scoped_ptr<SharedBuffer> buffer =
    186       SharedBuffer::Create(this, size, next_shared_buffer_id_);
    187   if (buffer) {
    188     shared_buffers_++;
    189 
    190     // |next_shared_buffer_id_| starts from 1 and incrementing it by 2 makes
    191     // sure it is always odd and therefore zero is never used as a valid buffer
    192     // ID.
    193     //
    194     // It is very unlikely (though theoretically possible) to allocate the same
    195     // ID for two different buffers due to integer overflow. It should take
    196     // about a year of allocating 100 new buffers every second. Practically
    197     // speaking it never happens.
    198     next_shared_buffer_id_ += 2;
    199 
    200     IPC::PlatformFileForTransit handle;
    201 #if defined(OS_WIN)
    202     handle = buffer->handle();
    203 #else
    204     handle = base::FileDescriptor(buffer->handle(), false);
    205 #endif
    206     SendToNetwork(new ChromotingDesktopNetworkMsg_CreateSharedBuffer(
    207         buffer->id(), handle, buffer->size()));
    208   }
    209 
    210   return buffer.release();
    211 }
    212 
    213 DesktopSessionAgent::~DesktopSessionAgent() {
    214   DCHECK(!audio_capturer_);
    215   DCHECK(!desktop_environment_);
    216   DCHECK(!network_channel_);
    217   DCHECK(!screen_controls_);
    218   DCHECK(!video_capturer_);
    219 }
    220 
    221 const std::string& DesktopSessionAgent::client_jid() const {
    222   return client_jid_;
    223 }
    224 
    225 void DesktopSessionAgent::DisconnectSession() {
    226   SendToNetwork(new ChromotingDesktopNetworkMsg_DisconnectSession());
    227 }
    228 
    229 void DesktopSessionAgent::OnLocalMouseMoved(
    230     const webrtc::DesktopVector& new_pos) {
    231   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    232 
    233   remote_input_filter_->LocalMouseMoved(new_pos);
    234 }
    235 
    236 void DesktopSessionAgent::SetDisableInputs(bool disable_inputs) {
    237   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    238 
    239   // Do not expect this method to be called because it is only used by It2Me.
    240   NOTREACHED();
    241 }
    242 
    243 void DesktopSessionAgent::ResetVideoPipeline() {
    244   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    245 
    246   // This method is only used by HostExtensionSessions in the network process.
    247   NOTREACHED();
    248 }
    249 
    250 void DesktopSessionAgent::OnStartSessionAgent(
    251     const std::string& authenticated_jid,
    252     const ScreenResolution& resolution,
    253     bool virtual_terminal) {
    254   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    255   DCHECK(!started_);
    256   DCHECK(!audio_capturer_);
    257   DCHECK(!desktop_environment_);
    258   DCHECK(!input_injector_);
    259   DCHECK(!screen_controls_);
    260   DCHECK(!video_capturer_);
    261 
    262   started_ = true;
    263   client_jid_ = authenticated_jid;
    264 
    265   // Enable the curtain mode.
    266   delegate_->desktop_environment_factory().SetEnableCurtaining(
    267       virtual_terminal);
    268 
    269   // Create a desktop environment for the new session.
    270   desktop_environment_ = delegate_->desktop_environment_factory().Create(
    271       weak_factory_.GetWeakPtr());
    272 
    273   // Create the session controller and set the initial screen resolution.
    274   screen_controls_ = desktop_environment_->CreateScreenControls();
    275   SetScreenResolution(resolution);
    276 
    277   // Create the input injector.
    278   input_injector_ = desktop_environment_->CreateInputInjector();
    279 
    280   // Hook up the input filter.
    281   input_tracker_.reset(new protocol::InputEventTracker(input_injector_.get()));
    282   remote_input_filter_.reset(new RemoteInputFilter(input_tracker_.get()));
    283 
    284 #if defined(OS_WIN)
    285   // LocalInputMonitorWin filters out an echo of the injected input before it
    286   // reaches |remote_input_filter_|.
    287   remote_input_filter_->SetExpectLocalEcho(false);
    288 #endif  // defined(OS_WIN)
    289 
    290   // Start the input injector.
    291   scoped_ptr<protocol::ClipboardStub> clipboard_stub(
    292       new DesktopSesssionClipboardStub(this));
    293   input_injector_->Start(clipboard_stub.Pass());
    294 
    295   // Start the audio capturer.
    296   if (delegate_->desktop_environment_factory().SupportsAudioCapture()) {
    297     audio_capturer_ = desktop_environment_->CreateAudioCapturer();
    298     audio_capture_task_runner_->PostTask(
    299         FROM_HERE, base::Bind(&DesktopSessionAgent::StartAudioCapturer, this));
    300   }
    301 
    302   // Start the video capturer and mouse cursor monitor.
    303   video_capturer_ = desktop_environment_->CreateVideoCapturer();
    304   mouse_cursor_monitor_ = desktop_environment_->CreateMouseCursorMonitor();
    305   video_capture_task_runner_->PostTask(
    306       FROM_HERE, base::Bind(
    307           &DesktopSessionAgent::StartVideoCapturerAndMouseMonitor, this));
    308 }
    309 
    310 void DesktopSessionAgent::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
    311   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    312 
    313   last_frame_.reset(frame);
    314 
    315   current_size_ = frame->size();
    316 
    317   // Serialize webrtc::DesktopFrame.
    318   SerializedDesktopFrame serialized_frame;
    319   serialized_frame.shared_buffer_id = frame->shared_memory()->id();
    320   serialized_frame.bytes_per_row = frame->stride();
    321   serialized_frame.dimensions = frame->size();
    322   serialized_frame.capture_time_ms = frame->capture_time_ms();
    323   serialized_frame.dpi = frame->dpi();
    324   for (webrtc::DesktopRegion::Iterator i(frame->updated_region());
    325        !i.IsAtEnd(); i.Advance()) {
    326     serialized_frame.dirty_region.push_back(i.rect());
    327   }
    328 
    329   SendToNetwork(
    330       new ChromotingDesktopNetworkMsg_CaptureCompleted(serialized_frame));
    331 }
    332 
    333 void DesktopSessionAgent::OnMouseCursor(webrtc::MouseCursor* cursor) {
    334   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    335 
    336   scoped_ptr<webrtc::MouseCursor> owned_cursor(cursor);
    337 
    338   SendToNetwork(
    339       new ChromotingDesktopNetworkMsg_MouseCursor(*owned_cursor));
    340 }
    341 
    342 void DesktopSessionAgent::OnMouseCursorPosition(
    343     webrtc::MouseCursorMonitor::CursorState state,
    344     const webrtc::DesktopVector& position) {
    345   // We're not subscribing to mouse position changes.
    346   NOTREACHED();
    347 }
    348 
    349 void DesktopSessionAgent::InjectClipboardEvent(
    350     const protocol::ClipboardEvent& event) {
    351   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    352 
    353   std::string serialized_event;
    354   if (!event.SerializeToString(&serialized_event)) {
    355     LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
    356     return;
    357   }
    358 
    359   SendToNetwork(
    360       new ChromotingDesktopNetworkMsg_InjectClipboardEvent(serialized_event));
    361 }
    362 
    363 void DesktopSessionAgent::ProcessAudioPacket(scoped_ptr<AudioPacket> packet) {
    364   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
    365 
    366   std::string serialized_packet;
    367   if (!packet->SerializeToString(&serialized_packet)) {
    368     LOG(ERROR) << "Failed to serialize AudioPacket.";
    369     return;
    370   }
    371 
    372   SendToNetwork(new ChromotingDesktopNetworkMsg_AudioPacket(serialized_packet));
    373 }
    374 
    375 bool DesktopSessionAgent::Start(const base::WeakPtr<Delegate>& delegate,
    376                                 IPC::PlatformFileForTransit* desktop_pipe_out) {
    377   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    378   DCHECK(delegate_.get() == NULL);
    379 
    380   delegate_ = delegate;
    381 
    382   // Create an IPC channel to communicate with the network process.
    383   bool result = CreateConnectedIpcChannel(io_task_runner_,
    384                                           this,
    385                                           &desktop_pipe_,
    386                                           &network_channel_);
    387   base::PlatformFile raw_desktop_pipe = desktop_pipe_.GetPlatformFile();
    388 #if defined(OS_WIN)
    389   *desktop_pipe_out = IPC::PlatformFileForTransit(raw_desktop_pipe);
    390 #elif defined(OS_POSIX)
    391   *desktop_pipe_out = IPC::PlatformFileForTransit(raw_desktop_pipe, false);
    392 #else
    393 #error Unsupported platform.
    394 #endif
    395   return result;
    396 }
    397 
    398 void DesktopSessionAgent::Stop() {
    399   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    400 
    401   delegate_.reset();
    402 
    403   // Make sure the channel is closed.
    404   network_channel_.reset();
    405 
    406   if (started_) {
    407     started_ = false;
    408 
    409     // Ignore any further callbacks.
    410     weak_factory_.InvalidateWeakPtrs();
    411     client_jid_.clear();
    412 
    413     remote_input_filter_.reset();
    414 
    415     // Ensure that any pressed keys or buttons are released.
    416     input_tracker_->ReleaseAll();
    417     input_tracker_.reset();
    418 
    419     desktop_environment_.reset();
    420     input_injector_.reset();
    421     screen_controls_.reset();
    422 
    423     // Stop the audio capturer.
    424     audio_capture_task_runner_->PostTask(
    425         FROM_HERE, base::Bind(&DesktopSessionAgent::StopAudioCapturer, this));
    426 
    427     // Stop the video capturer.
    428     video_capture_task_runner_->PostTask(
    429         FROM_HERE, base::Bind(
    430             &DesktopSessionAgent::StopVideoCapturerAndMouseMonitor, this));
    431   }
    432 }
    433 
    434 void DesktopSessionAgent::OnCaptureFrame() {
    435   if (!video_capture_task_runner_->BelongsToCurrentThread()) {
    436     video_capture_task_runner_->PostTask(
    437         FROM_HERE,
    438         base::Bind(&DesktopSessionAgent::OnCaptureFrame, this));
    439     return;
    440   }
    441 
    442   mouse_cursor_monitor_->Capture();
    443 
    444   // webrtc::DesktopCapturer supports a very few (currently 2) outstanding
    445   // capture requests. The requests are serialized on
    446   // |video_capture_task_runner()| task runner. If the client issues more
    447   // requests, pixel data in captured frames will likely be corrupted but
    448   // stability of webrtc::DesktopCapturer will not be affected.
    449   video_capturer_->Capture(webrtc::DesktopRegion());
    450 }
    451 
    452 void DesktopSessionAgent::OnInjectClipboardEvent(
    453     const std::string& serialized_event) {
    454   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    455 
    456   protocol::ClipboardEvent event;
    457   if (!event.ParseFromString(serialized_event)) {
    458     LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
    459     return;
    460   }
    461 
    462   // InputStub implementations must verify events themselves, so we don't need
    463   // verification here. This matches HostEventDispatcher.
    464   input_injector_->InjectClipboardEvent(event);
    465 }
    466 
    467 void DesktopSessionAgent::OnInjectKeyEvent(
    468     const std::string& serialized_event) {
    469   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    470 
    471   protocol::KeyEvent event;
    472   if (!event.ParseFromString(serialized_event)) {
    473     LOG(ERROR) << "Failed to parse protocol::KeyEvent.";
    474     return;
    475   }
    476 
    477   // InputStub implementations must verify events themselves, so we need only
    478   // basic verification here. This matches HostEventDispatcher.
    479   if (!event.has_usb_keycode() || !event.has_pressed()) {
    480     LOG(ERROR) << "Received invalid key event.";
    481     return;
    482   }
    483 
    484   remote_input_filter_->InjectKeyEvent(event);
    485 }
    486 
    487 void DesktopSessionAgent::OnInjectTextEvent(
    488     const std::string& serialized_event) {
    489   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    490 
    491   protocol::TextEvent event;
    492   if (!event.ParseFromString(serialized_event)) {
    493     LOG(ERROR) << "Failed to parse protocol::TextEvent.";
    494     return;
    495   }
    496 
    497   // InputStub implementations must verify events themselves, so we need only
    498   // basic verification here. This matches HostEventDispatcher.
    499   if (!event.has_text()) {
    500     LOG(ERROR) << "Received invalid TextEvent.";
    501     return;
    502   }
    503 
    504   remote_input_filter_->InjectTextEvent(event);
    505 }
    506 
    507 void DesktopSessionAgent::OnInjectMouseEvent(
    508     const std::string& serialized_event) {
    509   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    510 
    511   protocol::MouseEvent event;
    512   if (!event.ParseFromString(serialized_event)) {
    513     LOG(ERROR) << "Failed to parse protocol::MouseEvent.";
    514     return;
    515   }
    516 
    517   // InputStub implementations must verify events themselves, so we don't need
    518   // verification here. This matches HostEventDispatcher.
    519   remote_input_filter_->InjectMouseEvent(event);
    520 }
    521 
    522 void DesktopSessionAgent::SetScreenResolution(
    523     const ScreenResolution& resolution) {
    524   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    525 
    526   if (screen_controls_ && resolution.IsEmpty())
    527     screen_controls_->SetScreenResolution(resolution);
    528 }
    529 
    530 void DesktopSessionAgent::SendToNetwork(IPC::Message* message) {
    531   if (!caller_task_runner_->BelongsToCurrentThread()) {
    532     caller_task_runner_->PostTask(
    533         FROM_HERE,
    534         base::Bind(&DesktopSessionAgent::SendToNetwork, this, message));
    535     return;
    536   }
    537 
    538   if (network_channel_) {
    539     network_channel_->Send(message);
    540   } else {
    541     delete message;
    542   }
    543 }
    544 
    545 void DesktopSessionAgent::StartAudioCapturer() {
    546   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
    547 
    548   if (audio_capturer_) {
    549     audio_capturer_->Start(base::Bind(&DesktopSessionAgent::ProcessAudioPacket,
    550                                       this));
    551   }
    552 }
    553 
    554 void DesktopSessionAgent::StopAudioCapturer() {
    555   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
    556 
    557   audio_capturer_.reset();
    558 }
    559 
    560 void DesktopSessionAgent::StartVideoCapturerAndMouseMonitor() {
    561   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    562 
    563   if (video_capturer_) {
    564     video_capturer_->Start(this);
    565   }
    566 
    567   if (mouse_cursor_monitor_) {
    568     mouse_cursor_monitor_->Init(this, webrtc::MouseCursorMonitor::SHAPE_ONLY);
    569   }
    570 }
    571 
    572 void DesktopSessionAgent::StopVideoCapturerAndMouseMonitor() {
    573   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    574 
    575   video_capturer_.reset();
    576   last_frame_.reset();
    577   mouse_cursor_monitor_.reset();
    578 
    579   // Video capturer must delete all buffers.
    580   DCHECK_EQ(shared_buffers_, 0);
    581 }
    582 
    583 void DesktopSessionAgent::OnSharedBufferDeleted(int id) {
    584   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    585   DCHECK(id != 0);
    586 
    587   shared_buffers_--;
    588   DCHECK_GE(shared_buffers_, 0);
    589   SendToNetwork(new ChromotingDesktopNetworkMsg_ReleaseSharedBuffer(id));
    590 }
    591 
    592 }  // namespace remoting
    593