summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authormikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-19 23:27:01 +0000
committermikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-19 23:27:01 +0000
commit641d8ecb3f0b3436d80376646b00af0dca6e7f0d (patch)
treef71de1cde9fce9234f3b7f2e24aa1a919dfc323d /media
parentbcc5c381a6523c0e1acf6c660018b0f5fb1a351a (diff)
downloadchromium_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.gyp6
-rw-r--r--media/cast/cast_receiver.gyp4
-rw-r--r--media/cast/cast_receiver.h9
-rw-r--r--media/cast/video_receiver/codecs/vp8/vp8_decoder.cc68
-rw-r--r--media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp26
-rw-r--r--media/cast/video_receiver/codecs/vp8/vp8_decoder.h37
-rw-r--r--media/cast/video_receiver/video_decoder.cc66
-rw-r--r--media/cast/video_receiver/video_decoder.h48
-rw-r--r--media/cast/video_receiver/video_decoder_unittest.cc99
-rw-r--r--media/cast/video_receiver/video_receiver.cc337
-rw-r--r--media/cast/video_receiver/video_receiver.gypi30
-rw-r--r--media/cast/video_receiver/video_receiver.h124
-rw-r--r--media/cast/video_receiver/video_receiver_unittest.cc141
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
+
+