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 |