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 "base/time/time.h"
12#include "remoting/proto/video.pb.h"
13#include "remoting/protocol/session_config.h"
14#include "third_party/libwebm/source/mkvmuxer.hpp"
15
16namespace remoting {
17
18static int kFrameIntervalNs = 1000000;
19
20class MediaSourceVideoRenderer::VideoWriter : public mkvmuxer::IMkvWriter {
21 public:
22  typedef std::vector<uint8_t> DataBuffer;
23
24  VideoWriter(const webrtc::DesktopSize& frame_size, const char* codec_id);
25  virtual ~VideoWriter();
26
27  const webrtc::DesktopSize& size() { return frame_size_; }
28  int64_t last_frame_timestamp() { return timecode_ - kFrameIntervalNs; }
29
30  // IMkvWriter interface.
31  virtual mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) OVERRIDE;
32  virtual mkvmuxer::int64 Position() const OVERRIDE;
33  virtual mkvmuxer::int32 Position(mkvmuxer::int64 position) OVERRIDE;
34  virtual bool Seekable() const OVERRIDE;
35  virtual void ElementStartNotify(mkvmuxer::uint64 element_id,
36                                  mkvmuxer::int64 position) OVERRIDE;
37
38  scoped_ptr<DataBuffer> OnVideoFrame(const std::string& video_data,
39                                      bool keyframe);
40
41 private:
42  webrtc::DesktopSize frame_size_;
43  const char* codec_id_;
44
45  scoped_ptr<DataBuffer> output_data_;
46  int64_t position_;
47  scoped_ptr<mkvmuxer::Segment> segment_;
48  int64_t timecode_;
49};
50
51MediaSourceVideoRenderer::VideoWriter::VideoWriter(
52    const webrtc::DesktopSize& frame_size,
53    const char* codec_id)
54    : frame_size_(frame_size),
55      codec_id_(codec_id),
56      position_(0),
57      timecode_(0) {
58  segment_.reset(new mkvmuxer::Segment());
59  segment_->Init(this);
60  segment_->set_mode(mkvmuxer::Segment::kLive);
61
62  // DateUTC is specified in nanoseconds from 0:00 on January 1st, 2001.
63  base::Time::Exploded millennium_exploded;
64  memset(&millennium_exploded, 0, sizeof(millennium_exploded));
65  millennium_exploded.year = 2001;
66  millennium_exploded.month = 1;
67  millennium_exploded.day_of_month = 1;
68  segment_->GetSegmentInfo()->set_date_utc(
69      (base::Time::Now() - base::Time::FromUTCExploded(millennium_exploded))
70          .InMicroseconds() *
71      base::Time::kNanosecondsPerMicrosecond);
72
73  uint64 crop_right = 0;
74  int width = frame_size_.width();
75  if (width % 2 == 1) {
76    ++width;
77    crop_right = 1;
78  }
79
80  uint64 crop_bottom = 0;
81  int height = frame_size_.height();
82  if (height % 2 == 1) {
83    ++height;
84    crop_bottom = 1;
85  }
86
87  segment_->AddVideoTrack(width, height, 1);
88  mkvmuxer::VideoTrack* video_track =
89      reinterpret_cast<mkvmuxer::VideoTrack*>(segment_->GetTrackByNumber(1));
90  video_track->set_codec_id(codec_id_);
91  video_track->set_crop_right(crop_right);
92  video_track->set_crop_bottom(crop_bottom);
93  video_track->set_frame_rate(base::Time::kNanosecondsPerSecond /
94                              kFrameIntervalNs);
95  video_track->set_default_duration(kFrameIntervalNs);
96  mkvmuxer::SegmentInfo* const info = segment_->GetSegmentInfo();
97  info->set_writing_app("ChromotingViewer");
98  info->set_muxing_app("ChromotingViewer");
99}
100
101MediaSourceVideoRenderer::VideoWriter::~VideoWriter() {}
102
103mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Write(
104    const void* buf,
105    mkvmuxer::uint32 len) {
106  output_data_->insert(output_data_->end(),
107                       reinterpret_cast<const char*>(buf),
108                       reinterpret_cast<const char*>(buf) + len);
109  position_ += len;
110  return 0;
111}
112
113mkvmuxer::int64 MediaSourceVideoRenderer::VideoWriter::Position() const {
114  return position_;
115}
116
117mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Position(
118    mkvmuxer::int64 position) {
119  return -1;
120}
121
122bool MediaSourceVideoRenderer::VideoWriter::Seekable() const {
123  return false;
124}
125
126void MediaSourceVideoRenderer::VideoWriter::ElementStartNotify(
127    mkvmuxer::uint64 element_id,
128    mkvmuxer::int64 position) {
129}
130
131scoped_ptr<MediaSourceVideoRenderer::VideoWriter::DataBuffer>
132MediaSourceVideoRenderer::VideoWriter::OnVideoFrame(
133    const std::string& video_data,
134    bool keyframe) {
135  DCHECK(!output_data_);
136
137  output_data_.reset(new DataBuffer());
138  segment_->AddFrame(reinterpret_cast<const uint8_t*>(video_data.data()),
139                     video_data.size(), 1, timecode_, keyframe);
140  timecode_ += kFrameIntervalNs;
141  return output_data_.Pass();
142}
143
144MediaSourceVideoRenderer::MediaSourceVideoRenderer(Delegate* delegate)
145    : delegate_(delegate),
146      codec_id_(mkvmuxer::Tracks::kVp8CodecId),
147      latest_sequence_number_(0) {
148}
149
150MediaSourceVideoRenderer::~MediaSourceVideoRenderer() {}
151
152void MediaSourceVideoRenderer::Initialize(
153    const protocol::SessionConfig& config) {
154  switch (config.video_config().codec) {
155    case protocol::ChannelConfig::CODEC_VP8:
156      format_string_ = "video/webm; codecs=\"vp8\"";
157      codec_id_ = mkvmuxer::Tracks::kVp8CodecId;
158      break;
159    case protocol::ChannelConfig::CODEC_VP9:
160      format_string_ = "video/webm; codecs=\"vp9\"";
161      codec_id_ = mkvmuxer::Tracks::kVp9CodecId;
162      break;
163    default:
164      NOTREACHED();
165  }
166}
167
168ChromotingStats* MediaSourceVideoRenderer::GetStats() {
169  return &stats_;
170}
171
172void MediaSourceVideoRenderer::ProcessVideoPacket(
173    scoped_ptr<VideoPacket> packet,
174    const base::Closure& done) {
175  base::ScopedClosureRunner done_runner(done);
176
177  // Don't need to do anything if the packet is empty. Host sends empty video
178  // packets when the screen is not changing.
179  if (!packet->data().size())
180    return;
181
182  // Update statistics.
183  stats_.video_frame_rate()->Record(1);
184  stats_.video_bandwidth()->Record(packet->data().size());
185  if (packet->has_capture_time_ms())
186    stats_.video_capture_ms()->Record(packet->capture_time_ms());
187  if (packet->has_encode_time_ms())
188    stats_.video_encode_ms()->Record(packet->encode_time_ms());
189  if (packet->has_client_sequence_number() &&
190      packet->client_sequence_number() > latest_sequence_number_) {
191    latest_sequence_number_ = packet->client_sequence_number();
192    base::TimeDelta round_trip_latency =
193        base::Time::Now() -
194        base::Time::FromInternalValue(packet->client_sequence_number());
195    stats_.round_trip_ms()->Record(round_trip_latency.InMilliseconds());
196  }
197
198  bool media_source_needs_reset = false;
199
200  webrtc::DesktopSize frame_size(packet->format().screen_width(),
201                                 packet->format().screen_height());
202  if (!writer_ ||
203      (!writer_->size().equals(frame_size) && !frame_size.is_empty())) {
204    media_source_needs_reset = true;
205    writer_.reset(new VideoWriter(frame_size, codec_id_));
206    delegate_->OnMediaSourceReset(format_string_);
207  }
208
209  webrtc::DesktopVector frame_dpi(packet->format().x_dpi(),
210                                  packet->format().y_dpi());
211  if (media_source_needs_reset || !frame_dpi_.equals(frame_dpi)) {
212    frame_dpi_ = frame_dpi;
213    delegate_->OnMediaSourceSize(frame_size, frame_dpi);
214  }
215
216  // Update the desktop shape region.
217  webrtc::DesktopRegion desktop_shape;
218  if (packet->has_use_desktop_shape()) {
219    for (int i = 0; i < packet->desktop_shape_rects_size(); ++i) {
220      Rect remoting_rect = packet->desktop_shape_rects(i);
221      desktop_shape.AddRect(webrtc::DesktopRect::MakeXYWH(
222          remoting_rect.x(), remoting_rect.y(),
223          remoting_rect.width(), remoting_rect.height()));
224    }
225  } else {
226    // Fallback for the case when the host didn't include the desktop shape.
227    desktop_shape =
228        webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(frame_size));
229  }
230
231  if (!desktop_shape_.Equals(desktop_shape)) {
232    desktop_shape_.Swap(&desktop_shape);
233    delegate_->OnMediaSourceShape(desktop_shape_);
234  }
235
236  // First bit is set to 0 for key frames.
237  bool keyframe = (packet->data()[0] & 1) == 0;
238
239  scoped_ptr<VideoWriter::DataBuffer> buffer =
240      writer_->OnVideoFrame(packet->data(), keyframe);
241  delegate_->OnMediaSourceData(&(*(buffer->begin())), buffer->size(), keyframe);
242}
243
244}  // namespace remoting
245