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 MediaTranscoder
18
19 // #define LOG_NDEBUG 0
20 #define LOG_TAG "MediaTranscoderTests"
21
22 #include <android-base/logging.h>
23 #include <android/binder_process.h>
24 #include <fcntl.h>
25 #include <gtest/gtest.h>
26 #include <media/MediaSampleReaderNDK.h>
27 #include <media/MediaTranscoder.h>
28 #include <media/NdkCommon.h>
29
30 #include "TranscoderTestUtils.h"
31
32 namespace android {
33
34 #define DEFINE_FORMAT_VALUE_EQUAL_FUNC(_type, _typeName) \
35 static bool equal##_typeName(const char* key, AMediaFormat* src, AMediaFormat* dst) { \
36 _type srcVal, dstVal; \
37 bool srcPresent = AMediaFormat_get##_typeName(src, key, &srcVal); \
38 bool dstPresent = AMediaFormat_get##_typeName(dst, key, &dstVal); \
39 return (srcPresent == dstPresent) && (!srcPresent || (srcVal == dstVal)); \
40 }
41
42 DEFINE_FORMAT_VALUE_EQUAL_FUNC(int64_t, Int64);
43 DEFINE_FORMAT_VALUE_EQUAL_FUNC(int32_t, Int32);
44
45 struct FormatVerifierEntry {
46 const char* key;
47 std::function<bool(const char*, AMediaFormat*, AMediaFormat*)> equal;
48 };
49
50 static const FormatVerifierEntry kFieldsToPreserve[] = {
51 {AMEDIAFORMAT_KEY_DURATION, equalInt64}, {AMEDIAFORMAT_KEY_WIDTH, equalInt32},
52 {AMEDIAFORMAT_KEY_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_FRAME_RATE, equalInt32},
53 {AMEDIAFORMAT_KEY_FRAME_COUNT, equalInt32}, {AMEDIAFORMAT_KEY_DISPLAY_WIDTH, equalInt32},
54 {AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_SAR_WIDTH, equalInt32},
55 {AMEDIAFORMAT_KEY_SAR_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_ROTATION, equalInt32},
56 };
57
58 // Write-only, create file if non-existent, don't overwrite existing file.
59 static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
60 // User R+W permission.
61 static constexpr int kFileMode = S_IRUSR | S_IWUSR;
62
63 class MediaTranscoderTests : public ::testing::Test {
64 public:
MediaTranscoderTests()65 MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests created"; }
~MediaTranscoderTests()66 ~MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests destroyed"; }
67
SetUp()68 void SetUp() override {
69 LOG(DEBUG) << "MediaTranscoderTests set up";
70 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
71 ABinderProcess_startThreadPool();
72 }
73
TearDown()74 void TearDown() override {
75 LOG(DEBUG) << "MediaTranscoderTests tear down";
76 mCallbacks.reset();
77 }
78
deleteFile(const char * path)79 void deleteFile(const char* path) { unlink(path); }
80
getFileSizeDiffPercent(const char * path1,const char * path2,bool absolute=false)81 float getFileSizeDiffPercent(const char* path1, const char* path2, bool absolute = false) {
82 struct stat s1, s2;
83 EXPECT_EQ(stat(path1, &s1), 0);
84 EXPECT_EQ(stat(path2, &s2), 0);
85
86 int64_t diff = s2.st_size - s1.st_size;
87 if (absolute && diff < 0) diff = -diff;
88
89 return (float)diff * 100.0f / s1.st_size;
90 }
91
92 typedef enum {
93 kRunToCompletion,
94 kCheckHeartBeat,
95 kCancelAfterProgress,
96 kCancelAfterStart,
97 kPauseAfterProgress,
98 kPauseAfterStart,
99 } TranscodeExecutionControl;
100
101 using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
transcodeHelper(const char * srcPath,const char * destPath,FormatConfigurationCallback formatCallback,TranscodeExecutionControl executionControl=kRunToCompletion,int64_t heartBeatIntervalUs=-1)102 media_status_t transcodeHelper(const char* srcPath, const char* destPath,
103 FormatConfigurationCallback formatCallback,
104 TranscodeExecutionControl executionControl = kRunToCompletion,
105 int64_t heartBeatIntervalUs = -1) {
106 auto transcoder = MediaTranscoder::create(mCallbacks, heartBeatIntervalUs);
107 EXPECT_NE(transcoder, nullptr);
108
109 const int srcFd = open(srcPath, O_RDONLY);
110 EXPECT_EQ(transcoder->configureSource(srcFd), AMEDIA_OK);
111
112 std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
113 EXPECT_GT(trackFormats.size(), 0);
114
115 for (int i = 0; i < trackFormats.size(); ++i) {
116 AMediaFormat* format = formatCallback(trackFormats[i].get());
117 EXPECT_EQ(transcoder->configureTrackFormat(i, format), AMEDIA_OK);
118
119 // Save original video track format for verification.
120 const char* mime = nullptr;
121 AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
122 if (strncmp(mime, "video/", 6) == 0) {
123 mSourceVideoFormat = trackFormats[i];
124 }
125
126 if (format != nullptr) {
127 AMediaFormat_delete(format);
128 }
129 }
130 deleteFile(destPath);
131 const int dstFd = open(destPath, kOpenFlags, kFileMode);
132 EXPECT_EQ(transcoder->configureDestination(dstFd), AMEDIA_OK);
133
134 media_status_t startStatus = transcoder->start();
135 EXPECT_EQ(startStatus, AMEDIA_OK);
136
137 if (startStatus == AMEDIA_OK) {
138 std::shared_ptr<ndk::ScopedAParcel> pausedState;
139
140 switch (executionControl) {
141 case kCancelAfterProgress:
142 mCallbacks->waitForProgressMade();
143 FALLTHROUGH_INTENDED;
144 case kCancelAfterStart:
145 transcoder->cancel();
146 break;
147 case kPauseAfterProgress:
148 mCallbacks->waitForProgressMade();
149 FALLTHROUGH_INTENDED;
150 case kPauseAfterStart:
151 transcoder->pause(&pausedState);
152 break;
153 case kCheckHeartBeat: {
154 mCallbacks->waitForProgressMade();
155 auto startTime = std::chrono::system_clock::now();
156 mCallbacks->waitForTranscodingFinished();
157 auto finishTime = std::chrono::system_clock::now();
158 int32_t expectedCount =
159 (finishTime - startTime) / std::chrono::microseconds(heartBeatIntervalUs);
160 // Here we relax the expected count by 1, in case the last heart-beat just
161 // missed the window, other than that the count should be exact.
162 EXPECT_GE(mCallbacks->mHeartBeatCount, expectedCount - 1);
163 break;
164 }
165 case kRunToCompletion:
166 default:
167 mCallbacks->waitForTranscodingFinished();
168 break;
169 }
170 }
171 close(srcFd);
172 close(dstFd);
173
174 return mCallbacks->mStatus;
175 }
176
testTranscodeVideo(const char * srcPath,const char * destPath,const char * dstMime,int32_t bitrate=0)177 void testTranscodeVideo(const char* srcPath, const char* destPath, const char* dstMime,
178 int32_t bitrate = 0) {
179 EXPECT_EQ(transcodeHelper(srcPath, destPath,
180 [dstMime, bitrate](AMediaFormat* sourceFormat) {
181 AMediaFormat* format = nullptr;
182 const char* mime = nullptr;
183 AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME,
184 &mime);
185
186 if (strncmp(mime, "video/", 6) == 0 &&
187 (bitrate > 0 || dstMime != nullptr)) {
188 format = AMediaFormat_new();
189
190 if (bitrate > 0) {
191 AMediaFormat_setInt32(
192 format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
193 }
194
195 if (dstMime != nullptr) {
196 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME,
197 dstMime);
198 }
199 }
200 return format;
201 }),
202 AMEDIA_OK);
203
204 if (dstMime != nullptr) {
205 std::vector<FormatVerifierEntry> extraVerifiers = {
206 {AMEDIAFORMAT_KEY_MIME,
207 [dstMime](const char* key, AMediaFormat* src __unused, AMediaFormat* dst) {
208 const char* mime = nullptr;
209 AMediaFormat_getString(dst, key, &mime);
210 return !strcmp(mime, dstMime);
211 }},
212 };
213 verifyOutputFormat(destPath, &extraVerifiers);
214 } else {
215 verifyOutputFormat(destPath);
216 }
217 }
218
verifyOutputFormat(const char * destPath,const std::vector<FormatVerifierEntry> * extraVerifiers=nullptr)219 void verifyOutputFormat(const char* destPath,
220 const std::vector<FormatVerifierEntry>* extraVerifiers = nullptr) {
221 int dstFd = open(destPath, O_RDONLY);
222 EXPECT_GT(dstFd, 0);
223 ssize_t fileSize = lseek(dstFd, 0, SEEK_END);
224 lseek(dstFd, 0, SEEK_SET);
225
226 std::shared_ptr<MediaSampleReader> sampleReader =
227 MediaSampleReaderNDK::createFromFd(dstFd, 0, fileSize);
228 ASSERT_NE(sampleReader, nullptr);
229
230 std::shared_ptr<AMediaFormat> videoFormat;
231 const size_t trackCount = sampleReader->getTrackCount();
232 for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
233 AMediaFormat* trackFormat = sampleReader->getTrackFormat(static_cast<int>(trackIndex));
234 if (trackFormat != nullptr) {
235 const char* mime = nullptr;
236 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
237
238 if (strncmp(mime, "video/", 6) == 0) {
239 LOG(INFO) << "Track # " << trackIndex << ": "
240 << AMediaFormat_toString(trackFormat);
241 videoFormat = std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete);
242 break;
243 }
244 }
245 }
246
247 EXPECT_NE(videoFormat, nullptr);
248 if (videoFormat != nullptr) {
249 LOG(INFO) << "source video format: " << AMediaFormat_toString(mSourceVideoFormat.get());
250 LOG(INFO) << "transcoded video format: " << AMediaFormat_toString(videoFormat.get());
251
252 for (int i = 0; i < (sizeof(kFieldsToPreserve) / sizeof(kFieldsToPreserve[0])); ++i) {
253 EXPECT_TRUE(kFieldsToPreserve[i].equal(kFieldsToPreserve[i].key,
254 mSourceVideoFormat.get(), videoFormat.get()))
255 << "Failed at key " << kFieldsToPreserve[i].key;
256 }
257
258 if (extraVerifiers != nullptr) {
259 for (int i = 0; i < extraVerifiers->size(); ++i) {
260 const FormatVerifierEntry& entry = (*extraVerifiers)[i];
261 EXPECT_TRUE(
262 entry.equal(entry.key, mSourceVideoFormat.get(), videoFormat.get()));
263 }
264 }
265 }
266
267 close(dstFd);
268 }
269
270 std::shared_ptr<TestTranscoderCallbacks> mCallbacks;
271 std::shared_ptr<AMediaFormat> mSourceVideoFormat;
272 };
273
TEST_F(MediaTranscoderTests,TestPassthrough)274 TEST_F(MediaTranscoderTests, TestPassthrough) {
275 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
276 const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
277 testTranscodeVideo(srcPath, destPath, nullptr);
278 }
279
TEST_F(MediaTranscoderTests,TestVideoTranscode_AvcToAvc_Basic)280 TEST_F(MediaTranscoderTests, TestVideoTranscode_AvcToAvc_Basic) {
281 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
282 const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_AvcToAvc_Basic.MP4";
283 testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
284 }
285
TEST_F(MediaTranscoderTests,TestVideoTranscode_HevcToAvc_Basic)286 TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Basic) {
287 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/jets_hevc_1280x720_20Mbps.mp4";
288 const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Basic.MP4";
289 testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
290 }
291
TEST_F(MediaTranscoderTests,TestVideoTranscode_HevcToAvc_Rotation)292 TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Rotation) {
293 const char* srcPath =
294 "/data/local/tmp/TranscodingTestAssets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4";
295 const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Rotation.MP4";
296 testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
297 }
298
TEST_F(MediaTranscoderTests,TestVideoTranscode_4K)299 TEST_F(MediaTranscoderTests, TestVideoTranscode_4K) {
300 #if defined(__i386__) || defined(__x86_64__)
301 LOG(WARNING) << "Skipping 4K test on x86 as SW encoder does not support 4K.";
302 return;
303 #else
304 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/Video_4K_HEVC_10Frames_Audio.mp4";
305 const char* destPath = "/data/local/tmp/MediaTranscoder_4K.MP4";
306 testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
307 #endif
308 }
309
TEST_F(MediaTranscoderTests,TestPreserveBitrate)310 TEST_F(MediaTranscoderTests, TestPreserveBitrate) {
311 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
312 const char* destPath = "/data/local/tmp/MediaTranscoder_PreserveBitrate.MP4";
313 testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
314
315 // Require maximum of 25% difference in file size.
316 // TODO(b/174678336): Find a better test asset to tighten the threshold.
317 EXPECT_LT(getFileSizeDiffPercent(srcPath, destPath, true /* absolute*/), 25);
318 }
319
TEST_F(MediaTranscoderTests,TestCustomBitrate)320 TEST_F(MediaTranscoderTests, TestCustomBitrate) {
321 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
322 const char* destPath1 = "/data/local/tmp/MediaTranscoder_CustomBitrate_2Mbps.MP4";
323 const char* destPath2 = "/data/local/tmp/MediaTranscoder_CustomBitrate_8Mbps.MP4";
324 testTranscodeVideo(srcPath, destPath1, AMEDIA_MIMETYPE_VIDEO_AVC, 2 * 1000 * 1000);
325 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
326 testTranscodeVideo(srcPath, destPath2, AMEDIA_MIMETYPE_VIDEO_AVC, 8 * 1000 * 1000);
327
328 // The source asset is very short and heavily compressed from the beginning so don't expect the
329 // requested bitrate to be exactly matched. However the 8mbps should at least be larger.
330 // TODO(b/174678336): Find a better test asset to tighten the threshold.
331 EXPECT_GT(getFileSizeDiffPercent(destPath1, destPath2), 10);
332 }
333
getAVCVideoFormat(AMediaFormat * sourceFormat)334 static AMediaFormat* getAVCVideoFormat(AMediaFormat* sourceFormat) {
335 AMediaFormat* format = nullptr;
336 const char* mime = nullptr;
337 AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
338
339 if (strncmp(mime, "video/", 6) == 0) {
340 format = AMediaFormat_new();
341 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
342 }
343
344 return format;
345 }
346
TEST_F(MediaTranscoderTests,TestCancelAfterProgress)347 TEST_F(MediaTranscoderTests, TestCancelAfterProgress) {
348 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
349 const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
350
351 for (int i = 0; i < 20; ++i) {
352 EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterProgress),
353 AMEDIA_OK);
354 EXPECT_FALSE(mCallbacks->mFinished);
355 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
356 }
357 }
358
TEST_F(MediaTranscoderTests,TestCancelAfterStart)359 TEST_F(MediaTranscoderTests, TestCancelAfterStart) {
360 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
361 const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
362
363 for (int i = 0; i < 20; ++i) {
364 EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterStart),
365 AMEDIA_OK);
366 EXPECT_FALSE(mCallbacks->mFinished);
367 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
368 }
369 }
370
TEST_F(MediaTranscoderTests,TestPauseAfterProgress)371 TEST_F(MediaTranscoderTests, TestPauseAfterProgress) {
372 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
373 const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
374
375 for (int i = 0; i < 20; ++i) {
376 EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterProgress),
377 AMEDIA_OK);
378 EXPECT_FALSE(mCallbacks->mFinished);
379 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
380 }
381 }
382
TEST_F(MediaTranscoderTests,TestPauseAfterStart)383 TEST_F(MediaTranscoderTests, TestPauseAfterStart) {
384 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
385 const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
386
387 for (int i = 0; i < 20; ++i) {
388 EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterStart),
389 AMEDIA_OK);
390 EXPECT_FALSE(mCallbacks->mFinished);
391 mCallbacks = std::make_shared<TestTranscoderCallbacks>();
392 }
393 }
394
TEST_F(MediaTranscoderTests,TestHeartBeat)395 TEST_F(MediaTranscoderTests, TestHeartBeat) {
396 const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
397 const char* destPath = "/data/local/tmp/MediaTranscoder_HeartBeat.MP4";
398
399 // Use a shorter value of 500ms than the default 1000ms to get more heart beat for testing.
400 const int64_t heartBeatIntervalUs = 500000LL;
401 EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCheckHeartBeat,
402 heartBeatIntervalUs),
403 AMEDIA_OK);
404 EXPECT_TRUE(mCallbacks->mFinished);
405 }
406
407 } // namespace android
408
main(int argc,char ** argv)409 int main(int argc, char** argv) {
410 ::testing::InitGoogleTest(&argc, argv);
411 return RUN_ALL_TESTS();
412 }
413