media_source_video_renderer.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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
3// found in the LICENSE file.
4
5#include "remoting/client/plugin/media_source_video_renderer.h"
6
7#include <string.h>
8
9#include "base/callback_helpers.h"
10#include "base/logging.h"
11#include "remoting/proto/video.pb.h"
12#include "remoting/protocol/session_config.h"
13#include "third_party/libwebm/source/mkvmuxer.hpp"
14
15namespace remoting {
16
17static int kFrameIntervalNs = 1000000;
18
19class MediaSourceVideoRenderer::VideoWriter : public mkvmuxer::IMkvWriter {
20 public:
21  typedef std::vector<uint8_t> DataBuffer;
22
23  VideoWriter(const webrtc::DesktopSize& frame_size);
24  virtual ~VideoWriter();
25
26  const webrtc::DesktopSize& size() { return frame_size_; }
27  int64_t last_frame_timestamp() { return timecode_ - kFrameIntervalNs; }
28
29  // IMkvWriter interface.
30  virtual mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) OVERRIDE;
31  virtual mkvmuxer::int64 Position() const OVERRIDE;
32  virtual mkvmuxer::int32 Position(mkvmuxer::int64 position) OVERRIDE;
33  virtual bool Seekable() const OVERRIDE;
34  virtual void ElementStartNotify(mkvmuxer::uint64 element_id,
35                                  mkvmuxer::int64 position) OVERRIDE;
36
37  scoped_ptr<DataBuffer> OnVideoFrame(const std::string& video_data);
38
39 private:
40  webrtc::DesktopSize frame_size_;
41  scoped_ptr<DataBuffer> output_data_;
42  int64_t position_;
43  scoped_ptr<mkvmuxer::Segment> segment_;
44  int64_t timecode_;
45};
46
47MediaSourceVideoRenderer::VideoWriter::VideoWriter(
48    const webrtc::DesktopSize& frame_size)
49    : frame_size_(frame_size),
50      position_(0),
51      timecode_(0) {
52  segment_.reset(new mkvmuxer::Segment());
53  segment_->Init(this);
54  segment_->set_mode(mkvmuxer::Segment::kLive);
55  segment_->AddVideoTrack(frame_size_.width(), frame_size_.height(), 1);
56  mkvmuxer::SegmentInfo* const info = segment_->GetSegmentInfo();
57  info->set_writing_app("ChromotingViewer");
58  info->set_muxing_app("ChromotingViewer");
59}
60
61MediaSourceVideoRenderer::VideoWriter::~VideoWriter() {}
62
63mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Write(
64    const void* buf,
65    mkvmuxer::uint32 len) {
66  output_data_->insert(output_data_->end(),
67                       reinterpret_cast<const char*>(buf),
68                       reinterpret_cast<const char*>(buf) + len);
69  position_ += len;
70  return 0;
71}
72
73mkvmuxer::int64 MediaSourceVideoRenderer::VideoWriter::Position() const {
74  return position_;
75}
76
77mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Position(
78    mkvmuxer::int64 position) {
79  return -1;
80}
81
82bool MediaSourceVideoRenderer::VideoWriter::Seekable() const {
83  return false;
84}
85
86void MediaSourceVideoRenderer::VideoWriter::ElementStartNotify(
87    mkvmuxer::uint64 element_id,
88    mkvmuxer::int64 position) {
89}
90
91scoped_ptr<MediaSourceVideoRenderer::VideoWriter::DataBuffer>
92MediaSourceVideoRenderer::VideoWriter::OnVideoFrame(
93    const std::string& video_data) {
94  DCHECK(!output_data_);
95
96  output_data_.reset(new DataBuffer());
97  bool first_frame = (timecode_ == 0);
98  segment_->AddFrame(reinterpret_cast<const uint8_t*>(video_data.data()),
99                     video_data.size(), 1, timecode_, first_frame);
100  timecode_ += kFrameIntervalNs;
101  return output_data_.Pass();
102}
103
104MediaSourceVideoRenderer::MediaSourceVideoRenderer(Delegate* delegate)
105    : delegate_(delegate),
106      latest_sequence_number_(0) {
107}
108
109MediaSourceVideoRenderer::~MediaSourceVideoRenderer() {}
110
111void MediaSourceVideoRenderer::Initialize(
112    const protocol::SessionConfig& config) {
113  DCHECK_EQ(config.video_config().codec, protocol::ChannelConfig::CODEC_VP8);
114}
115
116ChromotingStats* MediaSourceVideoRenderer::GetStats() {
117  return &stats_;
118}
119
120void MediaSourceVideoRenderer::ProcessVideoPacket(
121    scoped_ptr<VideoPacket> packet,
122    const base::Closure& done) {
123  base::ScopedClosureRunner done_runner(done);
124
125  // Don't need to do anything if the packet is empty. Host sends empty video
126  // packets when the screen is not changing.
127  if (!packet->data().size())
128    return;
129
130  // Update statistics.
131  stats_.video_frame_rate()->Record(1);
132  stats_.video_bandwidth()->Record(packet->data().size());
133  if (packet->has_capture_time_ms())
134    stats_.video_capture_ms()->Record(packet->capture_time_ms());
135  if (packet->has_encode_time_ms())
136    stats_.video_encode_ms()->Record(packet->encode_time_ms());
137  if (packet->has_client_sequence_number() &&
138      packet->client_sequence_number() > latest_sequence_number_) {
139    latest_sequence_number_ = packet->client_sequence_number();
140    base::TimeDelta round_trip_latency =
141        base::Time::Now() -
142        base::Time::FromInternalValue(packet->client_sequence_number());
143    stats_.round_trip_ms()->Record(round_trip_latency.InMilliseconds());
144  }
145
146  bool media_source_needs_reset = false;
147
148  webrtc::DesktopSize frame_size(packet->format().screen_width(),
149                                 packet->format().screen_height());
150  if (!writer_ ||
151      (!writer_->size().equals(frame_size) && !frame_size.is_empty())) {
152    media_source_needs_reset = true;
153    writer_.reset(new VideoWriter(frame_size));
154    delegate_->OnMediaSourceReset("video/webm; codecs=\"vp8\"");
155  }
156
157  webrtc::DesktopVector frame_dpi(packet->format().x_dpi(),
158                                  packet->format().y_dpi());
159  if (media_source_needs_reset || !frame_dpi_.equals(frame_dpi)) {
160    frame_dpi_ = frame_dpi;
161    delegate_->OnMediaSourceSize(frame_size, frame_dpi);
162  }
163
164  // Update the desktop shape region.
165  webrtc::DesktopRegion desktop_shape;
166  if (packet->has_use_desktop_shape()) {
167    for (int i = 0; i < packet->desktop_shape_rects_size(); ++i) {
168      Rect remoting_rect = packet->desktop_shape_rects(i);
169      desktop_shape.AddRect(webrtc::DesktopRect::MakeXYWH(
170          remoting_rect.x(), remoting_rect.y(),
171          remoting_rect.width(), remoting_rect.height()));
172    }
173  } else {
174    // Fallback for the case when the host didn't include the desktop shape.
175    desktop_shape =
176        webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(frame_size));
177  }
178
179  if (!desktop_shape_.Equals(desktop_shape)) {
180    desktop_shape_.Swap(&desktop_shape);
181    delegate_->OnMediaSourceShape(desktop_shape_);
182  }
183
184  scoped_ptr<VideoWriter::DataBuffer> buffer =
185      writer_->OnVideoFrame(packet->data());
186  delegate_->OnMediaSourceData(&(*(buffer->begin())), buffer->size());
187}
188
189}  // namespace remoting
190