Index: media/filters/chunk_demuxer_unittest.cc |
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..53d2084d1b75bcc15dd42806395b8ee67f17b5ef |
--- /dev/null |
+++ b/media/filters/chunk_demuxer_unittest.cc |
@@ -0,0 +1,411 @@ |
+// Copyright (c) 2011 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 "base/base_paths.h" |
+#include "base/bind.h" |
+#include "base/file_util.h" |
+#include "base/path_service.h" |
+#include "media/base/media.h" |
+#include "media/base/mock_callback.h" |
+#include "media/base/mock_ffmpeg.h" |
+#include "media/filters/chunk_demuxer.h" |
+#include "media/webm/cluster_builder.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::InSequence; |
+using ::testing::Return; |
+using ::testing::SetArgumentPointee; |
+using ::testing::_; |
+ |
+namespace media { |
+ |
+static const uint8 kTracksHeader[] = { |
+ 0x16, 0x54, 0xAE, 0x6B, // Tracks ID |
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0) |
+}; |
+ |
+static const int kTracksHeaderSize = sizeof(kTracksHeader); |
+static const int kTracksSizeOffset = 4; |
+ |
+static const int kVideoTrackNum = 1; |
+static const int kAudioTrackNum = 2; |
+ |
+class ChunkDemuxerTest : public testing::Test{ |
+ protected: |
+ enum CodecsIndex { |
+ AUDIO, |
+ VIDEO, |
+ MAX_CODECS_INDEX |
+ }; |
+ |
+ ChunkDemuxerTest() |
+ : demuxer_(new ChunkDemuxer()) { |
+ memset(&format_context_, 0, sizeof(format_context_)); |
+ memset(&streams_, 0, sizeof(streams_)); |
+ memset(&codecs_, 0, sizeof(codecs_)); |
+ |
+ codecs_[VIDEO].codec_type = CODEC_TYPE_VIDEO; |
+ codecs_[VIDEO].codec_id = CODEC_ID_VP8; |
+ codecs_[VIDEO].width = 320; |
+ codecs_[VIDEO].height = 240; |
+ |
+ codecs_[AUDIO].codec_type = CODEC_TYPE_AUDIO; |
+ codecs_[AUDIO].codec_id = CODEC_ID_VORBIS; |
+ codecs_[AUDIO].channels = 2; |
+ codecs_[AUDIO].sample_rate = 44100; |
+ } |
+ |
+ virtual ~ChunkDemuxerTest() { |
+ if (demuxer_.get()) |
+ demuxer_->Shutdown(); |
+ } |
+ |
+ void ReadFile(const std::string& name, scoped_array<uint8>* buffer, |
+ int* size) { |
+ FilePath file_path; |
+ EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); |
+ file_path = file_path.Append(FILE_PATH_LITERAL("media")) |
+ .Append(FILE_PATH_LITERAL("test")) |
+ .Append(FILE_PATH_LITERAL("data")) |
+ .AppendASCII(name); |
+ |
+ int64 tmp = 0; |
+ EXPECT_TRUE(file_util::GetFileSize(file_path, &tmp)); |
+ EXPECT_LT(tmp, 32768); |
+ int file_size = static_cast<int>(tmp); |
+ |
+ buffer->reset(new uint8[file_size]); |
+ EXPECT_EQ(file_size, |
+ file_util::ReadFile(file_path, |
+ reinterpret_cast<char*>(buffer->get()), |
+ file_size)); |
+ *size = file_size; |
+ } |
+ |
+ void CreateInfoTracks(bool has_audio, bool has_video, |
+ scoped_array<uint8>* buffer, int* size) { |
+ scoped_array<uint8> info; |
+ int info_size = 0; |
+ scoped_array<uint8> audio_track_entry; |
+ int audio_track_entry_size = 0; |
+ scoped_array<uint8> video_track_entry; |
+ int video_track_entry_size = 0; |
+ |
+ ReadFile("webm_info_element", &info, &info_size); |
+ ReadFile("webm_vorbis_track_entry", &audio_track_entry, |
+ &audio_track_entry_size); |
+ ReadFile("webm_vp8_track_entry", &video_track_entry, |
+ &video_track_entry_size); |
+ |
+ int tracks_element_size = 0; |
+ |
+ if (has_audio) |
+ tracks_element_size += audio_track_entry_size; |
+ |
+ if (has_video) |
+ tracks_element_size += video_track_entry_size; |
+ |
+ *size = info_size + kTracksHeaderSize + tracks_element_size; |
+ |
+ buffer->reset(new uint8[*size]); |
+ |
+ uint8* buf = buffer->get(); |
+ memcpy(buf, info.get(), info_size); |
+ buf += info_size; |
+ |
+ memcpy(buf, kTracksHeader, kTracksHeaderSize); |
+ |
+ int tmp = tracks_element_size; |
+ for (int i = 7; i > 0; i--) { |
+ buf[kTracksSizeOffset + i] = tmp & 0xff; |
+ tmp >>= 8; |
+ } |
+ |
+ buf += kTracksHeaderSize; |
+ |
+ if (has_audio) { |
+ memcpy(buf, audio_track_entry.get(), audio_track_entry_size); |
+ buf += audio_track_entry_size; |
+ } |
+ |
+ if (has_video) { |
+ memcpy(buf, video_track_entry.get(), video_track_entry_size); |
+ buf += video_track_entry_size; |
+ } |
+ } |
+ |
+ void SetupAVFormatContext(bool has_audio, bool has_video) { |
+ int i = 0; |
+ if (has_audio) { |
+ format_context_.streams[i] = &streams_[i]; |
+ streams_[i].codec = &codecs_[AUDIO]; |
+ streams_[i].duration = 100; |
+ streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; |
+ streams_[i].time_base.num = 1; |
+ i++; |
+ } |
+ |
+ if (has_video) { |
+ format_context_.streams[i] = &streams_[i]; |
+ streams_[i].codec = &codecs_[VIDEO]; |
+ streams_[i].duration = 100; |
+ streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; |
+ streams_[i].time_base.num = 1; |
+ i++; |
+ } |
+ |
+ format_context_.nb_streams = i; |
+ } |
+ |
+void InitDemuxer(bool has_audio, bool has_video) { |
+ EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) |
+ .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), |
+ Return(0))); |
+ |
+ EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_)) |
+ .WillOnce(Return(0)); |
+ |
+ EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_)); |
+ |
+ EXPECT_CALL(mock_ffmpeg_, AVRegisterLockManager(_)) |
+ .WillRepeatedly(Return(0)); |
+ |
+ scoped_array<uint8> info_tracks; |
+ int info_tracks_size = 0; |
+ CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size); |
+ |
+ SetupAVFormatContext(has_audio, has_video); |
+ |
+ EXPECT_EQ(demuxer_->Init(info_tracks.get(), info_tracks_size), |
+ has_audio || has_video); |
+ } |
+ |
+ void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) { |
+ uint8 data[] = { 0x00 }; |
+ cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data)); |
+ } |
+ |
+ MOCK_METHOD1(Checkpoint, void(int id)); |
+ |
+ MockFFmpeg mock_ffmpeg_; |
+ |
+ AVFormatContext format_context_; |
+ AVCodecContext codecs_[MAX_CODECS_INDEX]; |
+ AVStream streams_[MAX_CODECS_INDEX]; |
+ |
+ scoped_refptr<ChunkDemuxer> demuxer_; |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest); |
+}; |
+ |
+TEST_F(ChunkDemuxerTest, TestInit) { |
+ // Test no streams, audio-only, video-only, and audio & video scenarios. |
+ for (int i = 0; i < 4; i++) { |
+ bool has_audio = (i & 0x1) != 0; |
+ bool has_video = (i & 0x2) != 0; |
+ |
+ demuxer_ = new ChunkDemuxer(); |
+ InitDemuxer(has_audio, has_video); |
+ EXPECT_EQ(demuxer_->GetStream(DemuxerStream::AUDIO).get() != NULL, |
+ has_audio); |
+ EXPECT_EQ(demuxer_->GetStream(DemuxerStream::VIDEO).get() != NULL, |
+ has_video); |
+ demuxer_->Shutdown(); |
+ demuxer_ = NULL; |
+ } |
+} |
+ |
+// Makes sure that Seek() reports an error if Shutdown() |
+// is called before the first cluster is passed to the demuxer. |
+TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { |
+ InitDemuxer(true, true); |
+ |
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0), |
+ NewExpectedStatusCB(PIPELINE_ERROR_ABORT)); |
+} |
+ |
+// Test that Seek() completes successfully when the first cluster |
+// arrives. |
+TEST_F(ChunkDemuxerTest, TestAddDataAfterSeek) { |
+ InitDemuxer(true, true); |
+ |
+ InSequence s; |
+ |
+ EXPECT_CALL(*this, Checkpoint(1)); |
+ |
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0), |
+ NewExpectedStatusCB(PIPELINE_OK)); |
+ |
+ EXPECT_CALL(*this, Checkpoint(2)); |
+ |
+ ClusterBuilder cb; |
+ cb.SetClusterTimecode(0); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 0); |
+ scoped_ptr<Cluster> cluster(cb.Finish()); |
+ |
+ Checkpoint(1); |
+ |
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size())); |
+ |
+ Checkpoint(2); |
+} |
+ |
+// Test the case where AddData() is called before Init(). This can happen |
+// when JavaScript starts sending data before the pipeline is completely |
+// initialized. |
+TEST_F(ChunkDemuxerTest, TestAddDataBeforeInit) { |
+ ClusterBuilder cb; |
+ cb.SetClusterTimecode(0); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 0); |
+ scoped_ptr<Cluster> cluster(cb.Finish()); |
+ |
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size())); |
+ |
+ InitDemuxer(true, true); |
+ |
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0), |
+ NewExpectedStatusCB(PIPELINE_OK)); |
+} |
+ |
+static void OnReadDone(const base::TimeDelta& expected_time, |
+ bool* called, |
+ Buffer* buffer) { |
+ EXPECT_EQ(expected_time, buffer->GetTimestamp()); |
+ *called = true; |
+} |
+ |
+// Make sure Read() callbacks are dispatched with the proper data. |
+TEST_F(ChunkDemuxerTest, TestRead) { |
+ InitDemuxer(true, true); |
+ |
+ scoped_refptr<DemuxerStream> audio = |
+ demuxer_->GetStream(DemuxerStream::AUDIO); |
+ scoped_refptr<DemuxerStream> video = |
+ demuxer_->GetStream(DemuxerStream::VIDEO); |
+ |
+ bool audio_read_done = false; |
+ bool video_read_done = false; |
+ audio->Read(base::Bind(&OnReadDone, |
+ base::TimeDelta::FromMilliseconds(32), |
+ &audio_read_done)); |
+ |
+ video->Read(base::Bind(&OnReadDone, |
+ base::TimeDelta::FromMilliseconds(123), |
+ &video_read_done)); |
+ |
+ ClusterBuilder cb; |
+ cb.SetClusterTimecode(0); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 32); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 123); |
+ scoped_ptr<Cluster> cluster(cb.Finish()); |
+ |
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size())); |
+ |
+ EXPECT_TRUE(audio_read_done); |
+ EXPECT_TRUE(video_read_done); |
+} |
+ |
+TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { |
+ InitDemuxer(true, true); |
+ |
+ ClusterBuilder cb; |
+ |
+ cb.SetClusterTimecode(10); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 10); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 10); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 33); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 43); |
+ scoped_ptr<Cluster> clusterA(cb.Finish()); |
+ |
+ EXPECT_TRUE(demuxer_->AddData(clusterA->data(), clusterA->size())); |
+ |
+ // Cluster B starts before clusterA and has data |
+ // that overlaps. |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 7); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 28); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 40); |
+ scoped_ptr<Cluster> clusterB(cb.Finish()); |
+ |
+ // Make sure that AddData() fails because this cluster data |
+ // is before previous data. |
+ EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size())); |
+ |
+ // Cluster C starts after clusterA. |
+ cb.SetClusterTimecode(56); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 56); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 76); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 79); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 109); |
+ scoped_ptr<Cluster> clusterC(cb.Finish()); |
+ |
+ // Verify that clusterC is accepted. |
+ EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size())); |
+ |
+ // Flush and try clusterB again. |
+ demuxer_->FlushData(); |
+ EXPECT_TRUE(demuxer_->AddData(clusterB->data(), clusterB->size())); |
+ |
+ // Following that with clusterC should work too since it doesn't |
+ // overlap with clusterB. |
+ EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size())); |
+} |
+ |
+TEST_F(ChunkDemuxerTest, TestInvalidBlockSequences) { |
+ InitDemuxer(true, true); |
+ |
+ ClusterBuilder cb; |
+ |
+ // Test the case where timecode is not monotonically |
+ // increasing but stays above the cluster timecode. |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 10); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 7); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 15); |
+ scoped_ptr<Cluster> clusterA(cb.Finish()); |
+ |
+ EXPECT_FALSE(demuxer_->AddData(clusterA->data(), clusterA->size())); |
+ |
+ // Test timecodes going backwards before cluster timecode. |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 3); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 3); |
+ scoped_ptr<Cluster> clusterB(cb.Finish()); |
+ |
+ EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size())); |
+ |
+ // Test strict monotonic increasing timestamps on a per stream |
+ // basis. |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 7); |
+ scoped_ptr<Cluster> clusterC(cb.Finish()); |
+ |
+ EXPECT_FALSE(demuxer_->AddData(clusterC->data(), clusterC->size())); |
+ |
+ // Test strict monotonic increasing timestamps on a per stream |
+ // basis across clusters. |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 5); |
+ scoped_ptr<Cluster> clusterD(cb.Finish()); |
+ |
+ EXPECT_TRUE(demuxer_->AddData(clusterD->data(), clusterD->size())); |
+ |
+ cb.SetClusterTimecode(5); |
+ AddSimpleBlock(&cb, kAudioTrackNum, 5); |
+ AddSimpleBlock(&cb, kVideoTrackNum, 7); |
+ scoped_ptr<Cluster> clusterE(cb.Finish()); |
+ |
+ EXPECT_FALSE(demuxer_->AddData(clusterE->data(), clusterE->size())); |
+} |
+ |
+} // namespace media |