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.
5 #include "remoting/client/plugin/media_source_video_renderer.h"
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"
18 static int kFrameIntervalNs
= 1000000;
20 class MediaSourceVideoRenderer::VideoWriter
: public mkvmuxer::IMkvWriter
{
22 typedef std::vector
<uint8_t> DataBuffer
;
24 VideoWriter(const webrtc::DesktopSize
& frame_size
, const char* codec_id
);
25 ~VideoWriter() override
;
27 const webrtc::DesktopSize
& size() { return frame_size_
; }
28 int64_t last_frame_timestamp() { return timecode_
- kFrameIntervalNs
; }
30 // IMkvWriter interface.
31 mkvmuxer::int32
Write(const void* buf
, mkvmuxer::uint32 len
) override
;
32 mkvmuxer::int64
Position() const override
;
33 mkvmuxer::int32
Position(mkvmuxer::int64 position
) override
;
34 bool Seekable() const override
;
35 void ElementStartNotify(mkvmuxer::uint64 element_id
,
36 mkvmuxer::int64 position
) override
;
38 scoped_ptr
<DataBuffer
> OnVideoFrame(const std::string
& video_data
,
42 webrtc::DesktopSize frame_size_
;
43 const char* codec_id_
;
45 scoped_ptr
<DataBuffer
> output_data_
;
47 scoped_ptr
<mkvmuxer::Segment
> segment_
;
51 MediaSourceVideoRenderer::VideoWriter::VideoWriter(
52 const webrtc::DesktopSize
& frame_size
,
54 : frame_size_(frame_size
),
58 segment_
.reset(new mkvmuxer::Segment());
60 segment_
->set_mode(mkvmuxer::Segment::kLive
);
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
))
71 base::Time::kNanosecondsPerMicrosecond
);
73 uint64 crop_right
= 0;
74 int width
= frame_size_
.width();
80 uint64 crop_bottom
= 0;
81 int height
= frame_size_
.height();
82 if (height
% 2 == 1) {
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
/
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");
101 MediaSourceVideoRenderer::VideoWriter::~VideoWriter() {}
103 mkvmuxer::int32
MediaSourceVideoRenderer::VideoWriter::Write(
105 mkvmuxer::uint32 len
) {
106 output_data_
->insert(output_data_
->end(),
107 reinterpret_cast<const char*>(buf
),
108 reinterpret_cast<const char*>(buf
) + len
);
113 mkvmuxer::int64
MediaSourceVideoRenderer::VideoWriter::Position() const {
117 mkvmuxer::int32
MediaSourceVideoRenderer::VideoWriter::Position(
118 mkvmuxer::int64 position
) {
122 bool MediaSourceVideoRenderer::VideoWriter::Seekable() const {
126 void MediaSourceVideoRenderer::VideoWriter::ElementStartNotify(
127 mkvmuxer::uint64 element_id
,
128 mkvmuxer::int64 position
) {
131 scoped_ptr
<MediaSourceVideoRenderer::VideoWriter::DataBuffer
>
132 MediaSourceVideoRenderer::VideoWriter::OnVideoFrame(
133 const std::string
& video_data
,
135 DCHECK(!output_data_
);
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();
144 MediaSourceVideoRenderer::MediaSourceVideoRenderer(Delegate
* delegate
)
145 : delegate_(delegate
),
146 codec_id_(mkvmuxer::Tracks::kVp8CodecId
),
147 latest_sequence_number_(0) {
150 MediaSourceVideoRenderer::~MediaSourceVideoRenderer() {}
152 void 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
;
159 case protocol::ChannelConfig::CODEC_VP9
:
160 format_string_
= "video/webm; codecs=\"vp9\"";
161 codec_id_
= mkvmuxer::Tracks::kVp9CodecId
;
168 ChromotingStats
* MediaSourceVideoRenderer::GetStats() {
172 void MediaSourceVideoRenderer::ProcessVideoPacket(
173 scoped_ptr
<VideoPacket
> packet
,
174 const base::Closure
& done
) {
175 base::ScopedClosureRunner
done_runner(done
);
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())
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
=
194 base::Time::FromInternalValue(packet
->client_sequence_number());
195 stats_
.round_trip_ms()->Record(round_trip_latency
.InMilliseconds());
198 bool media_source_needs_reset
= false;
200 webrtc::DesktopSize
frame_size(packet
->format().screen_width(),
201 packet
->format().screen_height());
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_
);
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
);
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()));
226 // Fallback for the case when the host didn't include the desktop shape.
228 webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(frame_size
));
231 if (!desktop_shape_
.Equals(desktop_shape
)) {
232 desktop_shape_
.Swap(&desktop_shape
);
233 delegate_
->OnMediaSourceShape(desktop_shape_
);
236 // First bit is set to 0 for key frames.
237 bool keyframe
= (packet
->data()[0] & 1) == 0;
239 scoped_ptr
<VideoWriter::DataBuffer
> buffer
=
240 writer_
->OnVideoFrame(packet
->data(), keyframe
);
241 delegate_
->OnMediaSourceData(&(*(buffer
->begin())), buffer
->size(), keyframe
);
244 } // namespace remoting