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