blob: 98813e774e4f53dfb55272f17dead2e43cecd53e [file] [log] [blame]
Linus Nilssona85df7f2020-02-20 16:32:04 -08001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// Unit Test for MediaSampleWriter
18
19// #define LOG_NDEBUG 0
20#define LOG_TAG "MediaSampleWriterTests"
21
22#include <android-base/logging.h>
23#include <fcntl.h>
24#include <gtest/gtest.h>
25#include <media/MediaSampleQueue.h>
26#include <media/MediaSampleWriter.h>
27#include <media/NdkMediaExtractor.h>
28
29#include <condition_variable>
30#include <list>
31#include <mutex>
32
33namespace android {
34
35/** Minimal one-shot semaphore */
36class SimpleSemaphore {
37public:
38 void signal() {
39 std::unique_lock<std::mutex> lock(mMutex);
40 mSignaled = true;
41 mCondition.notify_all();
42 }
43
44 void wait() {
45 std::unique_lock<std::mutex> lock(mMutex);
46 while (!mSignaled) {
47 mCondition.wait(lock);
48 }
49 }
50
51private:
52 std::mutex mMutex;
53 std::condition_variable mCondition;
54 bool mSignaled = false;
55};
56
57/** Muxer interface to enable MediaSampleWriter testing. */
58class TestMuxer : public MediaSampleWriterMuxerInterface {
59public:
60 // MuxerInterface
Chong Zhangd6e4aec2020-06-22 14:13:07 -070061 ssize_t addTrack(AMediaFormat* trackFormat) override {
Linus Nilssona85df7f2020-02-20 16:32:04 -080062 mEventQueue.push_back(AddTrack(trackFormat));
63 return mTrackCount++;
64 }
65 media_status_t start() override {
66 mEventQueue.push_back(Start());
67 return AMEDIA_OK;
68 }
69
70 media_status_t writeSampleData(size_t trackIndex, const uint8_t* data,
71 const AMediaCodecBufferInfo* info) override {
72 mEventQueue.push_back(WriteSample(trackIndex, data, info));
73 return AMEDIA_OK;
74 }
75 media_status_t stop() override {
76 mEventQueue.push_back(Stop());
77 return AMEDIA_OK;
78 }
79 // ~MuxerInterface
80
81 struct Event {
82 enum { NoEvent, AddTrack, Start, WriteSample, Stop } type = NoEvent;
83 const AMediaFormat* format = nullptr;
84 size_t trackIndex = 0;
85 const uint8_t* data = nullptr;
86 AMediaCodecBufferInfo info{};
87 };
88
89 static constexpr Event NoEvent = {Event::NoEvent, nullptr, 0, nullptr, {}};
90
91 static Event AddTrack(const AMediaFormat* format) {
92 return {.type = Event::AddTrack, .format = format};
93 }
94
95 static Event Start() { return {.type = Event::Start}; }
96 static Event Stop() { return {.type = Event::Stop}; }
97
98 static Event WriteSample(size_t trackIndex, const uint8_t* data,
99 const AMediaCodecBufferInfo* info) {
100 return {.type = Event::WriteSample, .trackIndex = trackIndex, .data = data, .info = *info};
101 }
102
103 const Event& popEvent() {
104 if (mEventQueue.empty()) {
105 mPoppedEvent = NoEvent;
106 } else {
107 mPoppedEvent = *mEventQueue.begin();
108 mEventQueue.pop_front();
109 }
110 return mPoppedEvent;
111 }
112
113private:
114 Event mPoppedEvent;
115 std::list<Event> mEventQueue;
116 ssize_t mTrackCount = 0;
117};
118
119bool operator==(const AMediaCodecBufferInfo& lhs, const AMediaCodecBufferInfo& rhs) {
120 return lhs.offset == rhs.offset && lhs.size == rhs.size &&
121 lhs.presentationTimeUs == rhs.presentationTimeUs && lhs.flags == rhs.flags;
122}
123
124bool operator==(const TestMuxer::Event& lhs, const TestMuxer::Event& rhs) {
125 return lhs.type == rhs.type && lhs.format == rhs.format && lhs.trackIndex == rhs.trackIndex &&
126 lhs.data == rhs.data && lhs.info == rhs.info;
127}
128
129/** Represents a media source file. */
130class TestMediaSource {
131public:
132 void init() {
133 static const char* sourcePath =
hkuang2ef2b432020-06-15 18:33:11 -0700134 "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
Linus Nilssona85df7f2020-02-20 16:32:04 -0800135
136 mExtractor = AMediaExtractor_new();
137 ASSERT_NE(mExtractor, nullptr);
138
139 int sourceFd = open(sourcePath, O_RDONLY);
140 ASSERT_GT(sourceFd, 0);
141
142 off_t fileSize = lseek(sourceFd, 0, SEEK_END);
143 lseek(sourceFd, 0, SEEK_SET);
144
145 media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, sourceFd, 0, fileSize);
146 ASSERT_EQ(status, AMEDIA_OK);
147 close(sourceFd);
148
149 mTrackCount = AMediaExtractor_getTrackCount(mExtractor);
150 ASSERT_GT(mTrackCount, 1);
151 for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
152 AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
153 ASSERT_NE(trackFormat, nullptr);
154 mTrackFormats.push_back(
155 std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete));
156
157 AMediaExtractor_selectTrack(mExtractor, trackIndex);
158 }
159 }
160
161 void reset() const {
162 media_status_t status = AMediaExtractor_seekTo(mExtractor, 0 /* seekPosUs */,
163 AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
164 ASSERT_EQ(status, AMEDIA_OK);
165 }
166
167 AMediaExtractor* mExtractor = nullptr;
168 size_t mTrackCount = 0;
169 std::vector<std::shared_ptr<AMediaFormat>> mTrackFormats;
170};
171
172class MediaSampleWriterTests : public ::testing::Test {
173public:
174 MediaSampleWriterTests() { LOG(DEBUG) << "MediaSampleWriterTests created"; }
175 ~MediaSampleWriterTests() { LOG(DEBUG) << "MediaSampleWriterTests destroyed"; }
176
177 static const TestMediaSource& getMediaSource() {
178 static TestMediaSource sMediaSource;
179 static std::once_flag sOnceToken;
180
181 std::call_once(sOnceToken, [] { sMediaSource.init(); });
182
183 sMediaSource.reset();
184 return sMediaSource;
185 }
186
187 static std::shared_ptr<MediaSample> newSample(int64_t ptsUs, uint32_t flags, size_t size,
188 size_t offset, const uint8_t* buffer) {
189 auto sample = std::make_shared<MediaSample>();
190 sample->info.presentationTimeUs = ptsUs;
191 sample->info.flags = flags;
192 sample->info.size = size;
193 sample->dataOffset = offset;
194 sample->buffer = buffer;
195 return sample;
196 }
197
198 static std::shared_ptr<MediaSample> newSampleEos() {
199 return newSample(0, SAMPLE_FLAG_END_OF_STREAM, 0, 0, nullptr);
200 }
201
202 static std::shared_ptr<MediaSample> newSampleWithPts(int64_t ptsUs) {
203 static uint32_t sampleCount = 0;
204
205 // Use sampleCount to get a unique dummy sample.
206 uint32_t sampleId = ++sampleCount;
207 return newSample(ptsUs, 0, sampleId, sampleId, reinterpret_cast<const uint8_t*>(sampleId));
208 }
209
210 void SetUp() override {
211 LOG(DEBUG) << "MediaSampleWriterTests set up";
212 mTestMuxer = std::make_shared<TestMuxer>();
213 mSampleQueue = std::make_shared<MediaSampleQueue>();
214 }
215
216 void TearDown() override {
217 LOG(DEBUG) << "MediaSampleWriterTests tear down";
218 mTestMuxer.reset();
219 mSampleQueue.reset();
220 }
221
222protected:
223 std::shared_ptr<TestMuxer> mTestMuxer;
224 std::shared_ptr<MediaSampleQueue> mSampleQueue;
225 const MediaSampleWriter::OnWritingFinishedCallback mEmptyCallback = [](media_status_t) {};
226};
227
228TEST_F(MediaSampleWriterTests, TestAddTrackWithoutInit) {
229 const TestMediaSource& mediaSource = getMediaSource();
230
231 MediaSampleWriter writer{};
232 EXPECT_FALSE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
233}
234
235TEST_F(MediaSampleWriterTests, TestStartWithoutInit) {
236 MediaSampleWriter writer{};
237 EXPECT_FALSE(writer.start());
238}
239
240TEST_F(MediaSampleWriterTests, TestStartWithoutTracks) {
241 MediaSampleWriter writer{};
242 EXPECT_TRUE(writer.init(mTestMuxer, mEmptyCallback));
243 EXPECT_FALSE(writer.start());
244 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
245}
246
247TEST_F(MediaSampleWriterTests, TestAddInvalidTrack) {
248 MediaSampleWriter writer{};
249 EXPECT_TRUE(writer.init(mTestMuxer, mEmptyCallback));
250
251 EXPECT_FALSE(writer.addTrack(mSampleQueue, nullptr));
252 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
253
254 const TestMediaSource& mediaSource = getMediaSource();
255 EXPECT_FALSE(writer.addTrack(nullptr, mediaSource.mTrackFormats[0]));
256 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
257}
258
259TEST_F(MediaSampleWriterTests, TestDoubleStartStop) {
260 MediaSampleWriter writer{};
261
262 bool callbackFired = false;
263 MediaSampleWriter::OnWritingFinishedCallback stoppedCallback =
264 [&callbackFired](media_status_t status) {
265 EXPECT_NE(status, AMEDIA_OK);
266 EXPECT_FALSE(callbackFired);
267 callbackFired = true;
268 };
269
270 EXPECT_TRUE(writer.init(mTestMuxer, stoppedCallback));
271
272 const TestMediaSource& mediaSource = getMediaSource();
273 EXPECT_TRUE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
274 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
275
276 EXPECT_TRUE(writer.start());
277 EXPECT_FALSE(writer.start());
278
279 EXPECT_TRUE(writer.stop());
280 EXPECT_TRUE(callbackFired);
281 EXPECT_FALSE(writer.stop());
282}
283
284TEST_F(MediaSampleWriterTests, TestStopWithoutStart) {
285 MediaSampleWriter writer{};
286 EXPECT_TRUE(writer.init(mTestMuxer, mEmptyCallback));
287
288 const TestMediaSource& mediaSource = getMediaSource();
289 EXPECT_TRUE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
290 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
291
292 EXPECT_FALSE(writer.stop());
293 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
294}
295
296TEST_F(MediaSampleWriterTests, TestStartWithoutCallback) {
297 MediaSampleWriter writer{};
298 EXPECT_FALSE(writer.init(mTestMuxer, nullptr));
299
300 const TestMediaSource& mediaSource = getMediaSource();
301 EXPECT_FALSE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
302 ASSERT_FALSE(writer.start());
303}
304
305TEST_F(MediaSampleWriterTests, TestInterleaving) {
306 static constexpr uint32_t kSegmentLength = MediaSampleWriter::kDefaultTrackSegmentLengthUs;
307 SimpleSemaphore semaphore;
308
309 MediaSampleWriter writer{kSegmentLength};
310 EXPECT_TRUE(writer.init(mTestMuxer, [&semaphore](media_status_t status) {
311 EXPECT_EQ(status, AMEDIA_OK);
312 semaphore.signal();
313 }));
314
315 // Use two tracks for this test.
316 static constexpr int kNumTracks = 2;
317 std::shared_ptr<MediaSampleQueue> sampleQueues[kNumTracks];
318 std::vector<std::pair<std::shared_ptr<MediaSample>, size_t>> interleavedSamples;
319 const TestMediaSource& mediaSource = getMediaSource();
320
321 for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
322 sampleQueues[trackIdx] = std::make_shared<MediaSampleQueue>();
323
324 auto trackFormat = mediaSource.mTrackFormats[trackIdx % mediaSource.mTrackCount];
325 EXPECT_TRUE(writer.addTrack(sampleQueues[trackIdx], trackFormat));
326 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(trackFormat.get()));
327 }
328
329 // Create samples in the expected interleaved order for easy verification.
330 auto addSampleToTrackWithPts = [&interleavedSamples, &sampleQueues](int trackIndex,
331 int64_t pts) {
332 auto sample = newSampleWithPts(pts);
333 sampleQueues[trackIndex]->enqueue(sample);
334 interleavedSamples.emplace_back(sample, trackIndex);
335 };
336
337 addSampleToTrackWithPts(0, 0);
338 addSampleToTrackWithPts(0, kSegmentLength / 2);
339 addSampleToTrackWithPts(0, kSegmentLength); // Track 0 reached 1st segment end
340
341 addSampleToTrackWithPts(1, 0);
342 addSampleToTrackWithPts(1, kSegmentLength); // Track 1 reached 1st segment end
343
344 addSampleToTrackWithPts(0, kSegmentLength * 2); // Track 0 reached 2nd segment end
345
346 addSampleToTrackWithPts(1, kSegmentLength + 1);
347 addSampleToTrackWithPts(1, kSegmentLength * 2); // Track 1 reached 2nd segment end
348
349 addSampleToTrackWithPts(0, kSegmentLength * 2 + 1);
350
351 for (int trackIndex = 0; trackIndex < kNumTracks; ++trackIndex) {
352 sampleQueues[trackIndex]->enqueue(newSampleEos());
353 }
354
355 // Start the writer.
356 ASSERT_TRUE(writer.start());
357
358 // Wait for writer to complete.
359 semaphore.wait();
360 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Start());
361
362 // Verify sample order.
363 for (auto entry : interleavedSamples) {
364 auto sample = entry.first;
365 auto trackIndex = entry.second;
366
367 const TestMuxer::Event& event = mTestMuxer->popEvent();
368 EXPECT_EQ(event.type, TestMuxer::Event::WriteSample);
369 EXPECT_EQ(event.trackIndex, trackIndex);
370 EXPECT_EQ(event.data, sample->buffer);
371 EXPECT_EQ(event.info.offset, sample->dataOffset);
372 EXPECT_EQ(event.info.size, sample->info.size);
373 EXPECT_EQ(event.info.presentationTimeUs, sample->info.presentationTimeUs);
374 EXPECT_EQ(event.info.flags, sample->info.flags);
375 }
376
377 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
378 EXPECT_TRUE(writer.stop());
379}
380
381TEST_F(MediaSampleWriterTests, TestAbortInputQueue) {
382 SimpleSemaphore semaphore;
383
384 MediaSampleWriter writer{};
385 EXPECT_TRUE(writer.init(mTestMuxer, [&semaphore](media_status_t status) {
386 EXPECT_NE(status, AMEDIA_OK);
387 semaphore.signal();
388 }));
389
390 // Use two tracks for this test.
391 static constexpr int kNumTracks = 2;
392 std::shared_ptr<MediaSampleQueue> sampleQueues[kNumTracks];
393 const TestMediaSource& mediaSource = getMediaSource();
394
395 for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
396 sampleQueues[trackIdx] = std::make_shared<MediaSampleQueue>();
397
398 auto trackFormat = mediaSource.mTrackFormats[trackIdx % mediaSource.mTrackCount];
399 EXPECT_TRUE(writer.addTrack(sampleQueues[trackIdx], trackFormat));
400 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(trackFormat.get()));
401 }
402
403 // Start the writer.
404 ASSERT_TRUE(writer.start());
405
406 // Abort the input queues and wait for the writer to complete.
407 for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
408 sampleQueues[trackIdx]->abort();
409 }
410 semaphore.wait();
411
412 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Start());
413 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
414 EXPECT_TRUE(writer.stop());
415}
416
417// Convenience function for reading a sample from an AMediaExtractor represented as a MediaSample.
418static std::shared_ptr<MediaSample> readSampleAndAdvance(AMediaExtractor* extractor,
419 size_t* trackIndexOut) {
420 int trackIndex = AMediaExtractor_getSampleTrackIndex(extractor);
421 if (trackIndex < 0) {
422 return nullptr;
423 }
424
425 if (trackIndexOut != nullptr) {
426 *trackIndexOut = trackIndex;
427 }
428
429 ssize_t sampleSize = AMediaExtractor_getSampleSize(extractor);
430 int64_t sampleTimeUs = AMediaExtractor_getSampleTime(extractor);
431 uint32_t flags = AMediaExtractor_getSampleFlags(extractor);
432
433 size_t bufferSize = static_cast<size_t>(sampleSize);
434 uint8_t* buffer = new uint8_t[bufferSize];
435
436 ssize_t dataRead = AMediaExtractor_readSampleData(extractor, buffer, bufferSize);
437 EXPECT_EQ(dataRead, sampleSize);
438
439 auto sample = MediaSample::createWithReleaseCallback(
440 buffer, 0 /* offset */, 0 /* id */, [buffer](MediaSample*) { delete[] buffer; });
441 sample->info.size = bufferSize;
442 sample->info.presentationTimeUs = sampleTimeUs;
443 sample->info.flags = flags;
444
445 (void)AMediaExtractor_advance(extractor);
446 return sample;
447}
448
449TEST_F(MediaSampleWriterTests, TestDefaultMuxer) {
450 // Write samples straight from an extractor and validate output file.
451 static const char* destinationPath =
452 "/data/local/tmp/MediaSampleWriterTests_TestDefaultMuxer_output.MP4";
453 const int destinationFd =
454 open(destinationPath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH);
455 ASSERT_GT(destinationFd, 0);
456
457 // Initialize writer.
458 SimpleSemaphore semaphore;
459 MediaSampleWriter writer{};
460 EXPECT_TRUE(writer.init(destinationFd, [&semaphore](media_status_t status) {
461 EXPECT_EQ(status, AMEDIA_OK);
462 semaphore.signal();
463 }));
464 close(destinationFd);
465
466 // Add tracks.
467 const TestMediaSource& mediaSource = getMediaSource();
468 std::vector<std::shared_ptr<MediaSampleQueue>> inputQueues;
469
470 for (size_t trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
471 inputQueues.push_back(std::make_shared<MediaSampleQueue>());
472 EXPECT_TRUE(
473 writer.addTrack(inputQueues[trackIndex], mediaSource.mTrackFormats[trackIndex]));
474 }
475
476 // Start the writer.
477 ASSERT_TRUE(writer.start());
478
479 // Enqueue samples and finally End Of Stream.
480 std::shared_ptr<MediaSample> sample;
481 size_t trackIndex;
482 while ((sample = readSampleAndAdvance(mediaSource.mExtractor, &trackIndex)) != nullptr) {
483 inputQueues[trackIndex]->enqueue(sample);
484 }
485 for (trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
486 inputQueues[trackIndex]->enqueue(newSampleEos());
487 }
488
489 // Wait for writer.
490 semaphore.wait();
491 EXPECT_TRUE(writer.stop());
492
493 // Compare output file with source.
494 mediaSource.reset();
495
496 AMediaExtractor* extractor = AMediaExtractor_new();
497 ASSERT_NE(extractor, nullptr);
498
499 int sourceFd = open(destinationPath, O_RDONLY);
500 ASSERT_GT(sourceFd, 0);
501
502 off_t fileSize = lseek(sourceFd, 0, SEEK_END);
503 lseek(sourceFd, 0, SEEK_SET);
504
505 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, sourceFd, 0, fileSize);
506 ASSERT_EQ(status, AMEDIA_OK);
507 close(sourceFd);
508
509 size_t trackCount = AMediaExtractor_getTrackCount(extractor);
510 EXPECT_EQ(trackCount, mediaSource.mTrackCount);
511
512 for (size_t trackIndex = 0; trackIndex < trackCount; trackIndex++) {
513 AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(extractor, trackIndex);
514 ASSERT_NE(trackFormat, nullptr);
515
516 AMediaExtractor_selectTrack(extractor, trackIndex);
517 }
518
519 // Compare samples.
520 std::shared_ptr<MediaSample> sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
521 std::shared_ptr<MediaSample> sample2 = readSampleAndAdvance(extractor, nullptr);
522
523 while (sample1 != nullptr && sample2 != nullptr) {
524 EXPECT_EQ(sample1->info.presentationTimeUs, sample2->info.presentationTimeUs);
525 EXPECT_EQ(sample1->info.size, sample2->info.size);
526 EXPECT_EQ(sample1->info.flags, sample2->info.flags);
527
528 EXPECT_EQ(memcmp(sample1->buffer, sample2->buffer, sample1->info.size), 0);
529
530 sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
531 sample2 = readSampleAndAdvance(extractor, nullptr);
532 }
533 EXPECT_EQ(sample1, nullptr);
534 EXPECT_EQ(sample2, nullptr);
535
536 AMediaExtractor_delete(extractor);
537}
538
539} // namespace android
540
541int main(int argc, char** argv) {
542 ::testing::InitGoogleTest(&argc, argv);
543 return RUN_ALL_TESTS();
544}