diff options
author | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-19 23:27:01 +0000 |
---|---|---|
committer | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-19 23:27:01 +0000 |
commit | 641d8ecb3f0b3436d80376646b00af0dca6e7f0d (patch) | |
tree | f71de1cde9fce9234f3b7f2e24aa1a919dfc323d /media | |
parent | bcc5c381a6523c0e1acf6c660018b0f5fb1a351a (diff) | |
download | chromium_src-641d8ecb3f0b3436d80376646b00af0dca6e7f0d.zip chromium_src-641d8ecb3f0b3436d80376646b00af0dca6e7f0d.tar.gz chromium_src-641d8ecb3f0b3436d80376646b00af0dca6e7f0d.tar.bz2 |
Cast: Adding video receiver
Review URL: https://chromiumcodereview.appspot.com/23480060
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@224227 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/cast/cast.gyp | 6 | ||||
-rw-r--r-- | media/cast/cast_receiver.gyp | 4 | ||||
-rw-r--r-- | media/cast/cast_receiver.h | 9 | ||||
-rw-r--r-- | media/cast/video_receiver/codecs/vp8/vp8_decoder.cc | 68 | ||||
-rw-r--r-- | media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp | 26 | ||||
-rw-r--r-- | media/cast/video_receiver/codecs/vp8/vp8_decoder.h | 37 | ||||
-rw-r--r-- | media/cast/video_receiver/video_decoder.cc | 66 | ||||
-rw-r--r-- | media/cast/video_receiver/video_decoder.h | 48 | ||||
-rw-r--r-- | media/cast/video_receiver/video_decoder_unittest.cc | 99 | ||||
-rw-r--r-- | media/cast/video_receiver/video_receiver.cc | 337 | ||||
-rw-r--r-- | media/cast/video_receiver/video_receiver.gypi | 30 | ||||
-rw-r--r-- | media/cast/video_receiver/video_receiver.h | 124 | ||||
-rw-r--r-- | media/cast/video_receiver/video_receiver_unittest.cc | 141 |
13 files changed, 988 insertions, 7 deletions
diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp index b102229..9d31e51 100644 --- a/media/cast/cast.gyp +++ b/media/cast/cast.gyp @@ -14,10 +14,10 @@ '<(DEPTH)/', ], 'sources': [ - 'cast_config.h', 'cast_config.cc', - 'cast_thread.h', + 'cast_config.h', 'cast_thread.cc', + 'cast_thread.h', ], # source }, { @@ -77,6 +77,8 @@ 'rtcp/rtcp_receiver_unittest.cc', 'rtcp/rtcp_sender_unittest.cc', 'rtcp/rtcp_unittest.cc', + 'video_receiver/video_decoder_unittest.cc', + 'video_receiver/video_receiver_unittest.cc', 'video_sender/video_encoder_unittest.cc', 'video_sender/video_sender_unittest.cc', ], # source diff --git a/media/cast/cast_receiver.gyp b/media/cast/cast_receiver.gyp index 7f12ad1..539c41d 100644 --- a/media/cast/cast_receiver.gyp +++ b/media/cast/cast_receiver.gyp @@ -5,7 +5,7 @@ { 'includes': [ 'audio_receiver/audio_receiver.gypi', -# 'video_receiver/video_receiver.gypi', + 'video_receiver/video_receiver.gypi', ], 'targets': [ { @@ -19,7 +19,7 @@ 'dependencies': [ 'rtp_receiver/rtp_receiver.gyp:*', 'cast_audio_receiver', -# 'video_receiver', + 'cast_video_receiver', 'framer/framer.gyp:cast_framer', 'pacing/paced_sender.gyp:paced_sender', ], diff --git a/media/cast/cast_receiver.h b/media/cast/cast_receiver.h index d7c991e..a2eef76 100644 --- a/media/cast/cast_receiver.h +++ b/media/cast/cast_receiver.h @@ -23,12 +23,15 @@ namespace cast { typedef base::Callback<void(scoped_ptr<PcmAudioFrame>, const base::TimeTicks)> AudioFrameDecodedCallback; +// Callback in which the raw frame and render time will be returned once +// decoding is complete. +typedef base::Callback<void(scoped_ptr<I420VideoFrame>, + const base::TimeTicks)> VideoFrameDecodedCallback; + // This Class is thread safe. class FrameReceiver : public base::RefCountedThreadSafe<FrameReceiver>{ public: - // TODO(pwestin): These functions must be updated. - virtual bool GetRawVideoFrame(I420VideoFrame* video_frame, - base::TimeTicks* render_time) = 0; + virtual bool GetRawVideoFrame(const VideoFrameDecodedCallback& callback) = 0; virtual bool GetEncodedVideoFrame(EncodedVideoFrame* video_frame, base::TimeTicks* render_time) = 0; diff --git a/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc b/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc new file mode 100644 index 0000000..93d3eb5 --- /dev/null +++ b/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc @@ -0,0 +1,68 @@ +// Copyright 2013 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 "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" + +#include "base/logging.h" +#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" + +namespace media { +namespace cast { + +Vp8Decoder::Vp8Decoder(int number_of_cores) { + decoder_.reset(new vpx_dec_ctx_t()); + InitDecode(number_of_cores); +} + +Vp8Decoder::~Vp8Decoder() {} + +void Vp8Decoder::InitDecode(int number_of_cores) { + vpx_codec_dec_cfg_t cfg; + cfg.threads = number_of_cores; + vpx_codec_flags_t flags = VPX_CODEC_USE_POSTPROC; + + if (vpx_codec_dec_init(decoder_.get(), vpx_codec_vp8_dx(), &cfg, flags)) { + DCHECK(false) << "VP8 decode error"; + } +} + +bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image, + I420VideoFrame* decoded_frame) { + if (input_image.data.empty()) return false; + + vpx_codec_iter_t iter = NULL; + vpx_image_t* img; + if (vpx_codec_decode(decoder_.get(), + input_image.data.data(), + input_image.data.size(), + 0, + 1 /* real time*/)) { + return false; + } + + img = vpx_codec_get_frame(decoder_.get(), &iter); + if (img == NULL) return false; + + // Populate the decoded image. + decoded_frame->width = img->d_w; + decoded_frame->height = img->d_h; + + decoded_frame->y_plane.stride = img->stride[VPX_PLANE_Y]; + decoded_frame->y_plane.length = img->stride[VPX_PLANE_Y] * img->d_h; + decoded_frame->y_plane.data = img->planes[VPX_PLANE_Y]; + + decoded_frame->u_plane.stride = img->stride[VPX_PLANE_U]; + decoded_frame->u_plane.length = img->stride[VPX_PLANE_U] * img->d_h; + decoded_frame->u_plane.data = img->planes[VPX_PLANE_U]; + + decoded_frame->v_plane.stride = img->stride[VPX_PLANE_V]; + decoded_frame->v_plane.length = img->stride[VPX_PLANE_V] * img->d_h; + decoded_frame->v_plane.data = img->planes[VPX_PLANE_V]; + + return true; +} + +} // namespace cast +} // namespace media + diff --git a/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp b/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp new file mode 100644 index 0000000..bed02c8 --- /dev/null +++ b/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp @@ -0,0 +1,26 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of the source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'cast_vp8_decoder', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + '<(DEPTH)/third_party/', + '<(DEPTH)/third_party/libvpx/', + ], + 'sources': [ + 'vp8_decoder.cc', + 'vp8_decoder.h', + ], # source + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', + '<(DEPTH)/base/base.gyp:test_support_base', + ], + }, + ], +} diff --git a/media/cast/video_receiver/codecs/vp8/vp8_decoder.h b/media/cast/video_receiver/codecs/vp8/vp8_decoder.h new file mode 100644 index 0000000..1acdb5a --- /dev/null +++ b/media/cast/video_receiver/codecs/vp8/vp8_decoder.h @@ -0,0 +1,37 @@ +// Copyright 2013 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. + +#ifndef MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ +#define MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ + +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" + +typedef struct vpx_codec_ctx vpx_dec_ctx_t; + +namespace media { +namespace cast { + +class Vp8Decoder { + public: + explicit Vp8Decoder(int number_of_cores); + + ~Vp8Decoder(); + + // Initialize the decoder. + void InitDecode(int number_of_cores); + + // Decode encoded image (as a part of a video stream). + bool Decode(const EncodedVideoFrame& input_image, + I420VideoFrame* decoded_frame); + + private: + scoped_ptr<vpx_dec_ctx_t> decoder_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ diff --git a/media/cast/video_receiver/video_decoder.cc b/media/cast/video_receiver/video_decoder.cc new file mode 100644 index 0000000..238d6db --- /dev/null +++ b/media/cast/video_receiver/video_decoder.cc @@ -0,0 +1,66 @@ +// Copyright 2013 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 "media/cast/video_receiver/video_decoder.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" + +namespace media { +namespace cast { + +VideoDecoder::VideoDecoder(scoped_refptr<CastThread> cast_thread, + const VideoReceiverConfig& video_config) + : cast_thread_(cast_thread), + codec_(video_config.codec), + vp8_decoder_() { + switch (video_config.codec) { + case kVp8: + // Initializing to use one core. + vp8_decoder_.reset(new Vp8Decoder(1)); + break; + case kH264: + NOTIMPLEMENTED(); + break; + case kExternalVideo: + DCHECK(false) << "Invalid codec"; + break; + } +} + +VideoDecoder::~VideoDecoder() {} + +void VideoDecoder::DecodeVideoFrame( + const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback, + base::Closure frame_release_callback) { + DecodeFrame(encoded_frame, render_time, frame_decoded_callback); + // Done with the frame -> release. + cast_thread_->PostTask(CastThread::MAIN, FROM_HERE, frame_release_callback); +} + +void VideoDecoder::DecodeFrame( + const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback) { + DCHECK(encoded_frame->codec == codec_) << "Invalid codec"; + // TODO(mikhal): Allow the application to allocate this memory. + scoped_ptr<I420VideoFrame> video_frame(new I420VideoFrame()); + + if (encoded_frame->data.size() > 0) { + bool success = vp8_decoder_->Decode(*encoded_frame, video_frame.get()); + // Frame decoded - return frame to the user via callback. + if (success) { + cast_thread_->PostTask(CastThread::MAIN, FROM_HERE, + base::Bind(frame_decoded_callback, + base::Passed(&video_frame), render_time)); + } + } +} + +} // namespace cast +} // namespace media diff --git a/media/cast/video_receiver/video_decoder.h b/media/cast/video_receiver/video_decoder.h new file mode 100644 index 0000000..abf1955 --- /dev/null +++ b/media/cast/video_receiver/video_decoder.h @@ -0,0 +1,48 @@ +// Copyright 2013 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. + +#ifndef MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ +#define MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/cast_thread.h" + +namespace media { +namespace cast { + +class Vp8Decoder; + +class VideoDecoder : public base::RefCountedThreadSafe<VideoDecoder>{ + public: + VideoDecoder(scoped_refptr<CastThread> cast_thread, + const VideoReceiverConfig& video_config); + ~VideoDecoder(); + + + // Decode a video frame. Decoded (raw) frame will be returned in the + // frame_decoded_callback. + void DecodeVideoFrame(const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback, + base::Closure frame_release_callback); + + private: + void DecodeFrame(const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback); + VideoCodec codec_; + scoped_ptr<Vp8Decoder> vp8_decoder_; + scoped_refptr<CastThread> cast_thread_; + + DISALLOW_COPY_AND_ASSIGN(VideoDecoder); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ diff --git a/media/cast/video_receiver/video_decoder_unittest.cc b/media/cast/video_receiver/video_decoder_unittest.cc new file mode 100644 index 0000000..797521f --- /dev/null +++ b/media/cast/video_receiver/video_decoder_unittest.cc @@ -0,0 +1,99 @@ +// Copyright 2013 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_thread.h" +#include "media/cast/video_receiver/video_decoder.h" + +namespace media { +namespace cast { + +using base::RunLoop; +using base::MessageLoopProxy; +using base::Thread; +using testing::_; + +// Random frame size for testing. +const int kFrameSize = 2345; + +class TestVideoDecoderCallback : + public base::RefCountedThreadSafe<TestVideoDecoderCallback> { + public: + TestVideoDecoderCallback() + : num_called_(0) {} + // TODO(mikhal): Set and check expectations. + void DecodeComplete(scoped_ptr<I420VideoFrame> frame, + const base::TimeTicks render_time) { + num_called_++; + } + + int number_times_called() {return num_called_;} + private: + int num_called_; +}; + +class VideoDecoderTest : public ::testing::Test { + protected: + VideoDecoderTest() { + // Configure to vp8. + config_.codec = kVp8; + config_.use_external_decoder = false; + video_decoder_callback_ = new TestVideoDecoderCallback(); + } + + ~VideoDecoderTest() {} + virtual void SetUp() { + cast_thread_ = new CastThread(MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current()); + decoder_ = new VideoDecoder(cast_thread_, config_); + } + + // Used in MessageLoopProxy::current(). + base::MessageLoop loop_; + scoped_refptr<VideoDecoder> decoder_; + VideoReceiverConfig config_; + EncodedVideoFrame encoded_frame_; + scoped_refptr<CastThread> cast_thread_; + scoped_refptr<TestVideoDecoderCallback> video_decoder_callback_; +}; + +// TODO(pwestin): Test decoding a real frame. +TEST_F(VideoDecoderTest, SizeZero) { + RunLoop run_loop; + encoded_frame_.codec = kVp8; + base::TimeTicks render_time; + VideoFrameDecodedCallback frame_decoded_callback = + base::Bind(&TestVideoDecoderCallback::DecodeComplete, + video_decoder_callback_.get()); + decoder_->DecodeVideoFrame(&encoded_frame_, render_time, + frame_decoded_callback, run_loop.QuitClosure()); + EXPECT_EQ(0, video_decoder_callback_->number_times_called()); +} + +TEST_F(VideoDecoderTest, InvalidCodec) { + RunLoop run_loop; + base::TimeTicks render_time; + VideoFrameDecodedCallback frame_decoded_callback = + base::Bind(&TestVideoDecoderCallback::DecodeComplete, + video_decoder_callback_.get()); + encoded_frame_.data.assign(kFrameSize, 0); + encoded_frame_.codec = kExternalVideo; + EXPECT_DEATH(decoder_->DecodeVideoFrame(&encoded_frame_, render_time, + frame_decoded_callback, run_loop.QuitClosure()), "Invalid codec"); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/video_receiver/video_receiver.cc b/media/cast/video_receiver/video_receiver.cc new file mode 100644 index 0000000..4d0421c --- /dev/null +++ b/media/cast/video_receiver/video_receiver.cc @@ -0,0 +1,337 @@ +// Copyright 2013 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 "media/cast/video_receiver/video_receiver.h" + +#include <algorithm> +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/cast_defines.h" +#include "media/cast/framer/framer.h" +#include "media/cast/video_receiver/video_decoder.h" + +namespace media { +namespace cast { + +const int64 kMinSchedulingDelayMs = 1; +static const int64 kMaxFrameWaitMs = 20; +static const int64 kMinTimeBetweenOffsetUpdatesMs = 500; +static const int kTimeOffsetFilter = 8; + +// Local implementation of RtpData (defined in rtp_rtcp_defines.h). +// Used to pass payload data into the video receiver. +class LocalRtpVideoData : public RtpData { + public: + explicit LocalRtpVideoData(VideoReceiver* video_receiver) + : video_receiver_(video_receiver), + time_updated_(false), + incoming_rtp_timestamp_(0) { + } + ~LocalRtpVideoData() {} + + virtual void OnReceivedPayloadData(const uint8* payload_data, + int payload_size, + const RtpCastHeader* rtp_header) OVERRIDE { + { + if (!time_updated_) { + incoming_rtp_timestamp_ = rtp_header->webrtc.header.timestamp; + time_incoming_packet_ = video_receiver_->clock_->NowTicks(); + time_updated_ = true; + } else if (video_receiver_->clock_->NowTicks() > time_incoming_packet_ + + base::TimeDelta::FromMilliseconds(kMinTimeBetweenOffsetUpdatesMs)) { + incoming_rtp_timestamp_ = rtp_header->webrtc.header.timestamp; + time_incoming_packet_ = video_receiver_->clock_->NowTicks(); + time_updated_ = true; + } + } + video_receiver_->IncomingRtpPacket(payload_data, payload_size, *rtp_header); + } + + bool GetPacketTimeInformation(base::TimeTicks* time_incoming_packet, + uint32* incoming_rtp_timestamp) { + *time_incoming_packet = time_incoming_packet_; + *incoming_rtp_timestamp = incoming_rtp_timestamp_; + bool time_updated = time_updated_; + time_updated_ = false; + return time_updated; + } + + private: + VideoReceiver* video_receiver_; + bool time_updated_; + base::TimeTicks time_incoming_packet_; + uint32 incoming_rtp_timestamp_; +}; + +// Local implementation of RtpPayloadFeedback (defined in rtp_defines.h) +// Used to convey cast-specific feedback from receiver to sender. +// Callback triggered by the Framer (cast message builder). +class LocalRtpVideoFeedback : public RtpPayloadFeedback { + public: + explicit LocalRtpVideoFeedback(VideoReceiver* video_receiver) + : video_receiver_(video_receiver) { + } + virtual void CastFeedback(const RtcpCastMessage& cast_message) OVERRIDE { + video_receiver_->CastFeedback(cast_message); + } + + virtual void RequestKeyFrame() OVERRIDE { + video_receiver_->RequestKeyFrame(); + } + + private: + VideoReceiver* video_receiver_; +}; + +// Local implementation of RtpReceiverStatistics (defined by rtcp.h). +// Used to pass statistics data from the RTP module to the RTCP module. +class LocalRtpReceiverStatistics : public RtpReceiverStatistics { + public: + explicit LocalRtpReceiverStatistics(RtpReceiver* rtp_receiver) + : rtp_receiver_(rtp_receiver) { + } + + virtual void GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, // 24 bits valid. + uint32* extended_high_sequence_number, + uint32* jitter) OVERRIDE { + rtp_receiver_->GetStatistics(fraction_lost, + cumulative_lost, + extended_high_sequence_number, + jitter); + } + + private: + RtpReceiver* rtp_receiver_; +}; + + +VideoReceiver::VideoReceiver(scoped_refptr<CastThread> cast_thread, + const VideoReceiverConfig& video_config, + PacedPacketSender* const packet_sender) + : cast_thread_(cast_thread), + codec_(video_config.codec), + incoming_ssrc_(video_config.incoming_ssrc), + default_tick_clock_(new base::DefaultTickClock()), + clock_(default_tick_clock_.get()), + incoming_payload_callback_(new LocalRtpVideoData(this)), + incoming_payload_feedback_(new LocalRtpVideoFeedback(this)), + rtp_receiver_(NULL, &video_config, incoming_payload_callback_.get()), + rtp_video_receiver_statistics_( + new LocalRtpReceiverStatistics(&rtp_receiver_)), + weak_factory_(this) { + target_delay_delta_ = base::TimeDelta::FromMilliseconds( + video_config.rtp_max_delay_ms); + int max_unacked_frames = video_config.rtp_max_delay_ms * + video_config.max_frame_rate / 1000; + DCHECK(max_unacked_frames) << "Invalid argument"; + + framer_.reset(new Framer(incoming_payload_feedback_.get(), + video_config.incoming_ssrc, + video_config.decoder_faster_than_max_frame_rate, + max_unacked_frames)); + if (!video_config.use_external_decoder) { + video_decoder_ = new VideoDecoder(cast_thread_, video_config); + } + + rtcp_.reset(new Rtcp(NULL, + packet_sender, + NULL, + rtp_video_receiver_statistics_.get(), + video_config.rtcp_mode, + base::TimeDelta::FromMilliseconds(video_config.rtcp_interval), + false, + video_config.feedback_ssrc, + video_config.rtcp_c_name)); + + rtcp_->SetRemoteSSRC(video_config.incoming_ssrc); + ScheduleNextRtcpReport(); + ScheduleNextCastMessage(); +} + +VideoReceiver::~VideoReceiver() {} + +void VideoReceiver::GetRawVideoFrame( + const VideoFrameDecodedCallback& callback) { + DCHECK(video_decoder_); + scoped_ptr<EncodedVideoFrame> encoded_frame(new EncodedVideoFrame()); + base::TimeTicks render_time; + if (GetEncodedVideoFrame(encoded_frame.get(), &render_time)) { + base::Closure frame_release_callback = + base::Bind(&VideoReceiver::ReleaseFrame, + weak_factory_.GetWeakPtr(), encoded_frame->frame_id); + // Hand the ownership of the encoded frame to the decode thread. + cast_thread_->PostTask(CastThread::VIDEO_DECODER, FROM_HERE, + base::Bind(&VideoReceiver::DecodeVideoFrameThread, + weak_factory_.GetWeakPtr(), encoded_frame.release(), + render_time, callback, frame_release_callback)); + } +} + +// Utility function to run the decoder on a designated decoding thread. +void VideoReceiver::DecodeVideoFrameThread( + const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback, + base::Closure frame_release_callback) { + video_decoder_->DecodeVideoFrame(encoded_frame, render_time, + frame_decoded_callback, frame_release_callback); + // Release memory. + delete encoded_frame; +} + +bool VideoReceiver::GetEncodedVideoFrame(EncodedVideoFrame* encoded_frame, + base::TimeTicks* render_time) { + DCHECK(encoded_frame); + DCHECK(render_time); + + uint32 rtp_timestamp = 0; + bool next_frame = false; + + base::TimeTicks timeout = clock_->NowTicks() + + base::TimeDelta::FromMilliseconds(kMaxFrameWaitMs); + if (!framer_->GetEncodedVideoFrame(timeout, + encoded_frame, + &rtp_timestamp, + &next_frame)) { + return false; + } + base::TimeTicks now = clock_->NowTicks(); + *render_time = GetRenderTime(now, rtp_timestamp); + + base::TimeDelta max_frame_wait_delta = + base::TimeDelta::FromMilliseconds(kMaxFrameWaitMs); + base::TimeDelta time_until_render = *render_time - now; + base::TimeDelta time_until_release = time_until_render - max_frame_wait_delta; + base::TimeDelta zero_delta = base::TimeDelta::FromMilliseconds(0); + if (!next_frame && (time_until_release > zero_delta)) { + // TODO(mikhal): If returning false, then the application should sleep, or + // else which may spin here. Alternatively, we could sleep here, which will + // be posting a delayed task to ourselves, but then can end up in getting + // stuck as well. + return false; + } + + base::TimeDelta dont_show_timeout_delta = time_until_render - + base::TimeDelta::FromMilliseconds(-kDontShowTimeoutMs); + if (codec_ == kVp8 && time_until_render < dont_show_timeout_delta) { + encoded_frame->data[0] &= 0xef; + VLOG(1) << "Don't show frame"; + } + + encoded_frame->codec = codec_; + return true; +} + +base::TimeTicks VideoReceiver::GetRenderTime(base::TimeTicks now, + uint32 rtp_timestamp) { + // Senders time in ms when this frame was captured. + // Note: the senders clock and our local clock might not be synced. + base::TimeTicks rtp_timestamp_in_ticks; + base::TimeTicks time_incoming_packet; + uint32 incoming_rtp_timestamp; + + if (time_offset_.InMilliseconds()) { // was == 0 + incoming_payload_callback_->GetPacketTimeInformation( + &time_incoming_packet, &incoming_rtp_timestamp); + + if (!rtcp_->RtpTimestampInSenderTime(kVideoFrequency, + incoming_rtp_timestamp, + &rtp_timestamp_in_ticks)) { + // We have not received any RTCP to sync the stream play it out as soon as + // possible. + return now; + } + time_offset_ = time_incoming_packet - rtp_timestamp_in_ticks; + } else if (incoming_payload_callback_->GetPacketTimeInformation( + &time_incoming_packet, &incoming_rtp_timestamp)) { + if (rtcp_->RtpTimestampInSenderTime(kVideoFrequency, + incoming_rtp_timestamp, + &rtp_timestamp_in_ticks)) { + // Time to update the time_offset. + base::TimeDelta time_offset = + time_incoming_packet - rtp_timestamp_in_ticks; + time_offset_ = ((kTimeOffsetFilter - 1) * time_offset_ + time_offset) + / kTimeOffsetFilter; + } + } + if (!rtcp_->RtpTimestampInSenderTime(kVideoFrequency, + rtp_timestamp, + &rtp_timestamp_in_ticks)) { + // This can fail if we have not received any RTCP packets in a long time. + return now; + } + return (rtp_timestamp_in_ticks + time_offset_ + target_delay_delta_); +} + +void VideoReceiver::IncomingPacket(const uint8* packet, int length) { + if (Rtcp::IsRtcpPacket(packet, length)) { + rtcp_->IncomingRtcpPacket(packet, length); + return; + } + rtp_receiver_.ReceivedPacket(packet, length); +} + +void VideoReceiver::IncomingRtpPacket(const uint8* payload_data, + int payload_size, + const RtpCastHeader& rtp_header) { + framer_->InsertPacket(payload_data, payload_size, rtp_header); +} + +// Send a cast feedback message. Actual message created in the framer (cast +// message builder). +void VideoReceiver::CastFeedback(const RtcpCastMessage& cast_message) { + rtcp_->SendRtcpCast(cast_message); + time_last_sent_cast_message_= clock_->NowTicks(); +} + +void VideoReceiver::ReleaseFrame(uint8 frame_id) { + framer_->ReleaseFrame(frame_id); +} + +// Send a key frame request to the sender. +void VideoReceiver::RequestKeyFrame() { + rtcp_->SendRtcpPli(incoming_ssrc_); +} + +// Cast messages should be sent within a maximum interval. Schedule a call +// if not triggered elsewhere, e.g. by the cast message_builder. +void VideoReceiver::ScheduleNextCastMessage() { + base::TimeTicks send_time; + framer_->TimeToSendNextCastMessage(&send_time); + + base::TimeDelta time_to_send = send_time - clock_->NowTicks(); + time_to_send = std::max(time_to_send, + base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + cast_thread_->PostDelayedTask(CastThread::MAIN, FROM_HERE, + base::Bind(&VideoReceiver::SendNextCastMessage, + weak_factory_.GetWeakPtr()), time_to_send); +} + +void VideoReceiver::SendNextCastMessage() { + framer_->SendCastMessage(); // Will only send a message if it is time. + ScheduleNextCastMessage(); +} + +// Schedule the next RTCP report to be sent back to the sender. +void VideoReceiver::ScheduleNextRtcpReport() { + base::TimeDelta time_to_next = + rtcp_->TimeToSendNextRtcpReport() - clock_->NowTicks(); + + time_to_next = std::max(time_to_next, + base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + + cast_thread_->PostDelayedTask(CastThread::MAIN, FROM_HERE, + base::Bind(&VideoReceiver::SendNextRtcpReport, + weak_factory_.GetWeakPtr()), time_to_next); +} + +void VideoReceiver::SendNextRtcpReport() { + rtcp_->SendRtcpReport(incoming_ssrc_); + ScheduleNextRtcpReport(); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/video_receiver/video_receiver.gypi b/media/cast/video_receiver/video_receiver.gypi new file mode 100644 index 0000000..bbee92e5 --- /dev/null +++ b/media/cast/video_receiver/video_receiver.gypi @@ -0,0 +1,30 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of the source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'cast_video_receiver', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + '<(DEPTH)/third_party/', + '<(DEPTH)/third_party/webrtc', + ], + 'sources': [ + 'video_decoder.h', + 'video_decoder.cc', + 'video_receiver.h', + 'video_receiver.cc', + ], # source + 'dependencies': [ + 'framer/framer.gyp:cast_framer', + 'video_receiver/codecs/vp8/vp8_decoder.gyp:cast_vp8_decoder', + 'rtp_receiver/rtp_receiver.gyp:cast_rtp_receiver', + ], + }, + ], +} + + diff --git a/media/cast/video_receiver/video_receiver.h b/media/cast/video_receiver/video_receiver.h new file mode 100644 index 0000000..40d0b03 --- /dev/null +++ b/media/cast/video_receiver/video_receiver.h @@ -0,0 +1,124 @@ +// Copyright 2013 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. + +#ifndef MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ +#define MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/default_tick_clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/cast_thread.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/rtp_common/rtp_defines.h" +#include "media/cast/rtp_receiver/rtp_receiver.h" + +namespace media { +namespace cast { + +class Framer; +class LocalRtpVideoData; +class LocalRtpVideoFeedback; +class PacedPacketSender; +class PeerVideoReceiver; +class Rtcp; +class RtpReceiverStatistics; +class VideoDecoder; + + +// Should only be called from the Main cast thread. +class VideoReceiver : public base::NonThreadSafe, + public base::SupportsWeakPtr<VideoReceiver> { + public: + + VideoReceiver(scoped_refptr<CastThread> cast_thread, + const VideoReceiverConfig& video_config, + PacedPacketSender* const packet_sender); + + virtual ~VideoReceiver(); + + // Request a raw frame. Will return frame via callback when available. + void GetRawVideoFrame(const VideoFrameDecodedCallback& callback); + + // Request an encoded frame. Memory allocated by application. + bool GetEncodedVideoFrame(EncodedVideoFrame* video_frame, + base::TimeTicks* render_time); + + // Insert a RTP packet to the video receiver. + void IncomingPacket(const uint8* packet, int length); + + // Release frame - should be called following a GetEncodedVideoFrame call. + // Removes frame from the frame map in the framer. + void ReleaseFrame(uint8 frame_id); + + void set_clock(base::TickClock* clock) { + clock_ = clock; + rtcp_->set_clock(clock); + } + protected: + void IncomingRtpPacket(const uint8* payload_data, + int payload_size, + const RtpCastHeader& rtp_header); + + void DecodeVideoFrameThread( + const EncodedVideoFrame* encoded_frame, + const base::TimeTicks render_time, + const VideoFrameDecodedCallback& frame_decoded_callback, + base::Closure frame_release_callback); + + private: + friend class LocalRtpVideoData; + friend class LocalRtpVideoFeedback; + + void CastFeedback(const RtcpCastMessage& cast_message); + void RequestKeyFrame(); + + // Returns Render time based on current time and the rtp timestamp. + base::TimeTicks GetRenderTime(base::TimeTicks now, uint32 rtp_timestamp); + + // Schedule timing for the next cast message. + void ScheduleNextCastMessage(); + + // Schedule timing for the next RTCP report. + void ScheduleNextRtcpReport(); + // Actually send the next cast message. + void SendNextCastMessage(); + // Actually send the next RTCP report. + void SendNextRtcpReport(); + + scoped_refptr<VideoDecoder> video_decoder_; + scoped_refptr<CastThread> cast_thread_; + scoped_ptr<Framer> framer_; + const VideoCodec codec_; + const uint32 incoming_ssrc_; + base::TimeDelta target_delay_delta_; + scoped_ptr<LocalRtpVideoData> incoming_payload_callback_; + scoped_ptr<LocalRtpVideoFeedback> incoming_payload_feedback_; + RtpReceiver rtp_receiver_; + scoped_ptr<Rtcp> rtcp_; + scoped_ptr<RtpReceiverStatistics> rtp_video_receiver_statistics_; + base::TimeTicks time_last_sent_cast_message_; + // Sender-receiver offset estimation. + base::TimeDelta time_offset_; + + scoped_ptr<base::TickClock> default_tick_clock_; + base::TickClock* clock_; + + base::WeakPtrFactory<VideoReceiver> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(VideoReceiver); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ + diff --git a/media/cast/video_receiver/video_receiver_unittest.cc b/media/cast/video_receiver/video_receiver_unittest.cc new file mode 100644 index 0000000..4052d8f --- /dev/null +++ b/media/cast/video_receiver/video_receiver_unittest.cc @@ -0,0 +1,141 @@ +// Copyright 2013 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/threading/thread.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_thread.h" +#include "media/cast/pacing/mock_paced_packet_sender.h" +#include "media/cast/video_receiver/video_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" + +static const int kPacketSize = 1500; +static const int64 kStartMillisecond = 123456789; + + +namespace media { +namespace cast { + +// was thread counted thread safe. +class TestVideoReceiverCallback : + public base::RefCountedThreadSafe<TestVideoReceiverCallback> { + public: + TestVideoReceiverCallback() + :num_called_(0) {} + // TODO(mikhal): Set and check expectations. + void DecodeComplete(scoped_ptr<I420VideoFrame> frame, + const base::TimeTicks render_time) { + ++num_called_; + } + int number_times_called() { return num_called_;} + private: + int num_called_; +}; + +class PeerVideoReceiver : public VideoReceiver { + public: + PeerVideoReceiver(scoped_refptr<CastThread> cast_thread, + const VideoReceiverConfig& video_config, + PacedPacketSender* const packet_sender) + : VideoReceiver(cast_thread, video_config, packet_sender) { + } + using VideoReceiver::IncomingRtpPacket; +}; + + +class VideoReceiverTest : public ::testing::Test { + protected: + VideoReceiverTest() { + // Configure to use vp8 software implementation. + config_.codec = kVp8; + config_.use_external_decoder = false; + cast_thread_ = new CastThread(MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current()); + receiver_.reset(new + PeerVideoReceiver(cast_thread_, config_, &mock_transport_)); + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + video_receiver_callback_ = new TestVideoReceiverCallback(); + receiver_->set_clock(&testing_clock_); + } + + ~VideoReceiverTest() {} + + virtual void SetUp() { + payload_.assign(kPacketSize, 0); + + // Always start with a key frame. + rtp_header_.is_key_frame = true; + rtp_header_.frame_id = 0; + rtp_header_.packet_id = 0; + rtp_header_.max_packet_id = 0; + rtp_header_.is_reference = false; + rtp_header_.reference_frame_id = 0; + } + + // Used in MessageLoopProxy::current(). + base::MessageLoop loop_; + MockPacedPacketSender mock_transport_; + VideoReceiverConfig config_; + scoped_ptr<PeerVideoReceiver> receiver_; + std::vector<uint8> payload_; + RtpCastHeader rtp_header_; + base::SimpleTestTickClock testing_clock_; + + scoped_refptr<CastThread> cast_thread_; + scoped_refptr<TestVideoReceiverCallback> video_receiver_callback_; +}; + +TEST_F(VideoReceiverTest, GetOnePacketEncodedframe) { + base::RunLoop run_loop; + receiver_->IncomingRtpPacket(payload_.data(), payload_.size(), rtp_header_); + EncodedVideoFrame video_frame; + base::TimeTicks render_time; + EXPECT_TRUE(receiver_->GetEncodedVideoFrame(&video_frame, &render_time)); + EXPECT_TRUE(video_frame.key_frame); + EXPECT_EQ(kVp8, video_frame.codec); + run_loop.RunUntilIdle(); +} + +TEST_F(VideoReceiverTest, MultiplePackets) { + base::RunLoop run_loop; + rtp_header_.max_packet_id = 2; + receiver_->IncomingRtpPacket(payload_.data(), payload_.size(), rtp_header_); + ++rtp_header_.packet_id; + ++rtp_header_.webrtc.header.sequenceNumber; + receiver_->IncomingRtpPacket(payload_.data(), payload_.size(), rtp_header_); + ++rtp_header_.packet_id; + receiver_->IncomingRtpPacket(payload_.data(), payload_.size(), rtp_header_); + EncodedVideoFrame video_frame; + base::TimeTicks render_time; + EXPECT_TRUE(receiver_->GetEncodedVideoFrame(&video_frame, &render_time)); + run_loop.RunUntilIdle(); +} + +// TODO(pwesin): add encoded frames. +TEST_F(VideoReceiverTest, GetOnePacketRawframe) { + base::RunLoop run_loop; + receiver_->IncomingRtpPacket(payload_.data(), payload_.size(), rtp_header_); + // Decode error - requires legal input. + VideoFrameDecodedCallback frame_decoded_callback = + base::Bind(&TestVideoReceiverCallback::DecodeComplete, + video_receiver_callback_); + receiver_->GetRawVideoFrame(frame_decoded_callback); + run_loop.RunUntilIdle(); +} + +} // namespace cast +} // namespace media + + |