/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Unit Test for TranscodingLogger // #define LOG_NDEBUG 0 #define LOG_TAG "TranscodingLoggerTest" #include #include #include #include #include #include #include namespace android { using Reason = TranscodingLogger::SessionEndedReason; // Data structure corresponding to MediaTranscodingEnded atom. struct SessionEndedAtom { SessionEndedAtom(int32_t atomCode, int32_t reason, int32_t callingUid, int32_t status, int32_t transcoderFps, int32_t srcWidth, int32_t srcHeight, char const* srcMime, int32_t srcProfile, int32_t srcLevel, int32_t srcFps, int32_t srcDurationMs, bool srcIsHdr, int32_t dstWidth, int32_t dstHeight, char const* dstMime, bool dstIsHdr) : atomCode(atomCode), reason(reason), callingUid(callingUid), status(status), transcoderFps(transcoderFps), srcWidth(srcWidth), srcHeight(srcHeight), srcMime(srcMime), srcProfile(srcProfile), srcLevel(srcLevel), srcFps(srcFps), srcDurationMs(srcDurationMs), srcIsHdr(srcIsHdr), dstWidth(dstWidth), dstHeight(dstHeight), dstMime(dstMime), dstIsHdr(dstIsHdr) {} int32_t atomCode; int32_t reason; int32_t callingUid; int32_t status; int32_t transcoderFps; int32_t srcWidth; int32_t srcHeight; std::string srcMime; int32_t srcProfile; int32_t srcLevel; int32_t srcFps; int32_t srcDurationMs; bool srcIsHdr; int32_t dstWidth; int32_t dstHeight; std::string dstMime; bool dstIsHdr; }; // Default configuration values. static constexpr int32_t kDefaultCallingUid = 1; static constexpr std::chrono::microseconds kDefaultTranscodeDuration = std::chrono::seconds{2}; static constexpr int32_t kDefaultSrcWidth = 1920; static constexpr int32_t kDefaultSrcHeight = 1080; static const std::string kDefaultSrcMime{AMEDIA_MIMETYPE_VIDEO_HEVC}; static constexpr int32_t kDefaultSrcProfile = 1; // HEVC Main static constexpr int32_t kDefaultSrcLevel = 65536; // HEVCMainTierLevel51 static constexpr int32_t kDefaultSrcFps = 30; static constexpr int32_t kDefaultSrcFrameCount = 120; static constexpr int64_t kDefaultSrcDurationUs = 1000000 * kDefaultSrcFrameCount / kDefaultSrcFps; static constexpr int32_t kDefaultDstWidth = 1280; static constexpr int32_t kDefaultDstHeight = 720; static const std::string kDefaultDstMime{AMEDIA_MIMETYPE_VIDEO_AVC}; // Util for creating a default source video format. static AMediaFormat* CreateSrcFormat() { AMediaFormat* fmt = AMediaFormat_new(); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, kDefaultSrcWidth); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, kDefaultSrcHeight); AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, kDefaultSrcMime.c_str()); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_PROFILE, kDefaultSrcProfile); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_LEVEL, kDefaultSrcLevel); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_FRAME_RATE, kDefaultSrcFps); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_FRAME_COUNT, kDefaultSrcFrameCount); AMediaFormat_setInt64(fmt, AMEDIAFORMAT_KEY_DURATION, kDefaultSrcDurationUs); return fmt; } // Util for creating a default destination video format. static AMediaFormat* CreateDstFormat() { AMediaFormat* fmt = AMediaFormat_new(); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, kDefaultDstWidth); AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, kDefaultDstHeight); AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, kDefaultDstMime.c_str()); return fmt; } class TranscodingLoggerTest : public ::testing::Test { public: TranscodingLoggerTest() { ALOGI("TranscodingLoggerTest created"); } void SetUp() override { ALOGI("TranscodingLoggerTest set up"); mLogger.reset(new TranscodingLogger()); mLoggedAtoms.clear(); mSrcFormat.reset(); mDstFormat.reset(); // Set a custom atom writer that saves all data, so the test can validate it afterwards. mLogger->setSessionEndedAtomWriter( [=](int32_t atomCode, int32_t reason, int32_t callingUid, int32_t status, int32_t transcoderFps, int32_t srcWidth, int32_t srcHeight, char const* srcMime, int32_t srcProfile, int32_t srcLevel, int32_t srcFps, int32_t srcDurationMs, bool srcIsHdr, int32_t dstWidth, int32_t dstHeight, char const* dstMime, bool dstIsHdr) -> int { mLoggedAtoms.emplace_back(atomCode, reason, callingUid, status, transcoderFps, srcWidth, srcHeight, srcMime, srcProfile, srcLevel, srcFps, srcDurationMs, srcIsHdr, dstWidth, dstHeight, dstMime, dstIsHdr); return 0; }); } void logSession(const std::chrono::steady_clock::time_point& time, Reason reason, int status, AMediaFormat* srcFormat, AMediaFormat* dstFormat) { mLogger->logSessionEnded(time, reason, kDefaultCallingUid, status, kDefaultTranscodeDuration, srcFormat, dstFormat); } void logSession(const std::chrono::steady_clock::time_point& time, Reason reason, int status) { if (!mSrcFormat) { mSrcFormat = std::shared_ptr(CreateSrcFormat(), &AMediaFormat_delete); } if (!mDstFormat) { mDstFormat = std::shared_ptr(CreateDstFormat(), &AMediaFormat_delete); } logSession(time, reason, status, mSrcFormat.get(), mDstFormat.get()); } void logSessionFinished(const std::chrono::steady_clock::time_point& time) { logSession(time, Reason::FINISHED, 0); } void logSessionFailed(const std::chrono::steady_clock::time_point& time) { logSession(time, Reason::ERROR, AMEDIA_ERROR_UNKNOWN); } int logCount() const { return mLoggedAtoms.size(); } void validateLatestAtom(Reason reason, int status, bool passthrough = false) { const SessionEndedAtom& atom = mLoggedAtoms.back(); EXPECT_EQ(atom.atomCode, android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED); EXPECT_EQ(atom.reason, static_cast(reason)); EXPECT_EQ(atom.callingUid, kDefaultCallingUid); EXPECT_EQ(atom.status, status); EXPECT_EQ(atom.srcWidth, kDefaultSrcWidth); EXPECT_EQ(atom.srcHeight, kDefaultSrcHeight); EXPECT_EQ(atom.srcMime, kDefaultSrcMime); EXPECT_EQ(atom.srcProfile, kDefaultSrcProfile); EXPECT_EQ(atom.srcLevel, kDefaultSrcLevel); EXPECT_EQ(atom.srcFps, kDefaultSrcFps); EXPECT_EQ(atom.srcDurationMs, kDefaultSrcDurationUs / 1000); EXPECT_FALSE(atom.srcIsHdr); EXPECT_EQ(atom.dstWidth, passthrough ? kDefaultSrcWidth : kDefaultDstWidth); EXPECT_EQ(atom.dstHeight, passthrough ? kDefaultSrcHeight : kDefaultDstHeight); EXPECT_EQ(atom.dstMime, passthrough ? "passthrough" : kDefaultDstMime); EXPECT_FALSE(atom.dstIsHdr); // Transcoder frame rate is only present on successful sessions. if (status == AMEDIA_OK) { std::chrono::duration seconds{kDefaultTranscodeDuration}; const int32_t transcoderFps = static_cast(kDefaultSrcFrameCount / seconds.count()); EXPECT_EQ(atom.transcoderFps, transcoderFps); } else { EXPECT_EQ(atom.transcoderFps, -1); } } void TearDown() override { ALOGI("TranscodingLoggerTest tear down"); } ~TranscodingLoggerTest() { ALOGD("TranscodingLoggerTest destroyed"); } std::shared_ptr mLogger; std::vector mLoggedAtoms; std::shared_ptr mSrcFormat; std::shared_ptr mDstFormat; }; TEST_F(TranscodingLoggerTest, TestDailyLogQuota) { ALOGD("TestDailyLogQuota"); auto start = std::chrono::steady_clock::now(); EXPECT_LT(TranscodingLogger::kMaxSuccessfulAtomsPerDay, TranscodingLogger::kMaxAtomsPerDay); // 1. Check that the first kMaxSuccessfulAtomsPerDay successful atoms are logged. for (int i = 0; i < TranscodingLogger::kMaxSuccessfulAtomsPerDay; ++i) { logSessionFinished(start + std::chrono::seconds{i}); EXPECT_EQ(logCount(), i + 1); } // 2. Check that subsequent successful atoms within the same 24h interval are not logged. for (int i = 1; i < 24; ++i) { logSessionFinished(start + std::chrono::hours{i}); EXPECT_EQ(logCount(), TranscodingLogger::kMaxSuccessfulAtomsPerDay); } // 3. Check that failed atoms are logged up to kMaxAtomsPerDay. for (int i = TranscodingLogger::kMaxSuccessfulAtomsPerDay; i < TranscodingLogger::kMaxAtomsPerDay; ++i) { logSessionFailed(start + std::chrono::seconds{i}); EXPECT_EQ(logCount(), i + 1); } // 4. Check that subsequent failed atoms within the same 24h interval are not logged. for (int i = 1; i < 24; ++i) { logSessionFailed(start + std::chrono::hours{i}); EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay); } // 5. Check that failed and successful atoms are logged again after 24h. logSessionFinished(start + std::chrono::hours{24}); EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay + 1); logSessionFailed(start + std::chrono::hours{24} + std::chrono::seconds{1}); EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay + 2); } TEST_F(TranscodingLoggerTest, TestNullFormats) { ALOGD("TestNullFormats"); auto srcFormat = std::shared_ptr(CreateSrcFormat(), &AMediaFormat_delete); auto dstFormat = std::shared_ptr(CreateDstFormat(), &AMediaFormat_delete); auto now = std::chrono::steady_clock::now(); // Source format null, should not log. logSession(now, Reason::FINISHED, AMEDIA_OK, nullptr /*srcFormat*/, dstFormat.get()); EXPECT_EQ(logCount(), 0); // Both formats null, should not log. logSession(now, Reason::FINISHED, AMEDIA_OK, nullptr /*srcFormat*/, nullptr /*dstFormat*/); EXPECT_EQ(logCount(), 0); // Destination format null (passthrough mode), should log. logSession(now, Reason::FINISHED, AMEDIA_OK, srcFormat.get(), nullptr /*dstFormat*/); EXPECT_EQ(logCount(), 1); validateLatestAtom(Reason::FINISHED, AMEDIA_OK, true /*passthrough*/); } TEST_F(TranscodingLoggerTest, TestAtomContentCorrectness) { ALOGD("TestAtomContentCorrectness"); auto now = std::chrono::steady_clock::now(); // Log and validate a failure. logSession(now, Reason::ERROR, AMEDIA_ERROR_MALFORMED); EXPECT_EQ(logCount(), 1); validateLatestAtom(Reason::ERROR, AMEDIA_ERROR_MALFORMED); // Log and validate a success. logSession(now, Reason::FINISHED, AMEDIA_OK); EXPECT_EQ(logCount(), 2); validateLatestAtom(Reason::FINISHED, AMEDIA_OK); } } // namespace android