1 /*
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 PassthroughTrackTranscoder
18 
19 // #define LOG_NDEBUG 0
20 #define LOG_TAG "PassthroughTrackTranscoderTests"
21 
22 #include <android-base/logging.h>
23 #include <fcntl.h>
24 #include <gtest/gtest.h>
25 #include <media/MediaSampleReaderNDK.h>
26 #include <media/NdkMediaExtractor.h>
27 #include <media/PassthroughTrackTranscoder.h>
28 #include <openssl/md5.h>
29 
30 #include <vector>
31 
32 #include "TranscoderTestUtils.h"
33 
34 namespace android {
35 
36 class PassthroughTrackTranscoderTests : public ::testing::Test {
37 public:
PassthroughTrackTranscoderTests()38     PassthroughTrackTranscoderTests() { LOG(DEBUG) << "PassthroughTrackTranscoderTests created"; }
39 
SetUp()40     void SetUp() override { LOG(DEBUG) << "PassthroughTrackTranscoderTests set up"; }
41 
initSourceAndExtractor()42     void initSourceAndExtractor() {
43         const char* sourcePath =
44                 "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
45 
46         mExtractor = AMediaExtractor_new();
47         ASSERT_NE(mExtractor, nullptr);
48 
49         mSourceFd = open(sourcePath, O_RDONLY);
50         ASSERT_GT(mSourceFd, 0);
51 
52         mSourceFileSize = lseek(mSourceFd, 0, SEEK_END);
53         lseek(mSourceFd, 0, SEEK_SET);
54 
55         media_status_t status =
56                 AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mSourceFileSize);
57         ASSERT_EQ(status, AMEDIA_OK);
58 
59         const size_t trackCount = AMediaExtractor_getTrackCount(mExtractor);
60         for (size_t trackIndex = 0; trackIndex < trackCount; trackIndex++) {
61             AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
62             ASSERT_NE(trackFormat, nullptr);
63 
64             const char* mime = nullptr;
65             AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
66             ASSERT_NE(mime, nullptr);
67 
68             if (strncmp(mime, "audio/", 6) == 0) {
69                 mTrackIndex = trackIndex;
70                 AMediaExtractor_selectTrack(mExtractor, trackIndex);
71                 break;
72             }
73 
74             AMediaFormat_delete(trackFormat);
75         }
76     }
77 
TearDown()78     void TearDown() override {
79         LOG(DEBUG) << "PassthroughTrackTranscoderTests tear down";
80         if (mExtractor != nullptr) {
81             AMediaExtractor_delete(mExtractor);
82             mExtractor = nullptr;
83         }
84         if (mSourceFd > 0) {
85             close(mSourceFd);
86             mSourceFd = -1;
87         }
88     }
89 
~PassthroughTrackTranscoderTests()90     ~PassthroughTrackTranscoderTests() {
91         LOG(DEBUG) << "PassthroughTrackTranscoderTests destroyed";
92     }
93 
94     int mSourceFd = -1;
95     size_t mSourceFileSize;
96     int mTrackIndex;
97     AMediaExtractor* mExtractor = nullptr;
98 };
99 
100 /** Helper class for comparing sample data using checksums. */
101 class SampleID {
102 public:
SampleID(const uint8_t * sampleData,ssize_t sampleSize)103     SampleID(const uint8_t* sampleData, ssize_t sampleSize) : mSize{sampleSize} {
104         MD5_CTX md5Ctx;
105         MD5_Init(&md5Ctx);
106         MD5_Update(&md5Ctx, sampleData, sampleSize);
107         MD5_Final(mChecksum, &md5Ctx);
108     }
109 
operator ==(const SampleID & rhs) const110     bool operator==(const SampleID& rhs) const {
111         return mSize == rhs.mSize && memcmp(mChecksum, rhs.mChecksum, MD5_DIGEST_LENGTH) == 0;
112     }
113 
114     uint8_t mChecksum[MD5_DIGEST_LENGTH];
115     ssize_t mSize;
116 };
117 
118 /**
119  * Tests that the output samples of PassthroughTrackTranscoder are identical to the source samples
120  * and in correct order.
121  */
TEST_F(PassthroughTrackTranscoderTests,SampleEquality)122 TEST_F(PassthroughTrackTranscoderTests, SampleEquality) {
123     LOG(DEBUG) << "Testing SampleEquality";
124 
125     ssize_t bufferSize = 1024;
126     auto buffer = std::make_unique<uint8_t[]>(bufferSize);
127 
128     initSourceAndExtractor();
129 
130     // Loop through all samples of a track and store size and checksums.
131     std::vector<SampleID> sampleChecksums;
132 
133     int64_t sampleTime = AMediaExtractor_getSampleTime(mExtractor);
134     while (sampleTime != -1) {
135         if (AMediaExtractor_getSampleTrackIndex(mExtractor) == mTrackIndex) {
136             ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
137             if (bufferSize < sampleSize) {
138                 bufferSize = sampleSize;
139                 buffer = std::make_unique<uint8_t[]>(bufferSize);
140             }
141 
142             ssize_t bytesRead =
143                     AMediaExtractor_readSampleData(mExtractor, buffer.get(), bufferSize);
144             ASSERT_EQ(bytesRead, sampleSize);
145 
146             SampleID sampleId{buffer.get(), sampleSize};
147             sampleChecksums.push_back(sampleId);
148         }
149 
150         AMediaExtractor_advance(mExtractor);
151         sampleTime = AMediaExtractor_getSampleTime(mExtractor);
152     }
153 
154     // Create and start the transcoder.
155     auto callback = std::make_shared<TestTrackTranscoderCallback>();
156     PassthroughTrackTranscoder transcoder{callback};
157 
158     std::shared_ptr<MediaSampleReader> mediaSampleReader =
159             MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mSourceFileSize);
160     EXPECT_NE(mediaSampleReader, nullptr);
161 
162     EXPECT_EQ(mediaSampleReader->selectTrack(mTrackIndex), AMEDIA_OK);
163     EXPECT_EQ(transcoder.configure(mediaSampleReader, mTrackIndex, nullptr /* destinationFormat */),
164               AMEDIA_OK);
165     ASSERT_TRUE(transcoder.start());
166 
167     // Pull transcoder's output samples and compare against input checksums.
168     bool eos = false;
169     uint64_t sampleCount = 0;
170     transcoder.setSampleConsumer(
171             [&sampleCount, &sampleChecksums, &eos](const std::shared_ptr<MediaSample>& sample) {
172                 ASSERT_NE(sample, nullptr);
173                 EXPECT_FALSE(eos);
174 
175                 if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
176                     eos = true;
177                 } else {
178                     SampleID sampleId{sample->buffer, static_cast<ssize_t>(sample->info.size)};
179                     EXPECT_TRUE(sampleId == sampleChecksums[sampleCount]);
180                     ++sampleCount;
181                 }
182             });
183 
184     callback->waitUntilFinished();
185     EXPECT_EQ(sampleCount, sampleChecksums.size());
186 }
187 
188 /** Class for testing PassthroughTrackTranscoder's built in buffer pool. */
189 class BufferPoolTests : public ::testing::Test {
190 public:
191     static constexpr int kMaxBuffers = 5;
192 
SetUp()193     void SetUp() override {
194         LOG(DEBUG) << "BufferPoolTests set up";
195         mBufferPool = std::make_shared<PassthroughTrackTranscoder::BufferPool>(kMaxBuffers);
196     }
197 
TearDown()198     void TearDown() override {
199         LOG(DEBUG) << "BufferPoolTests tear down";
200         mBufferPool.reset();
201     }
202 
203     std::shared_ptr<PassthroughTrackTranscoder::BufferPool> mBufferPool;
204 };
205 
TEST_F(BufferPoolTests,BufferReuse)206 TEST_F(BufferPoolTests, BufferReuse) {
207     LOG(DEBUG) << "Testing BufferReuse";
208 
209     uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
210     EXPECT_NE(buffer1, nullptr);
211 
212     uint8_t* buffer2 = mBufferPool->getBufferWithSize(10);
213     EXPECT_NE(buffer2, nullptr);
214     EXPECT_NE(buffer2, buffer1);
215 
216     mBufferPool->returnBuffer(buffer1);
217 
218     uint8_t* buffer3 = mBufferPool->getBufferWithSize(10);
219     EXPECT_NE(buffer3, nullptr);
220     EXPECT_NE(buffer3, buffer2);
221     EXPECT_EQ(buffer3, buffer1);
222 
223     mBufferPool->returnBuffer(buffer2);
224 
225     uint8_t* buffer4 = mBufferPool->getBufferWithSize(10);
226     EXPECT_NE(buffer4, nullptr);
227     EXPECT_NE(buffer4, buffer1);
228     EXPECT_EQ(buffer4, buffer2);
229 }
230 
TEST_F(BufferPoolTests,SmallestAvailableBuffer)231 TEST_F(BufferPoolTests, SmallestAvailableBuffer) {
232     LOG(DEBUG) << "Testing SmallestAvailableBuffer";
233 
234     uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
235     EXPECT_NE(buffer1, nullptr);
236 
237     uint8_t* buffer2 = mBufferPool->getBufferWithSize(15);
238     EXPECT_NE(buffer2, nullptr);
239     EXPECT_NE(buffer2, buffer1);
240 
241     uint8_t* buffer3 = mBufferPool->getBufferWithSize(20);
242     EXPECT_NE(buffer3, nullptr);
243     EXPECT_NE(buffer3, buffer1);
244     EXPECT_NE(buffer3, buffer2);
245 
246     mBufferPool->returnBuffer(buffer1);
247     mBufferPool->returnBuffer(buffer2);
248     mBufferPool->returnBuffer(buffer3);
249 
250     uint8_t* buffer4 = mBufferPool->getBufferWithSize(11);
251     EXPECT_NE(buffer4, nullptr);
252     EXPECT_EQ(buffer4, buffer2);
253 
254     uint8_t* buffer5 = mBufferPool->getBufferWithSize(11);
255     EXPECT_NE(buffer5, nullptr);
256     EXPECT_EQ(buffer5, buffer3);
257 }
258 
TEST_F(BufferPoolTests,AddAfterAbort)259 TEST_F(BufferPoolTests, AddAfterAbort) {
260     LOG(DEBUG) << "Testing AddAfterAbort";
261 
262     uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
263     EXPECT_NE(buffer1, nullptr);
264     mBufferPool->returnBuffer(buffer1);
265 
266     mBufferPool->abort();
267     uint8_t* buffer2 = mBufferPool->getBufferWithSize(10);
268     EXPECT_EQ(buffer2, nullptr);
269 }
270 
TEST_F(BufferPoolTests,MaximumBuffers)271 TEST_F(BufferPoolTests, MaximumBuffers) {
272     LOG(DEBUG) << "Testing MaximumBuffers";
273 
274     static constexpr size_t kBufferBaseSize = 10;
275     std::unordered_map<uint8_t*, size_t> addressSizeMap;
276 
277     // Get kMaxBuffers * 2 new buffers with increasing size.
278     // (Note: Once kMaxBuffers have been allocated, the pool will delete old buffers to accommodate
279     // new ones making the deleted buffers free to be reused by the system's heap memory allocator.
280     // So we cannot test that each new pointer is unique here.)
281     for (int i = 0; i < kMaxBuffers * 2; i++) {
282         size_t size = kBufferBaseSize + i;
283         uint8_t* buffer = mBufferPool->getBufferWithSize(size);
284         EXPECT_NE(buffer, nullptr);
285         addressSizeMap[buffer] = size;
286         mBufferPool->returnBuffer(buffer);
287     }
288 
289     // Verify that the pool now contains the kMaxBuffers largest buffers allocated above and that
290     // the buffer of matching size is returned.
291     for (int i = kMaxBuffers; i < kMaxBuffers * 2; i++) {
292         size_t size = kBufferBaseSize + i;
293         uint8_t* buffer = mBufferPool->getBufferWithSize(size);
294         EXPECT_NE(buffer, nullptr);
295 
296         auto it = addressSizeMap.find(buffer);
297         ASSERT_NE(it, addressSizeMap.end());
298         EXPECT_EQ(it->second, size);
299         mBufferPool->returnBuffer(buffer);
300     }
301 }
302 
303 }  // namespace android
304 
main(int argc,char ** argv)305 int main(int argc, char** argv) {
306     ::testing::InitGoogleTest(&argc, argv);
307     return RUN_ALL_TESTS();
308 }
309