Chromium Code Reviews| Index: media/cast/video_receiver/video_decoder_unittest.cc |
| diff --git a/media/cast/video_receiver/video_decoder_unittest.cc b/media/cast/video_receiver/video_decoder_unittest.cc |
| index 44de8809f88d10e5d1d499990e3866f62a52eb68..aa6b7ac058c9df3605d49685df77f676e43f1795 100644 |
| --- a/media/cast/video_receiver/video_decoder_unittest.cc |
| +++ b/media/cast/video_receiver/video_decoder_unittest.cc |
| @@ -2,92 +2,186 @@ |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| -#include <stdint.h> |
| +#include <cstdlib> |
| #include "base/bind.h" |
| -#include "base/memory/scoped_ptr.h" |
| -#include "base/test/simple_test_tick_clock.h" |
| -#include "base/time/tick_clock.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/synchronization/condition_variable.h" |
| +#include "base/synchronization/lock.h" |
| +#include "base/time/time.h" |
| #include "media/cast/cast_config.h" |
| -#include "media/cast/cast_defines.h" |
| -#include "media/cast/cast_environment.h" |
| -#include "media/cast/cast_receiver.h" |
| -#include "media/cast/test/fake_single_thread_task_runner.h" |
| +#include "media/cast/test/utility/standalone_cast_environment.h" |
| +#include "media/cast/test/utility/video_utility.h" |
| #include "media/cast/video_receiver/video_decoder.h" |
| -#include "testing/gmock/include/gmock/gmock.h" |
| +#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| namespace media { |
| namespace cast { |
| -using testing::_; |
| +namespace { |
| -// Random frame size for testing. |
| -static const int64 kStartMillisecond = INT64_C(1245); |
| +const int kWidth = 360; |
| +const int kHeight = 240; |
| +const int kFrameRate = 10; |
| -namespace { |
| -class DecodeTestFrameCallback |
| - : public base::RefCountedThreadSafe<DecodeTestFrameCallback> { |
| - public: |
| - DecodeTestFrameCallback() {} |
| +VideoSenderConfig GetVideoSenderConfigForTest() { |
| + VideoSenderConfig config; |
| + config.width = kWidth; |
| + config.height = kHeight; |
| + config.max_frame_rate = kFrameRate; |
| + return config; |
| +} |
| - void DecodeComplete(const scoped_refptr<media::VideoFrame>& decoded_frame, |
| - const base::TimeTicks& render_time) {} |
| +} // namespace |
| + |
| +class VideoDecoderTest |
| + : public ::testing::TestWithParam<transport::VideoCodec> { |
| + public: |
| + VideoDecoderTest() |
| + : cast_environment_(new StandaloneCastEnvironment()), |
| + vp8_encoder_(GetVideoSenderConfigForTest(), 0), |
| + cond_(&lock_) { |
| + vp8_encoder_.Initialize(); |
| + } |
| protected: |
| - virtual ~DecodeTestFrameCallback() {} |
| + virtual void SetUp() OVERRIDE { |
| + VideoReceiverConfig decoder_config; |
| + decoder_config.use_external_decoder = false; |
| + decoder_config.codec = GetParam(); |
| + video_decoder_.reset(new VideoDecoder(cast_environment_, decoder_config)); |
| + CHECK_EQ(STATUS_VIDEO_INITIALIZED, video_decoder_->InitializationResult()); |
| + |
| + next_frame_timestamp_ = base::TimeDelta(); |
| + last_frame_id_ = 0; |
| + seen_a_decoded_frame_ = false; |
| + |
| + total_video_frames_feed_in_ = 0; |
| + total_video_frames_decoded_ = 0; |
| + } |
| - private: |
| - friend class base::RefCountedThreadSafe<DecodeTestFrameCallback>; |
| + // Called from the unit test thread to create another EncodedVideoFrame and |
| + // push it into the decoding pipeline. |
| + void FeedMoreVideo(int num_dropped_frames) { |
| + // Prepare a simulated EncodedVideoFrame to feed into the VideoDecoder. |
| + |
| + const gfx::Size frame_size(kWidth, kHeight); |
| + const scoped_refptr<VideoFrame> video_frame = |
| + VideoFrame::CreateFrame(VideoFrame::YV12, |
| + frame_size, |
| + gfx::Rect(frame_size), |
| + frame_size, |
| + next_frame_timestamp_); |
| + next_frame_timestamp_ += base::TimeDelta::FromSeconds(1) / kFrameRate; |
| + PopulateVideoFrame(video_frame, 0); |
| + |
| + // Encode |frame| into |encoded_frame->data|. |
| + scoped_ptr<transport::EncodedVideoFrame> encoded_frame( |
| + new transport::EncodedVideoFrame()); |
| + CHECK_EQ(transport::kVp8, GetParam()); // Only support VP8 test currently. |
| + vp8_encoder_.Encode(video_frame, encoded_frame.get()); |
| + encoded_frame->codec = GetParam(); |
| + encoded_frame->frame_id = last_frame_id_ + 1 + num_dropped_frames; |
| + last_frame_id_ = encoded_frame->frame_id; |
| + |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + ++total_video_frames_feed_in_; |
|
hubbe
2014/04/07 18:40:17
Don't we have some sort of atomic increment class/
miu
2014/04/08 00:59:41
No. There's base::Atomic32, but the comments at t
|
| + } |
| + |
| + cast_environment_->PostTask( |
| + CastEnvironment::MAIN, |
| + FROM_HERE, |
| + base::Bind(&VideoDecoder::DecodeFrame, |
| + base::Unretained(video_decoder_.get()), |
| + base::Passed(&encoded_frame), |
| + base::Bind(&VideoDecoderTest::OnDecodedFrame, |
| + base::Unretained(this), |
| + video_frame, |
| + num_dropped_frames == 0))); |
| + } |
| - DISALLOW_COPY_AND_ASSIGN(DecodeTestFrameCallback); |
| -}; |
| -} // namespace |
| + // Blocks the caller until all video that has been feed in has been decoded. |
| + void WaitForAllVideoToBeDecoded() { |
| + DCHECK(!cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| + base::AutoLock auto_lock(lock_); |
| + while (total_video_frames_decoded_ < total_video_frames_feed_in_) |
| + cond_.Wait(); |
| + EXPECT_EQ(total_video_frames_feed_in_, total_video_frames_decoded_); |
| + } |
| -class VideoDecoderTest : public ::testing::Test { |
| - protected: |
| - VideoDecoderTest() |
| - : testing_clock_(new base::SimpleTestTickClock()), |
| - task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), |
| - cast_environment_( |
| - new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_), |
| - task_runner_, |
| - task_runner_, |
| - task_runner_)), |
| - test_callback_(new DecodeTestFrameCallback()) { |
| - // Configure to vp8. |
| - config_.codec = transport::kVp8; |
| - config_.use_external_decoder = false; |
| - decoder_.reset(new VideoDecoder(config_, cast_environment_)); |
| - testing_clock_->Advance( |
| - base::TimeDelta::FromMilliseconds(kStartMillisecond)); |
| + private: |
| + // Called by |vp8_decoder_| to deliver each frame of decoded video. |
| + void OnDecodedFrame(const scoped_refptr<VideoFrame>& expected_video_frame, |
| + bool should_be_continuous, |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + bool is_continuous) { |
| + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| + |
| + // A NULL |video_frame| indicates a decode error, which we don't expect. |
| + ASSERT_FALSE(!video_frame); |
| + |
| + // Did the decoder detect whether frames were dropped? |
| + EXPECT_EQ(should_be_continuous, is_continuous); |
| + |
| + // Does the video data seem to be intact? |
| + EXPECT_EQ(expected_video_frame->coded_size().width(), |
| + video_frame->coded_size().width()); |
| + EXPECT_EQ(expected_video_frame->coded_size().height(), |
| + video_frame->coded_size().height()); |
| + EXPECT_LT(40.0, I420PSNR(expected_video_frame, video_frame)); |
| + // TODO(miu): Once we start using VideoFrame::timestamp_, check that here. |
| + |
| + // Signal the main test thread that more video was decoded. |
| + base::AutoLock auto_lock(lock_); |
| + ++total_video_frames_decoded_; |
| + cond_.Signal(); |
| } |
| - virtual ~VideoDecoderTest() {} |
| + const scoped_refptr<StandaloneCastEnvironment> cast_environment_; |
| + scoped_ptr<VideoDecoder> video_decoder_; |
| + base::TimeDelta next_frame_timestamp_; |
| + uint32 last_frame_id_; |
| + bool seen_a_decoded_frame_; |
| + |
| + Vp8Encoder vp8_encoder_; |
| - scoped_ptr<VideoDecoder> decoder_; |
| - VideoReceiverConfig config_; |
| - base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. |
| - scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; |
| - scoped_refptr<CastEnvironment> cast_environment_; |
| - scoped_refptr<DecodeTestFrameCallback> test_callback_; |
| + base::Lock lock_; |
| + base::ConditionVariable cond_; |
| + int total_video_frames_feed_in_; |
| + int total_video_frames_decoded_; |
| DISALLOW_COPY_AND_ASSIGN(VideoDecoderTest); |
| }; |
| -// TODO(pwestin): EXPECT_DEATH tests can not pass valgrind. |
| -TEST_F(VideoDecoderTest, DISABLED_SizeZero) { |
| - transport::EncodedVideoFrame encoded_frame; |
| - base::TimeTicks render_time; |
| - encoded_frame.codec = transport::kVp8; |
| - EXPECT_DEATH( |
| - decoder_->DecodeVideoFrame( |
| - &encoded_frame, |
| - render_time, |
| - base::Bind(&DecodeTestFrameCallback::DecodeComplete, test_callback_)), |
| - "Empty frame"); |
| +TEST_P(VideoDecoderTest, DecodesFrames) { |
| + const int kNumFrames = 10; |
| + for (int i = 0; i < kNumFrames; ++i) |
| + FeedMoreVideo(0); |
| + WaitForAllVideoToBeDecoded(); |
| +} |
| + |
| +TEST_P(VideoDecoderTest, RecoversFromDroppedFrames) { |
| + const int kNumFrames = 100; |
| + int next_drop_at = 3; |
| + int next_num_dropped = 1; |
| + for (int i = 0; i < kNumFrames; ++i) { |
| + if (i == next_drop_at) { |
| + const int num_dropped = next_num_dropped++; |
| + next_drop_at *= 2; |
| + i += num_dropped; |
| + FeedMoreVideo(num_dropped); |
| + } else { |
| + FeedMoreVideo(0); |
| + } |
| + } |
| + WaitForAllVideoToBeDecoded(); |
| } |
| -// TODO(pwestin): Test decoding a real frame. |
| +INSTANTIATE_TEST_CASE_P(VideoDecoderTestScenarios, |
| + VideoDecoderTest, |
| + ::testing::Values(transport::kVp8)); |
| } // namespace cast |
| } // namespace media |