1 /*
2 * Copyright (c) 2025 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 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 <dirent.h>
17 #include <filesystem>
18 #include <fstream>
19 #include <iostream>
20 #include <chrono>
21 #include <map>
22 #include <cstdio>
23 #include <cinttypes>
24 #include <securec.h>
25 #include <sys/mman.h>
26 #include <sys/stat.h>
27 #include "surface_buffer.h"
28 #include "v2_0/cm_color_space.h"
29 #include "v2_0/buffer_handle_meta_key_type.h"
30 #include "log.h"
31 #include "codec_heif_decode_helper.h"
32
33 namespace OHOS::VDI::HEIF {
34 using namespace std;
35 using namespace OHOS::HDI::Codec::Image::V2_1;
36 using namespace OHOS::HDI::Display::Graphic::Common::V2_0;
37
SplitString(const std::string & src,char sep,std::vector<std::string> & vec)38 void HeifDecoderHelper::InputParser::SplitString(const std::string& src, char sep, std::vector<std::string>& vec)
39 {
40 vec.clear();
41 string::size_type startPos = 0;
42 while (true) {
43 string::size_type endPos = src.find_first_of(sep, startPos);
44 if (endPos == string::npos) {
45 break;
46 }
47 vec.emplace_back(src.substr(startPos, endPos - startPos));
48 startPos = endPos + 1;
49 }
50 if (startPos != string::npos) {
51 vec.emplace_back(src.substr(startPos));
52 }
53 }
54
JoinPath(const std::string & base,const std::string & append)55 std::string HeifDecoderHelper::InputParser::JoinPath(const std::string& base, const std::string& append)
56 {
57 return (filesystem::path(base) / append).string();
58 }
59
ParseGridInfo(GridInfo & gridInfo)60 bool HeifDecoderHelper::InputParser::ParseGridInfo(GridInfo& gridInfo)
61 {
62 // source_ demo:
63 // 1. has grid: 3072x4096_grid_512x512_6x8
64 // 2. no grid: 3072x4096_nogrid
65 string baseDir = filesystem::path(source_).filename().string();
66 vector<string> vec;
67 SplitString(baseDir, MAIN_SEP, vec);
68 IF_TRUE_RETURN_VAL_WITH_MSG(vec.size() < MIN_MAIN_SEG_CNT, false,
69 "invalid source: %{public}s", source_.c_str());
70
71 vector<string> vecTmp;
72 SplitString(vec[DISPLAY_SIZE], SUB_SEP, vecTmp);
73 IF_TRUE_RETURN_VAL_WITH_MSG(vecTmp.size() != SUB_SEG_CNT, false, "invalid source: %{public}s", source_.c_str());
74 gridInfo.displayWidth = static_cast<uint32_t>(stol(vecTmp[HORIZONTAL].c_str()));
75 gridInfo.displayHeight = static_cast<uint32_t>(stol(vecTmp[VERTICAL].c_str()));
76
77 if (vec[GRID_FLAG].find(NO_GRID_INDICATOR) != string::npos) {
78 gridInfo.enableGrid = false;
79 gridInfo.cols = 1;
80 gridInfo.rows = 1;
81 gridInfo.tileWidth = gridInfo.displayWidth;
82 gridInfo.tileHeight = gridInfo.displayHeight;
83 } else {
84 IF_TRUE_RETURN_VAL_WITH_MSG(vec.size() < MAX_MAIN_SEG_CNT, false,
85 "invalid source: %{public}s", source_.c_str());
86
87 gridInfo.enableGrid = true;
88
89 SplitString(vec[TILE_SIZE], SUB_SEP, vecTmp);
90 IF_TRUE_RETURN_VAL_WITH_MSG(vecTmp.size() != SUB_SEG_CNT, false,
91 "invalid source: %{public}s", source_.c_str());
92 gridInfo.tileWidth = static_cast<uint32_t>(stol(vecTmp[HORIZONTAL].c_str()));
93 gridInfo.tileHeight = static_cast<uint32_t>(stol(vecTmp[VERTICAL].c_str()));
94
95 SplitString(vec[GRID_SIZE], SUB_SEP, vecTmp);
96 IF_TRUE_RETURN_VAL_WITH_MSG(vecTmp.size() != SUB_SEG_CNT, false,
97 "invalid source: %{public}s", source_.c_str());
98 gridInfo.cols = static_cast<uint32_t>(stol(vecTmp[HORIZONTAL].c_str()));
99 gridInfo.rows = static_cast<uint32_t>(stol(vecTmp[VERTICAL].c_str()));
100 }
101 return true;
102 }
103
FindXpsAndIFrameFile()104 void HeifDecoderHelper::InputParser::FindXpsAndIFrameFile()
105 {
106 DIR *dirp = opendir(source_.c_str());
107 IF_TRUE_RETURN_WITH_MSG(dirp == nullptr, "failed to open: %{public}s, errno=%{public}d",
108 source_.c_str(), errno);
109 struct dirent *dp;
110 while ((dp = readdir(dirp)) != nullptr) {
111 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
112 continue;
113 }
114 string path = JoinPath(source_, dp->d_name);
115 struct stat st{};
116 if (stat(path.c_str(), &st) != 0 || !S_ISREG(st.st_mode)) {
117 continue;
118 }
119 string fileName(dp->d_name);
120 if (fileName.find(XPS_INDICATOR) != string::npos) {
121 xpsFile_ = path;
122 } else if (fileName.find(I_FRAME_INDICATOR) != string::npos) {
123 iFrameFile_.emplace_back(path);
124 }
125 }
126 closedir(dirp);
127 }
128
ReadFileToAshmem(const string & filePath,vector<sptr<Ashmem>> & inputs)129 bool HeifDecoderHelper::InputParser::ReadFileToAshmem(const string& filePath, vector<sptr<Ashmem>>& inputs)
130 {
131 ifstream ifs(filePath, ios::binary);
132 IF_TRUE_RETURN_VAL_WITH_MSG(!ifs.is_open(), false, "failed to open file: %{public}s", filePath.c_str());
133
134 ifs.seekg(0, ifstream::end);
135 size_t fileSize = static_cast<size_t>(ifs.tellg());
136 ifs.seekg(0, ifstream::beg);
137
138 sptr<Ashmem> ashmem = Ashmem::CreateAshmem(filePath.c_str(), static_cast<int32_t>(fileSize));
139 IF_TRUE_RETURN_VAL_WITH_MSG(ashmem == nullptr, false, "failed to create ashmem for %{public}s, size(%{public}zu)",
140 filePath.c_str(), fileSize);
141 int fd = ashmem->GetAshmemFd();
142 void* addr = ::mmap(nullptr, static_cast<int32_t>(fileSize), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
143 IF_TRUE_RETURN_VAL_WITH_MSG(addr == nullptr, false, "failed to map ashmem for %{public}s, size(%{public}zu)",
144 filePath.c_str(), fileSize);
145 ifs.read(reinterpret_cast<char*>(addr), static_cast<streamsize>(fileSize));
146 ifs.close();
147 ::munmap(addr, static_cast<int32_t>(fileSize));
148 inputs.emplace_back(ashmem);
149 return true;
150 }
151
ExtractIFrameNum(const string & filePath)152 int HeifDecoderHelper::InputParser::ExtractIFrameNum(const string& filePath)
153 {
154 string fileName = filesystem::path(filePath).filename().string();
155 string::size_type pos = fileName.find(I_FRAME_INDICATOR);
156 if (pos == string::npos) {
157 return -1;
158 }
159 return stoi(fileName.substr(pos + string(I_FRAME_INDICATOR).size()));
160 }
161
ReadInput(vector<sptr<Ashmem>> & inputs)162 bool HeifDecoderHelper::InputParser::ReadInput(vector<sptr<Ashmem>>& inputs)
163 {
164 FindXpsAndIFrameFile();
165 IF_TRUE_RETURN_VAL_WITH_MSG(xpsFile_.empty(), false, "no xps file in %{public}s", source_.c_str());
166 IF_TRUE_RETURN_VAL_WITH_MSG(iFrameFile_.empty(), false, "no iframe file in %{public}s", source_.c_str());
167
168 IF_TRUE_RETURN_VAL_WITH_MSG(!ReadFileToAshmem(xpsFile_, inputs), false,
169 "failed to read xps file: %{public}s", xpsFile_.c_str());
170 std::sort(iFrameFile_.begin(), iFrameFile_.end(), [](const string& a, const string& b) {
171 return ExtractIFrameNum(a) < ExtractIFrameNum(b);
172 });
173 for (const string& one : iFrameFile_) {
174 IF_TRUE_RETURN_VAL_WITH_MSG(!ReadFileToAshmem(one, inputs), false,
175 "failed to read iframe file: %{public}s", one.c_str());
176 }
177 return true;
178 }
179
IsValueInRange(uint32_t value,uint32_t maxValue,uint32_t minValue)180 static bool IsValueInRange(uint32_t value, uint32_t maxValue, uint32_t minValue)
181 {
182 return (value >= minValue) && (value <= maxValue);
183 }
184
IsHeifHardwareDecodeSupported(sptr<ICodecImage> & hdiHeifDecoder)185 bool HeifDecoderHelper::IsHeifHardwareDecodeSupported(sptr<ICodecImage>& hdiHeifDecoder)
186 {
187 std::vector<CodecImageCapability> capList;
188 auto ret = hdiHeifDecoder->GetImageCapability(capList);
189 IF_TRUE_RETURN_VAL(ret != HDF_SUCCESS, false);
190 for (const CodecImageCapability& one : capList) {
191 if (one.role != CODEC_IMAGE_HEIF || one.type != CODEC_IMAGE_TYPE_DECODER) {
192 continue;
193 }
194 uint32_t widthToCheck = decInfo_.gridInfo.enableGrid ?
195 decInfo_.gridInfo.tileWidth :
196 decInfo_.gridInfo.displayWidth;
197 uint32_t heightToCheck = decInfo_.gridInfo.enableGrid ?
198 decInfo_.gridInfo.tileHeight :
199 decInfo_.gridInfo.displayHeight;
200 if (IsValueInRange(widthToCheck, one.maxWidth, one.minWidth) &&
201 IsValueInRange(heightToCheck, one.maxHeight, one.minHeight)) {
202 return true;
203 }
204 if (IsValueInRange(widthToCheck, one.maxHeight, one.minHeight) &&
205 IsValueInRange(heightToCheck, one.maxWidth, one.minWidth)) {
206 return true;
207 }
208 }
209 return false;
210 }
211
DoDecode()212 void HeifDecoderHelper::DoDecode()
213 {
214 cout << "start heif decode" << endl;
215 IF_TRUE_RETURN_WITH_MSG(!GetOutputFormat(), "failed to get output format");
216 std::vector<sptr<Ashmem>> inputs;
217 IF_TRUE_RETURN_WITH_MSG(!ReadInput(inputs), "failed to read input");
218 GetSampleSize();
219 sptr<NativeBuffer> output;
220 IF_TRUE_RETURN_WITH_MSG(!AllocateOutputBuffer(output), "failed to allocate output buffer");
221 sptr<ICodecImage> hdiHeifDecoder = ICodecImage::Get();
222 IF_TRUE_RETURN_WITH_MSG(hdiHeifDecoder == nullptr, "failed to get ICodecImage");
223 IF_TRUE_RETURN_WITH_MSG(!IsHeifHardwareDecodeSupported(hdiHeifDecoder), "heif hw decode not supported");
224 int32_t ret = hdiHeifDecoder->DoHeifDecode(inputs, output, decInfo_);
225 if (ret == HDF_SUCCESS) {
226 cout << "heif decode succeed" << endl;
227 DumpOutput(output);
228 } else {
229 cout << "heif decode failed" << endl;
230 }
231 }
232
GetOutputFormat()233 bool HeifDecoderHelper::GetOutputFormat()
234 {
235 static const map<UserPixelFormat, OHOS::HDI::Display::Composer::V1_2::PixelFormat> pixelFmtMap = {
236 { UserPixelFormat::NV12, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCBCR_420_SP },
237 { UserPixelFormat::NV21, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCRCB_420_SP },
238 { UserPixelFormat::NV12_10BIT, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCBCR_P010 },
239 { UserPixelFormat::NV21_10BIT, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCRCB_P010 },
240 { UserPixelFormat::RGBA8888, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGBA_8888 },
241 { UserPixelFormat::BGRA8888, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_BGRA_8888 },
242 { UserPixelFormat::RGB565, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGB_565 },
243 { UserPixelFormat::RGBA1010102, OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGBA_1010102 },
244 };
245 auto iterFmt = pixelFmtMap.find(decodeOpt_.pixelFmt);
246 IF_TRUE_RETURN_VAL_WITH_MSG(iterFmt == pixelFmtMap.end(), false,
247 "unsupported pixel format: %{public}d", static_cast<int>(decodeOpt_.pixelFmt));
248 outputFormat_.format = iterFmt->second;
249
250 static const map<OHOS::HDI::Display::Composer::V1_2::PixelFormat, string> pixelFmtDescMap = {
251 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCBCR_420_SP, "NV12" },
252 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCRCB_420_SP, "NV21" },
253 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCBCR_P010, "NV12_10BIT" },
254 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_YCRCB_P010, "NV21_10BIT" },
255 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGBA_8888, "RGBA8888" },
256 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_BGRA_8888, "BGRA8888" },
257 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGB_565, "RGB565" },
258 { OHOS::HDI::Display::Composer::V1_2::PIXEL_FMT_RGBA_1010102, "RGBA1010102"},
259 };
260 auto iterDesc = pixelFmtDescMap.find(outputFormat_.format);
261 IF_TRUE_RETURN_VAL_WITH_MSG(iterDesc == pixelFmtDescMap.end(), false,
262 "unsupported pixel format: %{public}d", static_cast<int>(decodeOpt_.pixelFmt));
263 outputFormat_.desc = iterDesc->second;
264 return true;
265 }
266
ReadInput(vector<sptr<Ashmem>> & inputs)267 bool HeifDecoderHelper::ReadInput(vector<sptr<Ashmem>>& inputs)
268 {
269 InputParser parser(decodeOpt_.inputPath);
270 bool ret = parser.ParseGridInfo(decInfo_.gridInfo);
271 ret = ret && parser.ReadInput(inputs);
272 return ret;
273 }
274
GetMetaDataInfo(CM_ColorSpaceInfo & colorSpaceInfo)275 void HeifDecoderHelper::GetMetaDataInfo(CM_ColorSpaceInfo& colorSpaceInfo)
276 {
277 colorSpaceInfo.range = (decodeOpt_.isLimitedRange)? RANGE_LIMITED : RANGE_FULL;
278 static const map<ColorSpace, CM_Matrix> colorSpaceDescMap = {
279 { ColorSpace::BT_601_P, MATRIX_BT601_P },
280 { ColorSpace::BT_601_N, MATRIX_BT601_N },
281 { ColorSpace::P3, MATRIX_P3 },
282 { ColorSpace::BT_709, MATRIX_BT709 },
283 { ColorSpace::BT_2020, MATRIX_BT2020 },
284 };
285
286 auto iterDesc = colorSpaceDescMap.find(decodeOpt_.colorSpace);
287 if (iterDesc == colorSpaceDescMap.end()) {
288 HDF_LOGE("unsupported colorSpace: %{public}d", static_cast<uint32_t>(decodeOpt_.colorSpace));
289 colorSpaceInfo.matrix = MATRIX_BT601_P;
290 return;
291 }
292
293 colorSpaceInfo.matrix = iterDesc->second;
294 return;
295 }
296
AllocateOutputBuffer(sptr<NativeBuffer> & output)297 bool HeifDecoderHelper::AllocateOutputBuffer(sptr<NativeBuffer>& output)
298 {
299 uint64_t usage = BUFFER_USAGE_CPU_READ |
300 BUFFER_USAGE_CPU_WRITE |
301 BUFFER_USAGE_MEM_DMA |
302 BUFFER_USAGE_MEM_MMZ_CACHE;
303
304 sptr<OHOS::SurfaceBuffer> surfaceBuffer = SurfaceBuffer::Create();
305 IF_TRUE_RETURN_VAL_WITH_MSG(surfaceBuffer == nullptr, false, "failed to create buffer\n");
306
307 BufferRequestConfig config = {
308 .width = decInfo_.gridInfo.displayWidth / decInfo_.sampleSize,
309 .height = decInfo_.gridInfo.displayHeight / decInfo_.sampleSize,
310 .strideAlignment = 32,
311 .format = outputFormat_.format,
312 .usage = usage,
313 .timeout = 0
314 };
315
316 GSError ret = surfaceBuffer->Alloc(config);
317 IF_TRUE_RETURN_VAL_WITH_MSG(ret != GSERROR_OK, false, "failed to alloc surfaceBuffer, ret=%{public}d\n", ret);
318
319 CM_ColorSpaceInfo colorSpaceInfo = {};
320 GetMetaDataInfo(colorSpaceInfo);
321 surfaceBuffer->SetMetadata(ATTRKEY_COLORSPACE_INFO, Pod2Vec(colorSpaceInfo));
322
323 output = new NativeBuffer(surfaceBuffer->GetBufferHandle());
324
325 return true;
326 }
327
GetTimestampInMs()328 static int64_t GetTimestampInMs()
329 {
330 auto now = chrono::steady_clock::now();
331 return chrono::duration_cast<chrono::milliseconds>(now.time_since_epoch()).count();
332 }
333
DumpOutput(sptr<NativeBuffer> & output)334 void HeifDecoderHelper::DumpOutput(sptr<NativeBuffer>& output)
335 {
336 cout << "dump heif decode result" << endl;
337 sptr<SurfaceBuffer> outputSurface = SurfaceBuffer::Create();
338 IF_TRUE_RETURN_WITH_MSG(outputSurface == nullptr, "output is null");
339 outputSurface->SetBufferHandle(output->Move());
340 int64_t timestamp = GetTimestampInMs();
341 char outputFilePath[MAX_PATH_LEN] = {0};
342 int ret = 0;
343 if (decInfo_.gridInfo.enableGrid) {
344 ret = sprintf_s(outputFilePath, sizeof(outputFilePath),
345 "%s/%ld_hdiout_%s_%u(%d)x%u(%d)_grid_%ux%u_%ux%u_s%u_m%d_r%d.bin",
346 DUMP_PATH, timestamp, outputFormat_.desc.c_str(),
347 decInfo_.gridInfo.displayWidth, outputSurface->GetStride(),
348 decInfo_.gridInfo.displayHeight, outputSurface->GetHeight(),
349 decInfo_.gridInfo.tileWidth, decInfo_.gridInfo.tileHeight,
350 decInfo_.gridInfo.cols, decInfo_.gridInfo.rows, decInfo_.sampleSize,
351 static_cast<int32_t>(decodeOpt_.colorSpace), decodeOpt_.isLimitedRange);
352 } else {
353 ret = sprintf_s(outputFilePath, sizeof(outputFilePath),
354 "%s/%ld_hdiout_%s_%u(%d)x%u(%d)_nogrid_s%u_m%d_r%d.bin",
355 DUMP_PATH, timestamp, outputFormat_.desc.c_str(),
356 decInfo_.gridInfo.displayWidth, outputSurface->GetStride(),
357 decInfo_.gridInfo.displayHeight, outputSurface->GetHeight(),
358 decInfo_.sampleSize, static_cast<int32_t>(decodeOpt_.colorSpace),
359 decodeOpt_.isLimitedRange);
360 }
361 if (ret == -1) {
362 HDF_LOGE("failed to create dump file");
363 return;
364 }
365 cout << "dump result to: " << outputFilePath << endl;
366
367 std::ofstream dumpOutFile;
368 dumpOutFile.open(std::string(outputFilePath), std::ios_base::binary | std::ios_base::trunc);
369 if (!dumpOutFile.is_open()) {
370 cout << "failed to dump decode result" << endl;
371 return;
372 }
373
374 GSError err = outputSurface->InvalidateCache();
375 if (err != GSERROR_OK) {
376 cout << "InvalidateCache failed, GSError=" << err << endl;
377 }
378 dumpOutFile.write(reinterpret_cast<char*>(outputSurface->GetVirAddr()), outputSurface->GetSize());
379 dumpOutFile.close();
380 }
381
GetSampleSize()382 void HeifDecoderHelper::GetSampleSize()
383 {
384 static const map<SampleSize, uint32_t> sampleSizeMap = {
385 { SampleSize::SAMPLE_SIZE_1, 1 },
386 { SampleSize::SAMPLE_SIZE_2, 2 },
387 { SampleSize::SAMPLE_SIZE_4, 4 },
388 { SampleSize::SAMPLE_SIZE_8, 8 },
389 { SampleSize::SAMPLE_SIZE_16, 16 },
390 };
391 auto iter = sampleSizeMap.find(decodeOpt_.sampleSize);
392 if (iter != sampleSizeMap.end()) {
393 decInfo_.sampleSize = iter->second;
394 } else {
395 decInfo_.sampleSize = 1;
396 }
397 }
398 }