| // Copyright 2014 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/client/plugin/media_source_video_renderer.h" |
| |
| #include <string.h> |
| |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "remoting/proto/video.pb.h" |
| #include "remoting/protocol/session_config.h" |
| #include "third_party/libwebm/source/mkvmuxer.hpp" |
| |
| namespace remoting { |
| |
| static int kFrameIntervalNs = 1000000; |
| |
| class MediaSourceVideoRenderer::VideoWriter : public mkvmuxer::IMkvWriter { |
| public: |
| typedef std::vector<uint8_t> DataBuffer; |
| |
| VideoWriter(const webrtc::DesktopSize& frame_size, const char* codec_id); |
| ~VideoWriter() override; |
| |
| const webrtc::DesktopSize& size() { return frame_size_; } |
| int64_t last_frame_timestamp() { return timecode_ - kFrameIntervalNs; } |
| |
| // IMkvWriter interface. |
| mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) override; |
| mkvmuxer::int64 Position() const override; |
| mkvmuxer::int32 Position(mkvmuxer::int64 position) override; |
| bool Seekable() const override; |
| void ElementStartNotify(mkvmuxer::uint64 element_id, |
| mkvmuxer::int64 position) override; |
| |
| scoped_ptr<DataBuffer> OnVideoFrame(const std::string& video_data, |
| bool keyframe); |
| |
| private: |
| webrtc::DesktopSize frame_size_; |
| const char* codec_id_; |
| |
| scoped_ptr<DataBuffer> output_data_; |
| int64_t position_; |
| scoped_ptr<mkvmuxer::Segment> segment_; |
| int64_t timecode_; |
| }; |
| |
| MediaSourceVideoRenderer::VideoWriter::VideoWriter( |
| const webrtc::DesktopSize& frame_size, |
| const char* codec_id) |
| : frame_size_(frame_size), |
| codec_id_(codec_id), |
| position_(0), |
| timecode_(0) { |
| segment_.reset(new mkvmuxer::Segment()); |
| segment_->Init(this); |
| segment_->set_mode(mkvmuxer::Segment::kLive); |
| |
| // DateUTC is specified in nanoseconds from 0:00 on January 1st, 2001. |
| base::Time::Exploded millennium_exploded; |
| memset(&millennium_exploded, 0, sizeof(millennium_exploded)); |
| millennium_exploded.year = 2001; |
| millennium_exploded.month = 1; |
| millennium_exploded.day_of_month = 1; |
| segment_->GetSegmentInfo()->set_date_utc( |
| (base::Time::Now() - base::Time::FromUTCExploded(millennium_exploded)) |
| .InMicroseconds() * |
| base::Time::kNanosecondsPerMicrosecond); |
| |
| uint64 crop_right = 0; |
| int width = frame_size_.width(); |
| if (width % 2 == 1) { |
| ++width; |
| crop_right = 1; |
| } |
| |
| uint64 crop_bottom = 0; |
| int height = frame_size_.height(); |
| if (height % 2 == 1) { |
| ++height; |
| crop_bottom = 1; |
| } |
| |
| segment_->AddVideoTrack(width, height, 1); |
| mkvmuxer::VideoTrack* video_track = |
| reinterpret_cast<mkvmuxer::VideoTrack*>(segment_->GetTrackByNumber(1)); |
| video_track->set_codec_id(codec_id_); |
| video_track->set_crop_right(crop_right); |
| video_track->set_crop_bottom(crop_bottom); |
| video_track->set_frame_rate(base::Time::kNanosecondsPerSecond / |
| kFrameIntervalNs); |
| video_track->set_default_duration(kFrameIntervalNs); |
| mkvmuxer::SegmentInfo* const info = segment_->GetSegmentInfo(); |
| info->set_writing_app("ChromotingViewer"); |
| info->set_muxing_app("ChromotingViewer"); |
| } |
| |
| MediaSourceVideoRenderer::VideoWriter::~VideoWriter() {} |
| |
| mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Write( |
| const void* buf, |
| mkvmuxer::uint32 len) { |
| output_data_->insert(output_data_->end(), |
| reinterpret_cast<const char*>(buf), |
| reinterpret_cast<const char*>(buf) + len); |
| position_ += len; |
| return 0; |
| } |
| |
| mkvmuxer::int64 MediaSourceVideoRenderer::VideoWriter::Position() const { |
| return position_; |
| } |
| |
| mkvmuxer::int32 MediaSourceVideoRenderer::VideoWriter::Position( |
| mkvmuxer::int64 position) { |
| return -1; |
| } |
| |
| bool MediaSourceVideoRenderer::VideoWriter::Seekable() const { |
| return false; |
| } |
| |
| void MediaSourceVideoRenderer::VideoWriter::ElementStartNotify( |
| mkvmuxer::uint64 element_id, |
| mkvmuxer::int64 position) { |
| } |
| |
| scoped_ptr<MediaSourceVideoRenderer::VideoWriter::DataBuffer> |
| MediaSourceVideoRenderer::VideoWriter::OnVideoFrame( |
| const std::string& video_data, |
| bool keyframe) { |
| DCHECK(!output_data_); |
| |
| output_data_.reset(new DataBuffer()); |
| segment_->AddFrame(reinterpret_cast<const uint8_t*>(video_data.data()), |
| video_data.size(), 1, timecode_, keyframe); |
| timecode_ += kFrameIntervalNs; |
| return output_data_.Pass(); |
| } |
| |
| MediaSourceVideoRenderer::MediaSourceVideoRenderer(Delegate* delegate) |
| : delegate_(delegate), |
| codec_id_(mkvmuxer::Tracks::kVp8CodecId), |
| latest_sequence_number_(0) { |
| } |
| |
| MediaSourceVideoRenderer::~MediaSourceVideoRenderer() {} |
| |
| void MediaSourceVideoRenderer::Initialize( |
| const protocol::SessionConfig& config) { |
| switch (config.video_config().codec) { |
| case protocol::ChannelConfig::CODEC_VP8: |
| format_string_ = "video/webm; codecs=\"vp8\""; |
| codec_id_ = mkvmuxer::Tracks::kVp8CodecId; |
| break; |
| case protocol::ChannelConfig::CODEC_VP9: |
| format_string_ = "video/webm; codecs=\"vp9\""; |
| codec_id_ = mkvmuxer::Tracks::kVp9CodecId; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| ChromotingStats* MediaSourceVideoRenderer::GetStats() { |
| return &stats_; |
| } |
| |
| void MediaSourceVideoRenderer::ProcessVideoPacket( |
| scoped_ptr<VideoPacket> packet, |
| const base::Closure& done) { |
| base::ScopedClosureRunner done_runner(done); |
| |
| // Don't need to do anything if the packet is empty. Host sends empty video |
| // packets when the screen is not changing. |
| if (!packet->data().size()) |
| return; |
| |
| // Update statistics. |
| stats_.video_frame_rate()->Record(1); |
| stats_.video_bandwidth()->Record(packet->data().size()); |
| if (packet->has_capture_time_ms()) |
| stats_.video_capture_ms()->Record(packet->capture_time_ms()); |
| if (packet->has_encode_time_ms()) |
| stats_.video_encode_ms()->Record(packet->encode_time_ms()); |
| if (packet->has_client_sequence_number() && |
| packet->client_sequence_number() > latest_sequence_number_) { |
| latest_sequence_number_ = packet->client_sequence_number(); |
| base::TimeDelta round_trip_latency = |
| base::Time::Now() - |
| base::Time::FromInternalValue(packet->client_sequence_number()); |
| stats_.round_trip_ms()->Record(round_trip_latency.InMilliseconds()); |
| } |
| |
| bool media_source_needs_reset = false; |
| |
| webrtc::DesktopSize frame_size(packet->format().screen_width(), |
| packet->format().screen_height()); |
| if (!writer_ || |
| (!writer_->size().equals(frame_size) && !frame_size.is_empty())) { |
| media_source_needs_reset = true; |
| writer_.reset(new VideoWriter(frame_size, codec_id_)); |
| delegate_->OnMediaSourceReset(format_string_); |
| } |
| |
| webrtc::DesktopVector frame_dpi(packet->format().x_dpi(), |
| packet->format().y_dpi()); |
| if (media_source_needs_reset || !frame_dpi_.equals(frame_dpi)) { |
| frame_dpi_ = frame_dpi; |
| delegate_->OnMediaSourceSize(frame_size, frame_dpi); |
| } |
| |
| // Update the desktop shape region. |
| webrtc::DesktopRegion desktop_shape; |
| if (packet->has_use_desktop_shape()) { |
| for (int i = 0; i < packet->desktop_shape_rects_size(); ++i) { |
| Rect remoting_rect = packet->desktop_shape_rects(i); |
| desktop_shape.AddRect(webrtc::DesktopRect::MakeXYWH( |
| remoting_rect.x(), remoting_rect.y(), |
| remoting_rect.width(), remoting_rect.height())); |
| } |
| } else { |
| // Fallback for the case when the host didn't include the desktop shape. |
| desktop_shape = |
| webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(frame_size)); |
| } |
| |
| if (!desktop_shape_.Equals(desktop_shape)) { |
| desktop_shape_.Swap(&desktop_shape); |
| delegate_->OnMediaSourceShape(desktop_shape_); |
| } |
| |
| // First bit is set to 0 for key frames. |
| bool keyframe = (packet->data()[0] & 1) == 0; |
| |
| scoped_ptr<VideoWriter::DataBuffer> buffer = |
| writer_->OnVideoFrame(packet->data(), keyframe); |
| delegate_->OnMediaSourceData(&(*(buffer->begin())), buffer->size(), keyframe); |
| } |
| |
| } // namespace remoting |