blob: e3cb192cb344e506d47798a0979e8aa49a367c21 [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
Linus Nilsson42a971b2020-07-01 16:41:11 -0700377 // Verify EOS samples.
378 for (int trackIndex = 0; trackIndex < kNumTracks; ++trackIndex) {
379 auto trackFormat = mediaSource.mTrackFormats[trackIndex % mediaSource.mTrackCount];
380 int64_t duration = 0;
381 AMediaFormat_getInt64(trackFormat.get(), AMEDIAFORMAT_KEY_DURATION, &duration);
382
383 const AMediaCodecBufferInfo info = {0, 0, duration, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM};
384 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::WriteSample(trackIndex, nullptr, &info));
385 }
386
Linus Nilssona85df7f2020-02-20 16:32:04 -0800387 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
388 EXPECT_TRUE(writer.stop());
389}
390
391TEST_F(MediaSampleWriterTests, TestAbortInputQueue) {
392 SimpleSemaphore semaphore;
393
394 MediaSampleWriter writer{};
395 EXPECT_TRUE(writer.init(mTestMuxer, [&semaphore](media_status_t status) {
396 EXPECT_NE(status, AMEDIA_OK);
397 semaphore.signal();
398 }));
399
400 // Use two tracks for this test.
401 static constexpr int kNumTracks = 2;
402 std::shared_ptr<MediaSampleQueue> sampleQueues[kNumTracks];
403 const TestMediaSource& mediaSource = getMediaSource();
404
405 for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
406 sampleQueues[trackIdx] = std::make_shared<MediaSampleQueue>();
407
408 auto trackFormat = mediaSource.mTrackFormats[trackIdx % mediaSource.mTrackCount];
409 EXPECT_TRUE(writer.addTrack(sampleQueues[trackIdx], trackFormat));
410 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(trackFormat.get()));
411 }
412
413 // Start the writer.
414 ASSERT_TRUE(writer.start());
415
416 // Abort the input queues and wait for the writer to complete.
417 for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
418 sampleQueues[trackIdx]->abort();
419 }
420 semaphore.wait();
421
422 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Start());
423 EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
424 EXPECT_TRUE(writer.stop());
425}
426
427// Convenience function for reading a sample from an AMediaExtractor represented as a MediaSample.
428static std::shared_ptr<MediaSample> readSampleAndAdvance(AMediaExtractor* extractor,
429 size_t* trackIndexOut) {
430 int trackIndex = AMediaExtractor_getSampleTrackIndex(extractor);
431 if (trackIndex < 0) {
432 return nullptr;
433 }
434
435 if (trackIndexOut != nullptr) {
436 *trackIndexOut = trackIndex;
437 }
438
439 ssize_t sampleSize = AMediaExtractor_getSampleSize(extractor);
440 int64_t sampleTimeUs = AMediaExtractor_getSampleTime(extractor);
441 uint32_t flags = AMediaExtractor_getSampleFlags(extractor);
442
443 size_t bufferSize = static_cast<size_t>(sampleSize);
444 uint8_t* buffer = new uint8_t[bufferSize];
445
446 ssize_t dataRead = AMediaExtractor_readSampleData(extractor, buffer, bufferSize);
447 EXPECT_EQ(dataRead, sampleSize);
448
449 auto sample = MediaSample::createWithReleaseCallback(
450 buffer, 0 /* offset */, 0 /* id */, [buffer](MediaSample*) { delete[] buffer; });
451 sample->info.size = bufferSize;
452 sample->info.presentationTimeUs = sampleTimeUs;
453 sample->info.flags = flags;
454
455 (void)AMediaExtractor_advance(extractor);
456 return sample;
457}
458
459TEST_F(MediaSampleWriterTests, TestDefaultMuxer) {
460 // Write samples straight from an extractor and validate output file.
461 static const char* destinationPath =
462 "/data/local/tmp/MediaSampleWriterTests_TestDefaultMuxer_output.MP4";
463 const int destinationFd =
464 open(destinationPath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH);
465 ASSERT_GT(destinationFd, 0);
466
467 // Initialize writer.
468 SimpleSemaphore semaphore;
469 MediaSampleWriter writer{};
470 EXPECT_TRUE(writer.init(destinationFd, [&semaphore](media_status_t status) {
471 EXPECT_EQ(status, AMEDIA_OK);
472 semaphore.signal();
473 }));
474 close(destinationFd);
475
476 // Add tracks.
477 const TestMediaSource& mediaSource = getMediaSource();
478 std::vector<std::shared_ptr<MediaSampleQueue>> inputQueues;
479
480 for (size_t trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
481 inputQueues.push_back(std::make_shared<MediaSampleQueue>());
482 EXPECT_TRUE(
483 writer.addTrack(inputQueues[trackIndex], mediaSource.mTrackFormats[trackIndex]));
484 }
485
486 // Start the writer.
487 ASSERT_TRUE(writer.start());
488
489 // Enqueue samples and finally End Of Stream.
490 std::shared_ptr<MediaSample> sample;
491 size_t trackIndex;
492 while ((sample = readSampleAndAdvance(mediaSource.mExtractor, &trackIndex)) != nullptr) {
493 inputQueues[trackIndex]->enqueue(sample);
494 }
495 for (trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
496 inputQueues[trackIndex]->enqueue(newSampleEos());
497 }
498
499 // Wait for writer.
500 semaphore.wait();
501 EXPECT_TRUE(writer.stop());
502
503 // Compare output file with source.
504 mediaSource.reset();
505
506 AMediaExtractor* extractor = AMediaExtractor_new();
507 ASSERT_NE(extractor, nullptr);
508
509 int sourceFd = open(destinationPath, O_RDONLY);
510 ASSERT_GT(sourceFd, 0);
511
512 off_t fileSize = lseek(sourceFd, 0, SEEK_END);
513 lseek(sourceFd, 0, SEEK_SET);
514
515 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, sourceFd, 0, fileSize);
516 ASSERT_EQ(status, AMEDIA_OK);
517 close(sourceFd);
518
519 size_t trackCount = AMediaExtractor_getTrackCount(extractor);
520 EXPECT_EQ(trackCount, mediaSource.mTrackCount);
521
522 for (size_t trackIndex = 0; trackIndex < trackCount; trackIndex++) {
523 AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(extractor, trackIndex);
524 ASSERT_NE(trackFormat, nullptr);
525
526 AMediaExtractor_selectTrack(extractor, trackIndex);
527 }
528
529 // Compare samples.
530 std::shared_ptr<MediaSample> sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
531 std::shared_ptr<MediaSample> sample2 = readSampleAndAdvance(extractor, nullptr);
532
533 while (sample1 != nullptr && sample2 != nullptr) {
534 EXPECT_EQ(sample1->info.presentationTimeUs, sample2->info.presentationTimeUs);
535 EXPECT_EQ(sample1->info.size, sample2->info.size);
536 EXPECT_EQ(sample1->info.flags, sample2->info.flags);
537
538 EXPECT_EQ(memcmp(sample1->buffer, sample2->buffer, sample1->info.size), 0);
539
540 sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
541 sample2 = readSampleAndAdvance(extractor, nullptr);
542 }
543 EXPECT_EQ(sample1, nullptr);
544 EXPECT_EQ(sample2, nullptr);
545
546 AMediaExtractor_delete(extractor);
547}
548
549} // namespace android
550
551int main(int argc, char** argv) {
552 ::testing::InitGoogleTest(&argc, argv);
553 return RUN_ALL_TESTS();
554}