1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <fstream>
17 #include "hcodec.h"
18 #include "hcodec_log.h"
19 #include "hcodec_utils.h"
20
21 namespace OHOS::MediaAVCodec {
22 using namespace std;
23
PrintAllBufferInfo()24 void HCodec::PrintAllBufferInfo()
25 {
26 HLOGI("------------INPUT-----------");
27 for (const BufferInfo& info : inputBufferPool_) {
28 HLOGI("inBufId = %{public}u, owner = %{public}s", info.bufferId, ToString(info.owner));
29 }
30 HLOGI("----------------------------");
31 HLOGI("------------OUTPUT----------");
32 for (const BufferInfo& info : outputBufferPool_) {
33 HLOGI("outBufId = %{public}u, owner = %{public}s", info.bufferId, ToString(info.owner));
34 }
35 HLOGI("----------------------------");
36 }
37
CountOwner(bool isInput)38 std::array<uint32_t, HCodec::OWNER_CNT> HCodec::CountOwner(bool isInput)
39 {
40 std::array<uint32_t, OWNER_CNT> arr;
41 arr.fill(0);
42 const vector<BufferInfo>& pool = isInput ? inputBufferPool_ : outputBufferPool_;
43 for (const BufferInfo &info : pool) {
44 arr[info.owner]++;
45 }
46 return arr;
47 }
48
ChangeOwner(BufferInfo & info,BufferOwner newOwner)49 void HCodec::ChangeOwner(BufferInfo& info, BufferOwner newOwner)
50 {
51 if (!debugMode_) {
52 info.owner = newOwner;
53 return;
54 }
55 BufferOwner oldOwner = info.owner;
56 const char* oldOwnerStr = ToString(oldOwner);
57 const char* newOwnerStr = ToString(newOwner);
58 const char* idStr = info.isInput ? "inBufId" : "outBufId";
59
60 // calculate hold time
61 auto now = chrono::steady_clock::now();
62 uint64_t holdUs = chrono::duration_cast<chrono::microseconds>(now - info.lastOwnerChangeTime).count();
63 double holdMs = holdUs / US_TO_MS;
64 TotalCntAndCost& holdRecord = info.isInput ? inputHoldTimeRecord_[oldOwner][newOwner] :
65 outputHoldTimeRecord_[oldOwner][newOwner];
66 holdRecord.totalCnt++;
67 holdRecord.totalCostUs += holdUs;
68 double aveHoldMs = holdRecord.totalCostUs / US_TO_MS / holdRecord.totalCnt;
69
70 // now change owner
71 info.lastOwnerChangeTime = now;
72 info.owner = newOwner;
73 std::array<uint32_t, OWNER_CNT> arr = CountOwner(info.isInput);
74 HLOGI("%{public}s = %{public}u, after hold %{public}.1f ms (%{public}.1f ms), %{public}s -> %{public}s, "
75 "%{public}u/%{public}u/%{public}u/%{public}u",
76 idStr, info.bufferId, holdMs, aveHoldMs, oldOwnerStr, newOwnerStr,
77 arr[OWNED_BY_US], arr[OWNED_BY_USER], arr[OWNED_BY_OMX], arr[OWNED_BY_SURFACE]);
78
79 if (info.isInput && oldOwner == OWNED_BY_US && newOwner == OWNED_BY_OMX) {
80 UpdateInputRecord(info, now);
81 }
82 if (!info.isInput && oldOwner == OWNED_BY_OMX && newOwner == OWNED_BY_US) {
83 UpdateOutputRecord(info, now);
84 }
85 }
86
UpdateInputRecord(const BufferInfo & info,std::chrono::time_point<std::chrono::steady_clock> now)87 void HCodec::UpdateInputRecord(const BufferInfo& info, std::chrono::time_point<std::chrono::steady_clock> now)
88 {
89 if (!info.IsValidFrame()) {
90 return;
91 }
92 inTimeMap_[info.omxBuffer->pts] = now;
93 if (inTotalCnt_ == 0) {
94 firstInTime_ = now;
95 }
96 inTotalCnt_++;
97
98 uint64_t fromFirstInToNow = chrono::duration_cast<chrono::microseconds>(now - firstInTime_).count();
99 if (fromFirstInToNow == 0) {
100 HLOGI("pts = %{public}" PRId64 ", len = %{public}u, flags = 0x%{public}x",
101 info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag);
102 } else {
103 double inFps = inTotalCnt_ * US_TO_S / fromFirstInToNow;
104 HLOGI("pts = %{public}" PRId64 ", len = %{public}u, flags = 0x%{public}x, in fps %{public}.2f",
105 info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag, inFps);
106 }
107 }
108
UpdateOutputRecord(const BufferInfo & info,std::chrono::time_point<std::chrono::steady_clock> now)109 void HCodec::UpdateOutputRecord(const BufferInfo& info, std::chrono::time_point<std::chrono::steady_clock> now)
110 {
111 if (!info.IsValidFrame()) {
112 return;
113 }
114 auto it = inTimeMap_.find(info.omxBuffer->pts);
115 if (it == inTimeMap_.end()) {
116 return;
117 }
118 if (outRecord_.totalCnt == 0) {
119 firstOutTime_ = now;
120 }
121 outRecord_.totalCnt++;
122
123 uint64_t fromInToOut = chrono::duration_cast<chrono::microseconds>(now - it->second).count();
124 inTimeMap_.erase(it);
125 outRecord_.totalCostUs += fromInToOut;
126 double oneFrameCostMs = fromInToOut / US_TO_MS;
127 double averageCostMs = outRecord_.totalCostUs / US_TO_MS / outRecord_.totalCnt;
128
129 uint64_t fromFirstOutToNow = chrono::duration_cast<chrono::microseconds>(now - firstOutTime_).count();
130 if (fromFirstOutToNow == 0) {
131 HLOGI("pts = %{public}" PRId64 ", len = %{public}u, flags = 0x%{public}x, "
132 "cost %{public}.2f ms (%{public}.2f ms)",
133 info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag,
134 oneFrameCostMs, averageCostMs);
135 } else {
136 double outFps = outRecord_.totalCnt * US_TO_S / fromFirstOutToNow;
137 HLOGI("pts = %{public}" PRId64 ", len = %{public}u, flags = 0x%{public}x, "
138 "cost %{public}.2f ms (%{public}.2f ms), out fps %{public}.2f",
139 info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag,
140 oneFrameCostMs, averageCostMs, outFps);
141 }
142 }
143
IsValidFrame() const144 bool HCodec::BufferInfo::IsValidFrame() const
145 {
146 if (omxBuffer->flag & OMX_BUFFERFLAG_EOS) {
147 return false;
148 }
149 if (omxBuffer->flag & OMX_BUFFERFLAG_CODECCONFIG) {
150 return false;
151 }
152 if (omxBuffer->filledLen == 0) {
153 return false;
154 }
155 return true;
156 }
157
Dump(const string & prefix,DumpMode dumpMode,bool isEncoder) const158 void HCodec::BufferInfo::Dump(const string& prefix, DumpMode dumpMode, bool isEncoder) const
159 {
160 if (isInput) {
161 if (((dumpMode & DUMP_ENCODER_INPUT) && isEncoder) ||
162 ((dumpMode & DUMP_DECODER_INPUT) && !isEncoder)) {
163 Dump(prefix + "_Input");
164 }
165 } else {
166 if (((dumpMode & DUMP_ENCODER_OUTPUT) && isEncoder) ||
167 ((dumpMode & DUMP_DECODER_OUTPUT) && !isEncoder)) {
168 Dump(prefix + "_Output");
169 }
170 }
171 }
172
Dump(const string & prefix) const173 void HCodec::BufferInfo::Dump(const string& prefix) const
174 {
175 if (surfaceBuffer) {
176 DumpSurfaceBuffer(prefix);
177 } else {
178 DumpLinearBuffer(prefix);
179 }
180 }
181
DumpSurfaceBuffer(const std::string & prefix) const182 void HCodec::BufferInfo::DumpSurfaceBuffer(const std::string& prefix) const
183 {
184 const char* va = reinterpret_cast<const char*>(surfaceBuffer->GetVirAddr());
185 if (va == nullptr) {
186 LOGW("surface buffer has null va");
187 return;
188 }
189 bool eos = (omxBuffer->flag & OMX_BUFFERFLAG_EOS);
190 if (eos || omxBuffer->filledLen == 0) {
191 return;
192 }
193 int w = surfaceBuffer->GetWidth();
194 int h = surfaceBuffer->GetHeight();
195 int alignedW = surfaceBuffer->GetStride();
196 uint32_t totalSize = surfaceBuffer->GetSize();
197 if (w <= 0 || h <= 0 || alignedW <= 0 || w > alignedW) {
198 LOGW("invalid buffer dimension");
199 return;
200 }
201 std::optional<PixelFmt> fmt = TypeConverter::GraphicFmtToFmt(
202 static_cast<GraphicPixelFormat>(surfaceBuffer->GetFormat()));
203 if (fmt == nullopt) {
204 LOGW("invalid fmt=%{public}d", surfaceBuffer->GetFormat());
205 return;
206 }
207 optional<uint32_t> assumeAlignedH;
208 string suffix;
209 bool dumpAsVideo = true; // we could only save it as individual image if we don't know aligned height
210 DecideDumpInfo(assumeAlignedH, suffix, dumpAsVideo);
211
212 char name[128];
213 int ret = 0;
214 if (dumpAsVideo) {
215 ret = sprintf_s(name, sizeof(name), "%s/%s_%dx%d(%dx%d)_fmt%s.%s",
216 DUMP_PATH, prefix.c_str(), w, h, alignedW, assumeAlignedH.value_or(h),
217 fmt->strFmt.c_str(), suffix.c_str());
218 } else {
219 ret = sprintf_s(name, sizeof(name), "%s/%s_%dx%d(%d)_fmt%s_pts%" PRId64 ".%s",
220 DUMP_PATH, prefix.c_str(), w, h, alignedW, fmt->strFmt.c_str(), omxBuffer->pts, suffix.c_str());
221 }
222 if (ret > 0) {
223 ofstream ofs(name, ios::binary | ios::app);
224 if (ofs.is_open()) {
225 ofs.write(va, totalSize);
226 } else {
227 LOGW("cannot open %{public}s", name);
228 }
229 }
230 // if we unmap here, flush cache will fail
231 }
232
DecideDumpInfo(optional<uint32_t> & assumeAlignedH,string & suffix,bool & dumpAsVideo) const233 void HCodec::BufferInfo::DecideDumpInfo(optional<uint32_t>& assumeAlignedH, string& suffix, bool& dumpAsVideo) const
234 {
235 int h = surfaceBuffer->GetHeight();
236 int alignedW = surfaceBuffer->GetStride();
237 if (alignedW <= 0) {
238 return;
239 }
240 uint32_t totalSize = surfaceBuffer->GetSize();
241 GraphicPixelFormat fmt = static_cast<GraphicPixelFormat>(surfaceBuffer->GetFormat());
242 switch (fmt) {
243 case GRAPHIC_PIXEL_FMT_YCBCR_420_P:
244 case GRAPHIC_PIXEL_FMT_YCRCB_420_SP:
245 case GRAPHIC_PIXEL_FMT_YCBCR_420_SP:
246 case GRAPHIC_PIXEL_FMT_YCBCR_P010:
247 case GRAPHIC_PIXEL_FMT_YCRCB_P010: {
248 suffix = "yuv";
249 if (GetYuv420Size(alignedW, h) == totalSize) {
250 break;
251 }
252 uint32_t alignedH = totalSize * 2 / 3 / alignedW; // 2 bytes per pixel for UV, 3 bytes per pixel for YUV
253 if (GetYuv420Size(alignedW, alignedH) == totalSize) {
254 dumpAsVideo = true;
255 assumeAlignedH = alignedH;
256 } else {
257 dumpAsVideo = false;
258 }
259 break;
260 }
261 case GRAPHIC_PIXEL_FMT_RGBA_8888: {
262 suffix = "rgba";
263 if (static_cast<uint32_t>(alignedW * h) != totalSize) {
264 dumpAsVideo = false;
265 }
266 break;
267 }
268 default: {
269 suffix = "bin";
270 dumpAsVideo = false;
271 break;
272 }
273 }
274 }
275
DumpLinearBuffer(const string & prefix) const276 void HCodec::BufferInfo::DumpLinearBuffer(const string& prefix) const
277 {
278 if (avBuffer == nullptr || avBuffer->memory_ == nullptr) {
279 LOGW("invalid avbuffer");
280 return;
281 }
282 const char* va = reinterpret_cast<const char*>(avBuffer->memory_->GetAddr());
283 if (va == nullptr) {
284 LOGW("null va");
285 return;
286 }
287 bool eos = (omxBuffer->flag & OMX_BUFFERFLAG_EOS);
288 if (eos || omxBuffer->filledLen == 0) {
289 return;
290 }
291
292 char name[128];
293 int ret = sprintf_s(name, sizeof(name), "%s/%s.bin", DUMP_PATH, prefix.c_str());
294 if (ret <= 0) {
295 LOGW("sprintf_s failed");
296 return;
297 }
298 ofstream ofs(name, ios::binary | ios::app);
299 if (ofs.is_open()) {
300 ofs.write(va, omxBuffer->filledLen);
301 } else {
302 LOGW("cannot open %{public}s", name);
303 }
304 }
305 }