| /* |
| * 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 <android-base/logging.h> |
| #include <gtest/gtest.h> |
| #include <media/NdkCommon.h> |
| #include <media/TranscodingLogger.h> |
| #include <statslog_media.h> |
| #include <utils/Log.h> |
| |
| #include <chrono> |
| |
| 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<AMediaFormat>(CreateSrcFormat(), &AMediaFormat_delete); |
| } |
| if (!mDstFormat) { |
| mDstFormat = std::shared_ptr<AMediaFormat>(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<int>(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<double> seconds{kDefaultTranscodeDuration}; |
| const int32_t transcoderFps = |
| static_cast<int32_t>(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<TranscodingLogger> mLogger; |
| std::vector<SessionEndedAtom> mLoggedAtoms; |
| |
| std::shared_ptr<AMediaFormat> mSrcFormat; |
| std::shared_ptr<AMediaFormat> 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<AMediaFormat>(CreateSrcFormat(), &AMediaFormat_delete); |
| auto dstFormat = std::shared_ptr<AMediaFormat>(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 |