Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1769)

Unified Diff: media/filters/chunk_demuxer_unittest.cc

Issue 7203002: Adding ChunkDemuxer implementation. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed unit tests and nits Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/chunk_demuxer_factory.cc ('k') | media/filters/ffmpeg_demuxer.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « media/filters/chunk_demuxer_factory.cc ('k') | media/filters/ffmpeg_demuxer.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698