1 /*
2  * Copyright (C) 2021 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 // #define LOG_NDEBUG 0
18 #define LOG_TAG "TranscodingLogger"
19 
20 #include <media/NdkCommon.h>
21 #include <media/TranscodingLogger.h>
22 #include <statslog_media.h>
23 #include <utils/Log.h>
24 
25 #include <cmath>
26 #include <string>
27 
28 namespace android {
29 
30 static_assert(TranscodingLogger::UNKNOWN ==
31                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__UNKNOWN,
32               "Session event mismatch");
33 static_assert(TranscodingLogger::FINISHED ==
34                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__FINISHED,
35               "Session event mismatch");
36 static_assert(TranscodingLogger::ERROR ==
37                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__ERROR,
38               "Session event mismatch");
39 static_assert(TranscodingLogger::PAUSED ==
40                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__PAUSED,
41               "Session event mismatch");
42 static_assert(TranscodingLogger::CANCELLED ==
43                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CANCELLED,
44               "Session event mismatch");
45 static_assert(TranscodingLogger::START_FAILED ==
46                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__START_FAILED,
47               "Session event mismatch");
48 static_assert(TranscodingLogger::RESUME_FAILED ==
49                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__RESUME_FAILED,
50               "Session event mismatch");
51 static_assert(TranscodingLogger::CREATE_FAILED ==
52                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CREATE_FAILED,
53               "Session event mismatch");
54 static_assert(
55         TranscodingLogger::CONFIG_SRC_FAILED ==
56                 android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_SRC_FAILED,
57         "Session event mismatch");
58 static_assert(
59         TranscodingLogger::CONFIG_DST_FAILED ==
60                 android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_DST_FAILED,
61         "Session event mismatch");
62 static_assert(
63         TranscodingLogger::CONFIG_TRACK_FAILED ==
64                 android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_TRACK_FAILED,
65         "Session event mismatch");
66 static_assert(
67         TranscodingLogger::OPEN_SRC_FD_FAILED ==
68                 android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__OPEN_SRC_FD_FAILED,
69         "Session event mismatch");
70 static_assert(
71         TranscodingLogger::OPEN_DST_FD_FAILED ==
72                 android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__OPEN_DST_FD_FAILED,
73         "Session event mismatch");
74 static_assert(TranscodingLogger::NO_TRACKS ==
75                       android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__NO_TRACKS,
76               "Session event mismatch");
77 
getInt32(AMediaFormat * fmt,const char * key,int32_t defaultValue=-1)78 static inline int32_t getInt32(AMediaFormat* fmt, const char* key, int32_t defaultValue = -1) {
79     int32_t value;
80     if (fmt == nullptr || !AMediaFormat_getInt32(fmt, key, &value)) {
81         ALOGW("Unable to get %s", key);
82         value = defaultValue;
83     }
84     return value;
85 }
86 
87 // Note: returned string is owned by format and only valid until the next getString.
getString(AMediaFormat * fmt,const char * key,const char * defaultValue="(null)")88 static inline const char* getString(AMediaFormat* fmt, const char* key,
89                                     const char* defaultValue = "(null)") {
90     const char* value;
91     if (fmt == nullptr || !AMediaFormat_getString(fmt, key, &value)) {
92         ALOGW("Unable to get %s", key);
93         value = defaultValue;
94     }
95     return value;
96 }
97 
TranscodingLogger()98 TranscodingLogger::TranscodingLogger()
99       : mSessionEndedAtomWriter(&android::media::stats::stats_write) {}
100 
logSessionEnded(enum SessionEndedReason reason,uid_t callingUid,int status,std::chrono::microseconds duration,AMediaFormat * srcFormat,AMediaFormat * dstFormat)101 void TranscodingLogger::logSessionEnded(enum SessionEndedReason reason, uid_t callingUid,
102                                         int status, std::chrono::microseconds duration,
103                                         AMediaFormat* srcFormat, AMediaFormat* dstFormat) {
104     logSessionEnded(std::chrono::steady_clock::now(), reason, callingUid, status, duration,
105                     srcFormat, dstFormat);
106 }
107 
logSessionEnded(const std::chrono::steady_clock::time_point & now,enum SessionEndedReason reason,uid_t callingUid,int status,std::chrono::microseconds duration,AMediaFormat * srcFormat,AMediaFormat * dstFormat)108 void TranscodingLogger::logSessionEnded(const std::chrono::steady_clock::time_point& now,
109                                         enum SessionEndedReason reason, uid_t callingUid,
110                                         int status, std::chrono::microseconds duration,
111                                         AMediaFormat* srcFormat, AMediaFormat* dstFormat) {
112     if (srcFormat == nullptr) {
113         ALOGE("Source format is null. Dropping event.");
114         return;
115     }
116 
117     if (!shouldLogAtom(now, status)) {
118         ALOGD("Maximum logged event count reached. Dropping event.");
119         return;
120     }
121 
122     // Extract the pieces of information to log.
123     const int32_t srcWidth = getInt32(srcFormat, AMEDIAFORMAT_KEY_WIDTH);
124     const int32_t srcHeight = getInt32(srcFormat, AMEDIAFORMAT_KEY_HEIGHT);
125     const char* srcMime = getString(srcFormat, AMEDIAFORMAT_KEY_MIME);
126     const int32_t srcProfile = getInt32(srcFormat, AMEDIAFORMAT_KEY_PROFILE);
127     const int32_t srcLevel = getInt32(srcFormat, AMEDIAFORMAT_KEY_LEVEL);
128     const int32_t srcFrameRate = getInt32(srcFormat, AMEDIAFORMAT_KEY_FRAME_RATE);
129     const int32_t srcFrameCount = getInt32(srcFormat, AMEDIAFORMAT_KEY_FRAME_COUNT);
130     const bool srcIsHdr = AMediaFormatUtils::VideoIsHdr(srcFormat);
131 
132     int32_t dstWidth = getInt32(dstFormat, AMEDIAFORMAT_KEY_WIDTH, srcWidth);
133     int32_t dstHeight = getInt32(dstFormat, AMEDIAFORMAT_KEY_HEIGHT, srcHeight);
134     const char* dstMime = dstFormat == nullptr
135                                   ? "passthrough"
136                                   : getString(dstFormat, AMEDIAFORMAT_KEY_MIME, srcMime);
137     const bool dstIsHdr = false;  // Transcoder always request SDR output.
138 
139     int64_t tmpDurationUs;
140     const int32_t srcDurationMs =
141             AMediaFormat_getInt64(srcFormat, AMEDIAFORMAT_KEY_DURATION, &tmpDurationUs)
142                     ? static_cast<int32_t>(tmpDurationUs / 1000)
143                     : -1;
144 
145     int32_t transcodeFrameRate = -1;
146     if (status == 0 && srcFrameCount > 0 && duration.count() > 0) {
147         std::chrono::duration<double> seconds{duration};
148         transcodeFrameRate = static_cast<int32_t>(
149                 std::round(static_cast<double>(srcFrameCount) / seconds.count()));
150     }
151 
152     // Write the atom.
153     mSessionEndedAtomWriter(android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED,
154                             static_cast<int>(reason), callingUid, status, transcodeFrameRate,
155                             srcWidth, srcHeight, srcMime, srcProfile, srcLevel, srcFrameRate,
156                             srcDurationMs, srcIsHdr, dstWidth, dstHeight, dstMime, dstIsHdr);
157 }
158 
shouldLogAtom(const std::chrono::steady_clock::time_point & now,int status)159 bool TranscodingLogger::shouldLogAtom(const std::chrono::steady_clock::time_point& now,
160                                       int status) {
161     std::scoped_lock lock{mLock};
162     static const std::chrono::hours oneDay(24);
163 
164     // Remove events older than one day.
165     while (mLastLoggedAtoms.size() > 0 && (now - mLastLoggedAtoms.front().first) >= oneDay) {
166         if (mLastLoggedAtoms.front().second == AMEDIA_OK) {
167             --mSuccessfulCount;
168         }
169         mLastLoggedAtoms.pop();
170     }
171 
172     // Don't log if maximum number of events is reached.
173     if (mLastLoggedAtoms.size() >= kMaxAtomsPerDay) {
174         return false;
175     }
176 
177     // Don't log if the event is successful and the maximum number of successful events is reached.
178     if (status == AMEDIA_OK && mSuccessfulCount >= kMaxSuccessfulAtomsPerDay) {
179         return false;
180     }
181 
182     // Record the event.
183     if (status == AMEDIA_OK) {
184         ++mSuccessfulCount;
185     }
186     mLastLoggedAtoms.emplace(now, status);
187     return true;
188 }
189 
setSessionEndedAtomWriter(const SessionEndedAtomWriter & writer)190 void TranscodingLogger::setSessionEndedAtomWriter(const SessionEndedAtomWriter& writer) {
191     mSessionEndedAtomWriter = writer;
192 }
193 
194 }  // namespace android
195