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

Side by Side 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, 5 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « media/filters/chunk_demuxer_factory.cc ('k') | media/filters/ffmpeg_demuxer.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 virtual ~ChunkDemuxerTest() {
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 .AppendASCII(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) {
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 void InitDemuxer(bool has_audio, bool has_video) {
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));
173
174 scoped_array<uint8> info_tracks;
175 int info_tracks_size = 0;
176 CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size);
177
178 SetupAVFormatContext(has_audio, has_video);
179
180 EXPECT_EQ(demuxer_->Init(info_tracks.get(), info_tracks_size),
181 has_audio || has_video);
182 }
183
184 void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) {
185 uint8 data[] = { 0x00 };
186 cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data));
187 }
188
189 MOCK_METHOD1(Checkpoint, void(int id));
190
191 MockFFmpeg mock_ffmpeg_;
192
193 AVFormatContext format_context_;
194 AVCodecContext codecs_[MAX_CODECS_INDEX];
195 AVStream streams_[MAX_CODECS_INDEX];
196
197 scoped_refptr<ChunkDemuxer> demuxer_;
198
199 private:
200 DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest);
201 };
202
203 TEST_F(ChunkDemuxerTest, TestInit) {
204 // Test no streams, audio-only, video-only, and audio & video scenarios.
205 for (int i = 0; i < 4; i++) {
206 bool has_audio = (i & 0x1) != 0;
207 bool has_video = (i & 0x2) != 0;
208
209 demuxer_ = new ChunkDemuxer();
210 InitDemuxer(has_audio, has_video);
211 EXPECT_EQ(demuxer_->GetStream(DemuxerStream::AUDIO).get() != NULL,
212 has_audio);
213 EXPECT_EQ(demuxer_->GetStream(DemuxerStream::VIDEO).get() != NULL,
214 has_video);
215 demuxer_->Shutdown();
216 demuxer_ = NULL;
217 }
218 }
219
220 // Makes sure that Seek() reports an error if Shutdown()
221 // is called before the first cluster is passed to the demuxer.
222 TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) {
223 InitDemuxer(true, true);
224
225 demuxer_->Seek(base::TimeDelta::FromSeconds(0),
226 NewExpectedStatusCB(PIPELINE_ERROR_ABORT));
227 }
228
229 // Test that Seek() completes successfully when the first cluster
230 // arrives.
231 TEST_F(ChunkDemuxerTest, TestAddDataAfterSeek) {
232 InitDemuxer(true, true);
233
234 InSequence s;
235
236 EXPECT_CALL(*this, Checkpoint(1));
237
238 demuxer_->Seek(base::TimeDelta::FromSeconds(0),
239 NewExpectedStatusCB(PIPELINE_OK));
240
241 EXPECT_CALL(*this, Checkpoint(2));
242
243 ClusterBuilder cb;
244 cb.SetClusterTimecode(0);
245 AddSimpleBlock(&cb, kVideoTrackNum, 0);
246 scoped_ptr<Cluster> cluster(cb.Finish());
247
248 Checkpoint(1);
249
250 EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
251
252 Checkpoint(2);
253 }
254
255 // Test the case where AddData() is called before Init(). This can happen
256 // when JavaScript starts sending data before the pipeline is completely
257 // initialized.
258 TEST_F(ChunkDemuxerTest, TestAddDataBeforeInit) {
259 ClusterBuilder cb;
260 cb.SetClusterTimecode(0);
261 AddSimpleBlock(&cb, kVideoTrackNum, 0);
262 scoped_ptr<Cluster> cluster(cb.Finish());
263
264 EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
265
266 InitDemuxer(true, true);
267
268 demuxer_->Seek(base::TimeDelta::FromSeconds(0),
269 NewExpectedStatusCB(PIPELINE_OK));
270 }
271
272 static void OnReadDone(const base::TimeDelta& expected_time,
273 bool* called,
274 Buffer* buffer) {
275 EXPECT_EQ(expected_time, buffer->GetTimestamp());
276 *called = true;
277 }
278
279 // Make sure Read() callbacks are dispatched with the proper data.
280 TEST_F(ChunkDemuxerTest, TestRead) {
281 InitDemuxer(true, true);
282
283 scoped_refptr<DemuxerStream> audio =
284 demuxer_->GetStream(DemuxerStream::AUDIO);
285 scoped_refptr<DemuxerStream> video =
286 demuxer_->GetStream(DemuxerStream::VIDEO);
287
288 bool audio_read_done = false;
289 bool video_read_done = false;
290 audio->Read(base::Bind(&OnReadDone,
291 base::TimeDelta::FromMilliseconds(32),
292 &audio_read_done));
293
294 video->Read(base::Bind(&OnReadDone,
295 base::TimeDelta::FromMilliseconds(123),
296 &video_read_done));
297
298 ClusterBuilder cb;
299 cb.SetClusterTimecode(0);
300 AddSimpleBlock(&cb, kAudioTrackNum, 32);
301 AddSimpleBlock(&cb, kVideoTrackNum, 123);
302 scoped_ptr<Cluster> cluster(cb.Finish());
303
304 EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
305
306 EXPECT_TRUE(audio_read_done);
307 EXPECT_TRUE(video_read_done);
308 }
309
310 TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
311 InitDemuxer(true, true);
312
313 ClusterBuilder cb;
314
315 cb.SetClusterTimecode(10);
316 AddSimpleBlock(&cb, kAudioTrackNum, 10);
317 AddSimpleBlock(&cb, kVideoTrackNum, 10);
318 AddSimpleBlock(&cb, kAudioTrackNum, 33);
319 AddSimpleBlock(&cb, kVideoTrackNum, 43);
320 scoped_ptr<Cluster> clusterA(cb.Finish());
321
322 EXPECT_TRUE(demuxer_->AddData(clusterA->data(), clusterA->size()));
323
324 // Cluster B starts before clusterA and has data
325 // that overlaps.
326 cb.SetClusterTimecode(5);
327 AddSimpleBlock(&cb, kAudioTrackNum, 5);
328 AddSimpleBlock(&cb, kVideoTrackNum, 7);
329 AddSimpleBlock(&cb, kAudioTrackNum, 28);
330 AddSimpleBlock(&cb, kVideoTrackNum, 40);
331 scoped_ptr<Cluster> clusterB(cb.Finish());
332
333 // Make sure that AddData() fails because this cluster data
334 // is before previous data.
335 EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size()));
336
337 // Cluster C starts after clusterA.
338 cb.SetClusterTimecode(56);
339 AddSimpleBlock(&cb, kAudioTrackNum, 56);
340 AddSimpleBlock(&cb, kVideoTrackNum, 76);
341 AddSimpleBlock(&cb, kAudioTrackNum, 79);
342 AddSimpleBlock(&cb, kVideoTrackNum, 109);
343 scoped_ptr<Cluster> clusterC(cb.Finish());
344
345 // Verify that clusterC is accepted.
346 EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size()));
347
348 // Flush and try clusterB again.
349 demuxer_->FlushData();
350 EXPECT_TRUE(demuxer_->AddData(clusterB->data(), clusterB->size()));
351
352 // Following that with clusterC should work too since it doesn't
353 // overlap with clusterB.
354 EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size()));
355 }
356
357 TEST_F(ChunkDemuxerTest, TestInvalidBlockSequences) {
358 InitDemuxer(true, true);
359
360 ClusterBuilder cb;
361
362 // Test the case where timecode is not monotonically
363 // increasing but stays above the cluster timecode.
364 cb.SetClusterTimecode(5);
365 AddSimpleBlock(&cb, kAudioTrackNum, 5);
366 AddSimpleBlock(&cb, kVideoTrackNum, 10);
367 AddSimpleBlock(&cb, kAudioTrackNum, 7);
368 AddSimpleBlock(&cb, kVideoTrackNum, 15);
369 scoped_ptr<Cluster> clusterA(cb.Finish());
370
371 EXPECT_FALSE(demuxer_->AddData(clusterA->data(), clusterA->size()));
372
373 // Test timecodes going backwards before cluster timecode.
374 cb.SetClusterTimecode(5);
375 AddSimpleBlock(&cb, kAudioTrackNum, 5);
376 AddSimpleBlock(&cb, kVideoTrackNum, 5);
377 AddSimpleBlock(&cb, kAudioTrackNum, 3);
378 AddSimpleBlock(&cb, kVideoTrackNum, 3);
379 scoped_ptr<Cluster> clusterB(cb.Finish());
380
381 EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size()));
382
383 // Test strict monotonic increasing timestamps on a per stream
384 // basis.
385 cb.SetClusterTimecode(5);
386 AddSimpleBlock(&cb, kAudioTrackNum, 5);
387 AddSimpleBlock(&cb, kVideoTrackNum, 5);
388 AddSimpleBlock(&cb, kAudioTrackNum, 5);
389 AddSimpleBlock(&cb, kVideoTrackNum, 7);
390 scoped_ptr<Cluster> clusterC(cb.Finish());
391
392 EXPECT_FALSE(demuxer_->AddData(clusterC->data(), clusterC->size()));
393
394 // Test strict monotonic increasing timestamps on a per stream
395 // basis across clusters.
396 cb.SetClusterTimecode(5);
397 AddSimpleBlock(&cb, kAudioTrackNum, 5);
398 AddSimpleBlock(&cb, kVideoTrackNum, 5);
399 scoped_ptr<Cluster> clusterD(cb.Finish());
400
401 EXPECT_TRUE(demuxer_->AddData(clusterD->data(), clusterD->size()));
402
403 cb.SetClusterTimecode(5);
404 AddSimpleBlock(&cb, kAudioTrackNum, 5);
405 AddSimpleBlock(&cb, kVideoTrackNum, 7);
406 scoped_ptr<Cluster> clusterE(cb.Finish());
407
408 EXPECT_FALSE(demuxer_->AddData(clusterE->data(), clusterE->size()));
409 }
410
411 } // namespace media
OLDNEW
« 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