Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/base_paths.h" | |
| 6 #include "base/bind.h" | |
| 7 #include "base/file_util.h" | |
| 8 #include "base/path_service.h" | |
| 9 #include "media/base/media.h" | |
| 10 #include "media/base/mock_callback.h" | |
| 11 #include "media/base/mock_ffmpeg.h" | |
| 12 #include "media/filters/chunk_demuxer.h" | |
| 13 #include "media/webm/cluster_builder.h" | |
| 14 #include "testing/gtest/include/gtest/gtest.h" | |
| 15 | |
| 16 using ::testing::InSequence; | |
| 17 using ::testing::Return; | |
| 18 using ::testing::SetArgumentPointee; | |
| 19 using ::testing::_; | |
| 20 | |
| 21 namespace media { | |
| 22 | |
| 23 static const uint8 kTracksHeader[] = { | |
| 24 0x16, 0x54, 0xAE, 0x6B, // Tracks ID | |
| 25 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0) | |
| 26 }; | |
| 27 | |
| 28 static const int kTracksHeaderSize = sizeof(kTracksHeader); | |
| 29 static const int kTracksSizeOffset = 4; | |
| 30 | |
| 31 static const int kVideoTrackNum = 1; | |
| 32 static const int kAudioTrackNum = 2; | |
| 33 | |
| 34 class ChunkDemuxerTest : public testing::Test{ | |
| 35 protected: | |
| 36 enum CodecsIndex { | |
| 37 AUDIO, | |
| 38 VIDEO, | |
| 39 MAX_CODECS_INDEX | |
| 40 }; | |
| 41 | |
| 42 ChunkDemuxerTest() | |
| 43 : demuxer_(new ChunkDemuxer()) { | |
| 44 memset(&format_context_, 0, sizeof(format_context_)); | |
| 45 memset(&streams_, 0, sizeof(streams_)); | |
| 46 memset(&codecs_, 0, sizeof(codecs_)); | |
| 47 | |
| 48 codecs_[VIDEO].codec_type = CODEC_TYPE_VIDEO; | |
| 49 codecs_[VIDEO].codec_id = CODEC_ID_VP8; | |
| 50 codecs_[VIDEO].width = 320; | |
| 51 codecs_[VIDEO].height = 240; | |
| 52 | |
| 53 codecs_[AUDIO].codec_type = CODEC_TYPE_AUDIO; | |
| 54 codecs_[AUDIO].codec_id = CODEC_ID_VORBIS; | |
| 55 codecs_[AUDIO].channels = 2; | |
| 56 codecs_[AUDIO].sample_rate = 44100; | |
| 57 } | |
| 58 | |
| 59 ~ChunkDemuxerTest() { | |
|
scherkus (not reviewing)
2011/06/28 20:03:29
virtual
acolwell GONE FROM CHROMIUM
2011/06/29 16:38:43
Done.
| |
| 60 if (demuxer_.get()) | |
| 61 demuxer_->Shutdown(); | |
| 62 } | |
| 63 | |
| 64 void ReadFile(const std::string& name, scoped_array<uint8>* buffer, | |
| 65 int* size) { | |
| 66 FilePath file_path; | |
| 67 EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); | |
| 68 file_path = file_path.Append(FILE_PATH_LITERAL("media")) | |
| 69 .Append(FILE_PATH_LITERAL("test")) | |
| 70 .Append(FILE_PATH_LITERAL("data")) | |
| 71 .Append(name); | |
| 72 | |
| 73 int64 tmp = 0; | |
| 74 EXPECT_TRUE(file_util::GetFileSize(file_path, &tmp)); | |
| 75 EXPECT_LT(tmp, 32768); | |
| 76 int file_size = static_cast<int>(tmp); | |
| 77 | |
| 78 buffer->reset(new uint8[file_size]); | |
| 79 EXPECT_EQ(file_size, | |
| 80 file_util::ReadFile(file_path, | |
| 81 reinterpret_cast<char*>(buffer->get()), | |
| 82 file_size)); | |
| 83 *size = file_size; | |
| 84 } | |
| 85 | |
| 86 void CreateInfoTracks(bool has_audio, bool has_video, | |
| 87 scoped_array<uint8>* buffer, int* size) { | |
| 88 scoped_array<uint8> info; | |
| 89 int info_size = 0; | |
| 90 scoped_array<uint8> audio_track_entry; | |
| 91 int audio_track_entry_size = 0; | |
| 92 scoped_array<uint8> video_track_entry; | |
| 93 int video_track_entry_size = 0; | |
| 94 | |
| 95 ReadFile("webm_info_element", &info, &info_size); | |
| 96 ReadFile("webm_vorbis_track_entry", &audio_track_entry, | |
| 97 &audio_track_entry_size); | |
| 98 ReadFile("webm_vp8_track_entry", &video_track_entry, | |
| 99 &video_track_entry_size); | |
| 100 | |
| 101 int tracks_element_size = 0; | |
| 102 | |
| 103 if (has_audio) | |
| 104 tracks_element_size += audio_track_entry_size; | |
| 105 | |
| 106 if (has_video) | |
| 107 tracks_element_size += video_track_entry_size; | |
| 108 | |
| 109 *size = info_size + kTracksHeaderSize + tracks_element_size; | |
| 110 | |
| 111 buffer->reset(new uint8[*size]); | |
| 112 | |
| 113 uint8* buf = buffer->get(); | |
| 114 memcpy(buf, info.get(), info_size); | |
| 115 buf += info_size; | |
| 116 | |
| 117 memcpy(buf, kTracksHeader, kTracksHeaderSize); | |
| 118 | |
| 119 int tmp = tracks_element_size; | |
| 120 for (int i = 7; i > 0; i--) { | |
| 121 buf[kTracksSizeOffset + i] = tmp & 0xff; | |
| 122 tmp >>= 8; | |
| 123 } | |
| 124 | |
| 125 buf += kTracksHeaderSize; | |
| 126 | |
| 127 if (has_audio) { | |
| 128 memcpy(buf, audio_track_entry.get(), audio_track_entry_size); | |
| 129 buf += audio_track_entry_size; | |
| 130 } | |
| 131 | |
| 132 if (has_video) { | |
| 133 memcpy(buf, video_track_entry.get(), video_track_entry_size); | |
| 134 buf += video_track_entry_size; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 void SetupAVFormatContext(bool has_audio, bool has_video) { | |
|
scherkus (not reviewing)
2011/06/28 20:03:29
sadness
| |
| 139 int i = 0; | |
| 140 if (has_audio) { | |
| 141 format_context_.streams[i] = &streams_[i]; | |
| 142 streams_[i].codec = &codecs_[AUDIO]; | |
| 143 streams_[i].duration = 100; | |
| 144 streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; | |
| 145 streams_[i].time_base.num = 1; | |
| 146 i++; | |
| 147 } | |
| 148 | |
| 149 if (has_video) { | |
| 150 format_context_.streams[i] = &streams_[i]; | |
| 151 streams_[i].codec = &codecs_[VIDEO]; | |
| 152 streams_[i].duration = 100; | |
| 153 streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; | |
| 154 streams_[i].time_base.num = 1; | |
| 155 i++; | |
| 156 } | |
| 157 | |
| 158 format_context_.nb_streams = i; | |
| 159 } | |
| 160 | |
| 161 bool InitDemuxer(bool has_audio, bool has_video) { | |
|
scherkus (not reviewing)
2011/06/28 20:03:29
all of your InitDemuxer EXPECT_EQs are true, so wh
acolwell GONE FROM CHROMIUM
2011/06/29 16:38:43
Done.
| |
| 162 EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) | |
| 163 .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), | |
| 164 Return(0))); | |
| 165 | |
| 166 EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_)) | |
| 167 .WillOnce(Return(0)); | |
| 168 | |
| 169 EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_)); | |
| 170 | |
| 171 EXPECT_CALL(mock_ffmpeg_, AVRegisterLockManager(_)) | |
| 172 .WillRepeatedly(Return(0)); | |
|
scherkus (not reviewing)
2011/06/28 20:03:29
indent
acolwell GONE FROM CHROMIUM
2011/06/29 16:38:43
Done.
| |
| 173 | |
| 174 scoped_array<uint8> info_tracks; | |
| 175 int info_tracks_size = 0; | |
| 176 | |
| 177 CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size); | |
| 178 SetupAVFormatContext(has_audio, has_video); | |
| 179 return demuxer_->Init(info_tracks.get(), info_tracks_size); | |
| 180 } | |
| 181 | |
| 182 void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) { | |
| 183 uint8 data[] = { 0x00 }; | |
| 184 cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data)); | |
| 185 } | |
| 186 | |
| 187 MOCK_METHOD1(Checkpoint, void(int id)); | |
| 188 | |
| 189 MockFFmpeg mock_ffmpeg_; | |
| 190 | |
| 191 AVFormatContext format_context_; | |
| 192 AVCodecContext codecs_[MAX_CODECS_INDEX]; | |
| 193 AVStream streams_[MAX_CODECS_INDEX]; | |
| 194 | |
| 195 scoped_refptr<ChunkDemuxer> demuxer_; | |
| 196 | |
| 197 private: | |
| 198 DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest); | |
| 199 }; | |
| 200 | |
| 201 TEST_F(ChunkDemuxerTest, TestInit) { | |
| 202 // Test no streams, audio-only, video-only, and audio & video scenarios. | |
| 203 for (int i = 0; i < 4; i++) { | |
| 204 bool has_audio = (i & 0x1) != 0; | |
| 205 bool has_video = (i & 0x2) != 0; | |
| 206 bool has_a_stream = has_audio | has_video; | |
| 207 | |
| 208 demuxer_ = new ChunkDemuxer(); | |
| 209 EXPECT_EQ(InitDemuxer(has_audio, has_video), has_a_stream); | |
| 210 EXPECT_EQ(demuxer_->GetStream(DemuxerStream::AUDIO).get() != NULL, | |
| 211 has_audio); | |
| 212 EXPECT_EQ(demuxer_->GetStream(DemuxerStream::VIDEO).get() != NULL, | |
| 213 has_video); | |
| 214 demuxer_->Shutdown(); | |
| 215 demuxer_ = NULL; | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 // Makes sure that Seek() reports an error if Shutdown() | |
| 220 // is called before the first cluster is passed to the demuxer. | |
| 221 TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { | |
| 222 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 223 | |
| 224 demuxer_->Seek(base::TimeDelta::FromSeconds(0), | |
| 225 NewExpectedStatusCB(PIPELINE_ERROR_ABORT)); | |
| 226 } | |
| 227 | |
| 228 // Test that Seek() completes successfully when the first cluster | |
| 229 // arrives. | |
| 230 TEST_F(ChunkDemuxerTest, TestAddDataAfterSeek) { | |
| 231 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 232 | |
| 233 InSequence s; | |
| 234 | |
| 235 EXPECT_CALL(*this, Checkpoint(1)); | |
| 236 | |
| 237 demuxer_->Seek(base::TimeDelta::FromSeconds(0), | |
| 238 NewExpectedStatusCB(PIPELINE_OK)); | |
| 239 | |
| 240 EXPECT_CALL(*this, Checkpoint(2)); | |
| 241 | |
| 242 ClusterBuilder cb(0); | |
| 243 AddSimpleBlock(&cb, kVideoTrackNum, 0); | |
| 244 cb.Finish(); | |
| 245 | |
| 246 Checkpoint(1); | |
| 247 | |
| 248 EXPECT_TRUE(demuxer_->AddData(cb.GetClusterData(), cb.GetClusterDataSize())); | |
| 249 | |
| 250 Checkpoint(2); | |
| 251 } | |
| 252 | |
| 253 // Test the case where AddData() is called before Init(). This can happen | |
| 254 // when JavaScript starts sending data before the pipeline is completely | |
| 255 // initialized. | |
| 256 TEST_F(ChunkDemuxerTest, TestAddDataBeforeInit) { | |
| 257 ClusterBuilder cb(0); | |
| 258 AddSimpleBlock(&cb, kVideoTrackNum, 0); | |
| 259 cb.Finish(); | |
| 260 | |
| 261 EXPECT_TRUE(demuxer_->AddData(cb.GetClusterData(), cb.GetClusterDataSize())); | |
| 262 | |
| 263 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 264 | |
| 265 demuxer_->Seek(base::TimeDelta::FromSeconds(0), | |
| 266 NewExpectedStatusCB(PIPELINE_OK)); | |
| 267 } | |
| 268 | |
| 269 static void OnReadDone(const base::TimeDelta& expected_time, | |
| 270 bool* called, | |
| 271 Buffer* buffer) { | |
| 272 EXPECT_EQ(expected_time, buffer->GetTimestamp()); | |
| 273 *called = true; | |
| 274 } | |
| 275 | |
| 276 // Make sure Read() callbacks are dispatched with the proper data. | |
| 277 TEST_F(ChunkDemuxerTest, TestRead) { | |
| 278 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 279 | |
| 280 scoped_refptr<DemuxerStream> audio = | |
| 281 demuxer_->GetStream(DemuxerStream::AUDIO); | |
| 282 scoped_refptr<DemuxerStream> video = | |
| 283 demuxer_->GetStream(DemuxerStream::VIDEO); | |
| 284 | |
| 285 bool audio_read_done = false; | |
| 286 bool video_read_done = false; | |
| 287 audio->Read(base::Bind(&OnReadDone, | |
| 288 base::TimeDelta::FromMilliseconds(32), | |
| 289 &audio_read_done)); | |
| 290 | |
| 291 video->Read(base::Bind(&OnReadDone, | |
| 292 base::TimeDelta::FromMilliseconds(123), | |
| 293 &video_read_done)); | |
| 294 | |
| 295 ClusterBuilder cb(0); | |
| 296 AddSimpleBlock(&cb, kAudioTrackNum, 32); | |
| 297 AddSimpleBlock(&cb, kVideoTrackNum, 123); | |
| 298 cb.Finish(); | |
| 299 | |
| 300 EXPECT_TRUE(demuxer_->AddData(cb.GetClusterData(), cb.GetClusterDataSize())); | |
| 301 | |
| 302 EXPECT_TRUE(audio_read_done); | |
| 303 EXPECT_TRUE(video_read_done); | |
| 304 } | |
| 305 | |
| 306 TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { | |
| 307 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 308 | |
| 309 ClusterBuilder clusterA(10); | |
| 310 AddSimpleBlock(&clusterA, kAudioTrackNum, 10); | |
| 311 AddSimpleBlock(&clusterA, kVideoTrackNum, 10); | |
| 312 AddSimpleBlock(&clusterA, kAudioTrackNum, 33); | |
| 313 AddSimpleBlock(&clusterA, kVideoTrackNum, 43); | |
| 314 clusterA.Finish(); | |
| 315 | |
| 316 EXPECT_TRUE(demuxer_->AddData(clusterA.GetClusterData(), | |
| 317 clusterA.GetClusterDataSize())); | |
| 318 | |
| 319 // Cluster B starts before clusterA and has data | |
| 320 // that overlaps. | |
| 321 ClusterBuilder clusterB(5); | |
| 322 AddSimpleBlock(&clusterB, kAudioTrackNum, 5); | |
| 323 AddSimpleBlock(&clusterB, kVideoTrackNum, 7); | |
| 324 AddSimpleBlock(&clusterB, kAudioTrackNum, 28); | |
| 325 AddSimpleBlock(&clusterB, kVideoTrackNum, 40); | |
| 326 clusterB.Finish(); | |
| 327 | |
| 328 // Make sure that AddData() fails because this cluster data | |
| 329 // is before previous data. | |
| 330 EXPECT_FALSE(demuxer_->AddData(clusterB.GetClusterData(), | |
| 331 clusterB.GetClusterDataSize())); | |
| 332 | |
| 333 // Cluster C starts after clusterA. | |
| 334 ClusterBuilder clusterC(56); | |
| 335 AddSimpleBlock(&clusterC, kAudioTrackNum, 56); | |
| 336 AddSimpleBlock(&clusterC, kVideoTrackNum, 76); | |
| 337 AddSimpleBlock(&clusterC, kAudioTrackNum, 79); | |
| 338 AddSimpleBlock(&clusterC, kVideoTrackNum, 109); | |
| 339 clusterC.Finish(); | |
| 340 | |
| 341 // Verify that clusterC is accepted. | |
| 342 EXPECT_TRUE(demuxer_->AddData(clusterC.GetClusterData(), | |
| 343 clusterC.GetClusterDataSize())); | |
| 344 | |
| 345 // Flush and try clusterB again. | |
| 346 demuxer_->FlushData(); | |
| 347 EXPECT_TRUE(demuxer_->AddData(clusterB.GetClusterData(), | |
| 348 clusterB.GetClusterDataSize())); | |
| 349 | |
| 350 // Following that with clusterC should work too since it doesn't | |
| 351 // overlap with clusterB. | |
| 352 EXPECT_TRUE(demuxer_->AddData(clusterC.GetClusterData(), | |
| 353 clusterC.GetClusterDataSize())); | |
| 354 } | |
| 355 | |
| 356 TEST_F(ChunkDemuxerTest, TestInvalidBlockSequences) { | |
| 357 EXPECT_TRUE(InitDemuxer(true, true)); | |
| 358 | |
| 359 // Test the case where timecode is not monotonically | |
| 360 // increasing but stays above the cluster timecode. | |
| 361 ClusterBuilder clusterA(5); | |
| 362 AddSimpleBlock(&clusterA, kAudioTrackNum, 5); | |
| 363 AddSimpleBlock(&clusterA, kVideoTrackNum, 10); | |
| 364 AddSimpleBlock(&clusterA, kAudioTrackNum, 7); | |
| 365 AddSimpleBlock(&clusterA, kVideoTrackNum, 15); | |
| 366 clusterA.Finish(); | |
| 367 | |
| 368 EXPECT_FALSE(demuxer_->AddData(clusterA.GetClusterData(), | |
| 369 clusterA.GetClusterDataSize())); | |
| 370 | |
| 371 // Test timecodes going backwards before cluster timecode. | |
| 372 ClusterBuilder clusterB(5); | |
| 373 AddSimpleBlock(&clusterB, kAudioTrackNum, 5); | |
| 374 AddSimpleBlock(&clusterB, kVideoTrackNum, 5); | |
| 375 AddSimpleBlock(&clusterB, kAudioTrackNum, 3); | |
| 376 AddSimpleBlock(&clusterB, kVideoTrackNum, 3); | |
| 377 clusterB.Finish(); | |
| 378 | |
| 379 EXPECT_FALSE(demuxer_->AddData(clusterB.GetClusterData(), | |
| 380 clusterB.GetClusterDataSize())); | |
| 381 | |
| 382 // Test strict monotonic increasing timestamps on a per stream | |
| 383 // basis. | |
| 384 ClusterBuilder clusterC(5); | |
| 385 AddSimpleBlock(&clusterC, kAudioTrackNum, 5); | |
| 386 AddSimpleBlock(&clusterC, kVideoTrackNum, 5); | |
| 387 AddSimpleBlock(&clusterC, kAudioTrackNum, 5); | |
| 388 AddSimpleBlock(&clusterC, kVideoTrackNum, 7); | |
| 389 clusterC.Finish(); | |
| 390 | |
| 391 EXPECT_FALSE(demuxer_->AddData(clusterC.GetClusterData(), | |
| 392 clusterC.GetClusterDataSize())); | |
| 393 | |
| 394 // Test strict monotonic increasing timestamps on a per stream | |
| 395 // basis across clusters. | |
| 396 ClusterBuilder clusterD(5); | |
| 397 AddSimpleBlock(&clusterD, kAudioTrackNum, 5); | |
| 398 AddSimpleBlock(&clusterD, kVideoTrackNum, 5); | |
| 399 clusterD.Finish(); | |
| 400 | |
| 401 EXPECT_TRUE(demuxer_->AddData(clusterD.GetClusterData(), | |
| 402 clusterD.GetClusterDataSize())); | |
| 403 | |
| 404 ClusterBuilder clusterE(5); | |
| 405 AddSimpleBlock(&clusterE, kAudioTrackNum, 5); | |
| 406 AddSimpleBlock(&clusterE, kVideoTrackNum, 7); | |
| 407 clusterE.Finish(); | |
| 408 | |
| 409 EXPECT_FALSE(demuxer_->AddData(clusterE.GetClusterData(), | |
| 410 clusterE.GetClusterDataSize())); | |
| 411 } | |
| 412 | |
| 413 } // namespace media | |
| OLD | NEW |