blob: 11af0b15b1c197ec04f184d5fc2da3a628e6bf29 [file] [log] [blame]
Linus Nilsson478df7e2020-01-29 15:34:24 -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 MediaSampleReaderNDK
18
19// #define LOG_NDEBUG 0
20#define LOG_TAG "MediaSampleReaderNDKTests"
21
22#include <android-base/logging.h>
23#include <android/binder_manager.h>
24#include <android/binder_process.h>
25#include <fcntl.h>
26#include <gtest/gtest.h>
27#include <media/MediaSampleReaderNDK.h>
Linus Nilsson0c01f3d2020-12-01 09:29:50 -080028#include <openssl/md5.h>
Linus Nilsson478df7e2020-01-29 15:34:24 -080029#include <utils/Timers.h>
Linus Nilssonc31d2492020-09-23 12:30:00 -070030
Linus Nilsson800793f2020-07-31 16:16:38 -070031#include <cmath>
Linus Nilsson6233fed2020-08-13 15:15:14 -070032#include <mutex>
33#include <thread>
Linus Nilsson800793f2020-07-31 16:16:38 -070034
Linus Nilsson0c01f3d2020-12-01 09:29:50 -080035// TODO(b/153453392): Test more asset types (frame reordering?).
Linus Nilsson478df7e2020-01-29 15:34:24 -080036
37namespace android {
38
39#define SEC_TO_USEC(s) ((s)*1000 * 1000)
40
Linus Nilsson0c01f3d2020-12-01 09:29:50 -080041/** Helper class for comparing sample data using checksums. */
42class Sample {
43public:
44 Sample(uint32_t flags, int64_t timestamp, size_t size, const uint8_t* buffer)
45 : mFlags{flags}, mTimestamp{timestamp}, mSize{size} {
46 initChecksum(buffer);
47 }
48
49 Sample(AMediaExtractor* extractor) {
50 mFlags = AMediaExtractor_getSampleFlags(extractor);
51 mTimestamp = AMediaExtractor_getSampleTime(extractor);
52 mSize = static_cast<size_t>(AMediaExtractor_getSampleSize(extractor));
53
54 auto buffer = std::make_unique<uint8_t[]>(mSize);
55 AMediaExtractor_readSampleData(extractor, buffer.get(), mSize);
56
57 initChecksum(buffer.get());
58 }
59
60 void initChecksum(const uint8_t* buffer) {
61 MD5_CTX md5Ctx;
62 MD5_Init(&md5Ctx);
63 MD5_Update(&md5Ctx, buffer, mSize);
64 MD5_Final(mChecksum, &md5Ctx);
65 }
66
67 bool operator==(const Sample& rhs) const {
68 return mSize == rhs.mSize && mFlags == rhs.mFlags && mTimestamp == rhs.mTimestamp &&
69 memcmp(mChecksum, rhs.mChecksum, MD5_DIGEST_LENGTH) == 0;
70 }
71
72 uint32_t mFlags;
73 int64_t mTimestamp;
74 size_t mSize;
75 uint8_t mChecksum[MD5_DIGEST_LENGTH];
76};
77
78/** Constant for selecting all samples. */
79static constexpr int SAMPLE_COUNT_ALL = -1;
80
81/**
82 * Utility class to test different sample access patterns combined with sequential or parallel
83 * sample access modes.
84 */
85class SampleAccessTester {
86public:
87 SampleAccessTester(int sourceFd, size_t fileSize) {
88 mSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0, fileSize);
89 EXPECT_TRUE(mSampleReader);
90
91 mTrackCount = mSampleReader->getTrackCount();
92
93 for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
94 EXPECT_EQ(mSampleReader->selectTrack(trackIndex), AMEDIA_OK);
95 }
96
97 mSamples.resize(mTrackCount);
98 mTrackThreads.resize(mTrackCount);
99 }
100
101 void getSampleInfo(int trackIndex) {
102 MediaSampleInfo info;
103 media_status_t status = mSampleReader->getSampleInfoForTrack(trackIndex, &info);
104 EXPECT_EQ(status, AMEDIA_OK);
105 }
106
107 void readSamplesAsync(int trackIndex, int sampleCount) {
108 mTrackThreads[trackIndex] = std::thread{[this, trackIndex, sampleCount] {
109 int samplesRead = 0;
110 MediaSampleInfo info;
111 while (samplesRead < sampleCount || sampleCount == SAMPLE_COUNT_ALL) {
112 media_status_t status = mSampleReader->getSampleInfoForTrack(trackIndex, &info);
113 if (status != AMEDIA_OK) {
114 EXPECT_EQ(status, AMEDIA_ERROR_END_OF_STREAM);
115 EXPECT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0);
116 break;
117 }
118 ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
119
120 auto buffer = std::make_unique<uint8_t[]>(info.size);
121 status = mSampleReader->readSampleDataForTrack(trackIndex, buffer.get(), info.size);
122 EXPECT_EQ(status, AMEDIA_OK);
123
124 mSampleMutex.lock();
125 const uint8_t* bufferPtr = buffer.get();
126 mSamples[trackIndex].emplace_back(info.flags, info.presentationTimeUs, info.size,
127 bufferPtr);
128 mSampleMutex.unlock();
129 ++samplesRead;
130 }
131 }};
132 }
133
134 void readSamplesAsync(int sampleCount) {
135 for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
136 readSamplesAsync(trackIndex, sampleCount);
137 }
138 }
139
140 void waitForTrack(int trackIndex) {
141 ASSERT_TRUE(mTrackThreads[trackIndex].joinable());
142 mTrackThreads[trackIndex].join();
143 }
144
145 void waitForTracks() {
146 for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
147 waitForTrack(trackIndex);
148 }
149 }
150
151 void setEnforceSequentialAccess(bool enforce) {
152 media_status_t status = mSampleReader->setEnforceSequentialAccess(enforce);
153 EXPECT_EQ(status, AMEDIA_OK);
154 }
155
156 std::vector<std::vector<Sample>>& getSamples() { return mSamples; }
157
158 std::shared_ptr<MediaSampleReader> mSampleReader;
159 size_t mTrackCount;
160 std::mutex mSampleMutex;
161 std::vector<std::thread> mTrackThreads;
162 std::vector<std::vector<Sample>> mSamples;
163};
164
Linus Nilsson478df7e2020-01-29 15:34:24 -0800165class MediaSampleReaderNDKTests : public ::testing::Test {
166public:
167 MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests created"; }
168
169 void SetUp() override {
170 LOG(DEBUG) << "MediaSampleReaderNDKTests set up";
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800171
172 // Need to start a thread pool to prevent AMediaExtractor binder calls from starving
173 // (b/155663561).
174 ABinderProcess_startThreadPool();
175
Linus Nilsson478df7e2020-01-29 15:34:24 -0800176 const char* sourcePath =
hkuang2ef2b432020-06-15 18:33:11 -0700177 "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
Linus Nilsson478df7e2020-01-29 15:34:24 -0800178
Linus Nilsson478df7e2020-01-29 15:34:24 -0800179 mSourceFd = open(sourcePath, O_RDONLY);
180 ASSERT_GT(mSourceFd, 0);
181
182 mFileSize = lseek(mSourceFd, 0, SEEK_END);
183 lseek(mSourceFd, 0, SEEK_SET);
184
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800185 mExtractor = AMediaExtractor_new();
186 ASSERT_NE(mExtractor, nullptr);
187
Linus Nilsson478df7e2020-01-29 15:34:24 -0800188 media_status_t status =
189 AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mFileSize);
190 ASSERT_EQ(status, AMEDIA_OK);
191
192 mTrackCount = AMediaExtractor_getTrackCount(mExtractor);
193 for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
194 AMediaExtractor_selectTrack(mExtractor, trackIndex);
195 }
196 }
197
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800198 void initExtractorSamples() {
199 if (mExtractorSamples.size() == mTrackCount) return;
200
201 // Save sample information, per track, as reported by the extractor.
202 mExtractorSamples.resize(mTrackCount);
Linus Nilsson478df7e2020-01-29 15:34:24 -0800203 do {
204 const int trackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800205 mExtractorSamples[trackIndex].emplace_back(mExtractor);
Linus Nilsson478df7e2020-01-29 15:34:24 -0800206 } while (AMediaExtractor_advance(mExtractor));
Linus Nilsson800793f2020-07-31 16:16:38 -0700207
208 AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
209 }
210
211 std::vector<int32_t> getTrackBitrates() {
212 size_t totalSize[mTrackCount];
213 memset(totalSize, 0, sizeof(totalSize));
214
215 do {
216 const int trackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
217 totalSize[trackIndex] += AMediaExtractor_getSampleSize(mExtractor);
218 } while (AMediaExtractor_advance(mExtractor));
219
220 AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
221
222 std::vector<int32_t> bitrates;
223 for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
224 int64_t durationUs;
225 AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
226 EXPECT_NE(trackFormat, nullptr);
227 EXPECT_TRUE(AMediaFormat_getInt64(trackFormat, AMEDIAFORMAT_KEY_DURATION, &durationUs));
228 bitrates.push_back(roundf((float)totalSize[trackIndex] * 8 * 1000000 / durationUs));
229 }
230
231 return bitrates;
Linus Nilsson478df7e2020-01-29 15:34:24 -0800232 }
233
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800234 void compareSamples(std::vector<std::vector<Sample>>& readerSamples) {
235 initExtractorSamples();
236 EXPECT_EQ(readerSamples.size(), mTrackCount);
237
238 for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
239 LOG(DEBUG) << "Track " << trackIndex << ", comparing "
240 << readerSamples[trackIndex].size() << " samples.";
241 EXPECT_EQ(readerSamples[trackIndex].size(), mExtractorSamples[trackIndex].size());
242 for (size_t sampleIndex = 0; sampleIndex < readerSamples[trackIndex].size();
243 sampleIndex++) {
244 EXPECT_EQ(readerSamples[trackIndex][sampleIndex],
245 mExtractorSamples[trackIndex][sampleIndex]);
246 }
247 }
248 }
249
Linus Nilsson478df7e2020-01-29 15:34:24 -0800250 void TearDown() override {
251 LOG(DEBUG) << "MediaSampleReaderNDKTests tear down";
252 AMediaExtractor_delete(mExtractor);
253 close(mSourceFd);
254 }
255
256 ~MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests destroyed"; }
257
258 AMediaExtractor* mExtractor = nullptr;
259 size_t mTrackCount;
260 int mSourceFd;
261 size_t mFileSize;
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800262 std::vector<std::vector<Sample>> mExtractorSamples;
Linus Nilsson478df7e2020-01-29 15:34:24 -0800263};
264
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800265/** Reads all samples from all tracks in parallel. */
266TEST_F(MediaSampleReaderNDKTests, TestParallelSampleAccess) {
267 LOG(DEBUG) << "TestParallelSampleAccess Starts";
Linus Nilsson478df7e2020-01-29 15:34:24 -0800268
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800269 SampleAccessTester tester{mSourceFd, mFileSize};
270 tester.readSamplesAsync(SAMPLE_COUNT_ALL);
271 tester.waitForTracks();
272 compareSamples(tester.getSamples());
273}
Linus Nilsson478df7e2020-01-29 15:34:24 -0800274
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800275/** Reads all samples from all tracks sequentially. */
276TEST_F(MediaSampleReaderNDKTests, TestSequentialSampleAccess) {
277 LOG(DEBUG) << "TestSequentialSampleAccess Starts";
Linus Nilsson478df7e2020-01-29 15:34:24 -0800278
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800279 SampleAccessTester tester{mSourceFd, mFileSize};
280 tester.setEnforceSequentialAccess(true);
281 tester.readSamplesAsync(SAMPLE_COUNT_ALL);
282 tester.waitForTracks();
283 compareSamples(tester.getSamples());
284}
Linus Nilsson478df7e2020-01-29 15:34:24 -0800285
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800286/** Reads all samples from one track in parallel mode before switching to sequential mode. */
287TEST_F(MediaSampleReaderNDKTests, TestMixedSampleAccessTrackEOS) {
288 LOG(DEBUG) << "TestMixedSampleAccessTrackEOS Starts";
Linus Nilsson478df7e2020-01-29 15:34:24 -0800289
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800290 for (int readSampleInfoFlag = 0; readSampleInfoFlag <= 1; readSampleInfoFlag++) {
291 for (int trackIndToEOS = 0; trackIndToEOS < mTrackCount; ++trackIndToEOS) {
292 LOG(DEBUG) << "Testing EOS of track " << trackIndToEOS;
293
294 SampleAccessTester tester{mSourceFd, mFileSize};
295
296 // If the flag is set, read sample info from a different track before draining the track
297 // under test to force the reader to save the extractor position.
298 if (readSampleInfoFlag) {
299 tester.getSampleInfo((trackIndToEOS + 1) % mTrackCount);
Linus Nilsson6233fed2020-08-13 15:15:14 -0700300 }
Linus Nilsson6233fed2020-08-13 15:15:14 -0700301
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800302 // Read all samples from one track before enabling sequential access
303 tester.readSamplesAsync(trackIndToEOS, SAMPLE_COUNT_ALL);
304 tester.waitForTrack(trackIndToEOS);
305 tester.setEnforceSequentialAccess(true);
Linus Nilsson478df7e2020-01-29 15:34:24 -0800306
Linus Nilsson0c01f3d2020-12-01 09:29:50 -0800307 for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
308 if (trackIndex == trackIndToEOS) continue;
309
310 tester.readSamplesAsync(trackIndex, SAMPLE_COUNT_ALL);
311 tester.waitForTrack(trackIndex);
312 }
313
314 compareSamples(tester.getSamples());
315 }
316 }
317}
318
319/**
320 * Reads different combinations of sample counts from all tracks in parallel mode before switching
321 * to sequential mode and reading the rest of the samples.
322 */
323TEST_F(MediaSampleReaderNDKTests, TestMixedSampleAccess) {
324 LOG(DEBUG) << "TestMixedSampleAccess Starts";
325 initExtractorSamples();
326
327 for (int trackIndToTest = 0; trackIndToTest < mTrackCount; ++trackIndToTest) {
328 for (int sampleCount = 0; sampleCount <= (mExtractorSamples[trackIndToTest].size() + 1);
329 ++sampleCount) {
330 SampleAccessTester tester{mSourceFd, mFileSize};
331
332 for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
333 if (trackIndex == trackIndToTest) {
334 tester.readSamplesAsync(trackIndex, sampleCount);
335 } else {
336 tester.readSamplesAsync(trackIndex, mExtractorSamples[trackIndex].size() / 2);
337 }
338 }
339
340 tester.waitForTracks();
341 tester.setEnforceSequentialAccess(true);
342
343 tester.readSamplesAsync(SAMPLE_COUNT_ALL);
344 tester.waitForTracks();
345
346 compareSamples(tester.getSamples());
Linus Nilsson478df7e2020-01-29 15:34:24 -0800347 }
348 }
349}
350
Linus Nilsson800793f2020-07-31 16:16:38 -0700351TEST_F(MediaSampleReaderNDKTests, TestEstimatedBitrateAccuracy) {
352 // Just put a somewhat reasonable upper bound on the estimated bitrate expected in our test
353 // assets. This is mostly to make sure the estimation is not way off.
354 static constexpr int32_t kMaxEstimatedBitrate = 100 * 1000 * 1000; // 100 Mbps
355
356 auto sampleReader = MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mFileSize);
357 ASSERT_TRUE(sampleReader);
358
359 std::vector<int32_t> actualTrackBitrates = getTrackBitrates();
360 for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
Linus Nilsson6233fed2020-08-13 15:15:14 -0700361 EXPECT_EQ(sampleReader->selectTrack(trackIndex), AMEDIA_OK);
362
Linus Nilsson800793f2020-07-31 16:16:38 -0700363 int32_t bitrate;
364 EXPECT_EQ(sampleReader->getEstimatedBitrateForTrack(trackIndex, &bitrate), AMEDIA_OK);
365 EXPECT_GT(bitrate, 0);
366 EXPECT_LT(bitrate, kMaxEstimatedBitrate);
367
368 // Note: The test asset currently used in this test is shorter than the sampling duration
369 // used to estimate the bitrate in the sample reader. So for now the estimation should be
370 // exact but if/when a longer asset is used a reasonable delta needs to be defined.
371 EXPECT_EQ(bitrate, actualTrackBitrates[trackIndex]);
372 }
373}
374
Linus Nilsson478df7e2020-01-29 15:34:24 -0800375TEST_F(MediaSampleReaderNDKTests, TestInvalidFd) {
376 std::shared_ptr<MediaSampleReader> sampleReader =
377 MediaSampleReaderNDK::createFromFd(0, 0, mFileSize);
378 ASSERT_TRUE(sampleReader == nullptr);
379
380 sampleReader = MediaSampleReaderNDK::createFromFd(-1, 0, mFileSize);
381 ASSERT_TRUE(sampleReader == nullptr);
382}
383
384TEST_F(MediaSampleReaderNDKTests, TestZeroSize) {
385 std::shared_ptr<MediaSampleReader> sampleReader =
386 MediaSampleReaderNDK::createFromFd(mSourceFd, 0, 0);
387 ASSERT_TRUE(sampleReader == nullptr);
388}
389
390TEST_F(MediaSampleReaderNDKTests, TestInvalidOffset) {
391 std::shared_ptr<MediaSampleReader> sampleReader =
392 MediaSampleReaderNDK::createFromFd(mSourceFd, mFileSize, mFileSize);
393 ASSERT_TRUE(sampleReader == nullptr);
394}
395
396} // namespace android
397
398int main(int argc, char** argv) {
399 ::testing::InitGoogleTest(&argc, argv);
400 return RUN_ALL_TESTS();
401}