1 /*
2 * Copyright (C) 2021 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 "avmetadatahelper_demo.h"
17 #include <iostream>
18 #include <string>
19 #include <string_view>
20 #include <sys/stat.h>
21 #include <vector>
22 #include <cstdint>
23 #include <cstdlib>
24 #include <cstdio>
25 #include <fstream>
26 #include "jpeglib.h"
27 #include "string_ex.h"
28 #include "securec.h"
29 #include "uri_helper.h"
30
31 namespace OHOS {
32 namespace Media {
33 static constexpr int32_t RGBA8888_PIXEL_BYTES = 4;
34 static constexpr int32_t RGB888_PIXEL_BYTES = 3;
35 static constexpr int32_t RGB565_PIXEL_BYTES = 2;
36 static constexpr uint16_t RGB565_MASK_RED = 0x001F;
37 static constexpr uint16_t RGB565_MASK_GREEN = 0x07E0;
38 static constexpr uint16_t RGB565_MASK_BLUE = 0xF800;
39 static constexpr uint32_t RGBA8888_MASK_RED = 0x00FF0000;
40 static constexpr uint32_t RGBA8888_MASK_GREEN = 0x0000FF00;
41 static constexpr uint32_t RGBA8888_MASK_BLUE = 0x000000FF;
42 static constexpr uint8_t SHIFT_2_BIT = 2;
43 static constexpr uint8_t SHITF_3_BIT = 3;
44 static constexpr uint8_t SHIFT_5_BIT = 5;
45 static constexpr uint8_t SHIFT_8_BIT = 8;
46 static constexpr uint8_t SHIFT_11_BIT = 11;
47 static constexpr uint8_t SHIFT_16_BIT = 16;
48 static constexpr uint8_t R_INDEX = 2;
49 static constexpr uint8_t G_INDEX = 1;
50 static constexpr uint8_t B_INDEX = 0;
51
52 #define AVMETA_KEY_TO_STRING_MAP_ITEM(key) { key, #key }
53 static const std::unordered_map<int32_t, std::string_view> AVMETA_KEY_TO_STRING_MAP = {
54 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_ALBUM),
55 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_ALBUM_ARTIST),
56 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_ARTIST),
57 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_AUTHOR),
58 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_COMPOSER),
59 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_DATE_TIME),
60 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_DATE_TIME_FORMAT),
61 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_DURATION),
62 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_GENRE),
63 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_HAS_AUDIO),
64 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_HAS_VIDEO),
65 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_MIME_TYPE),
66 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_NUM_TRACKS),
67 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_SAMPLE_RATE),
68 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_TITLE),
69 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_VIDEO_HEIGHT),
70 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_VIDEO_WIDTH),
71 AVMETA_KEY_TO_STRING_MAP_ITEM(AV_KEY_VIDEO_ORIENTATION),
72 };
73
74 static struct jpeg_compress_struct jpeg;
75 static struct jpeg_error_mgr jerr;
76
Rgb888ToJpeg(const std::string_view & filename,const uint8_t * rgbData,int32_t width,int32_t height)77 static int32_t Rgb888ToJpeg(const std::string_view &filename, const uint8_t *rgbData, int32_t width, int32_t height)
78 {
79 if (rgbData == nullptr) {
80 std::cout << "rgbData is nullptr" << std::endl;
81 return -1;
82 }
83
84 jpeg.err = jpeg_std_error(&jerr);
85 jpeg_create_compress(&jpeg);
86 jpeg.image_width = static_cast<uint32_t>(width);
87 jpeg.image_height = static_cast<uint32_t>(height);
88 jpeg.input_components = RGB888_PIXEL_BYTES;
89 jpeg.in_color_space = JCS_RGB;
90 jpeg_set_defaults(&jpeg);
91
92 static constexpr int32_t quality = 100;
93 jpeg_set_quality(&jpeg, quality, TRUE);
94
95 FILE *file = fopen(filename.data(), "wb");
96 if (file == nullptr) {
97 jpeg_destroy_compress(&jpeg);
98 return 0;
99 }
100
101 jpeg_stdio_dest(&jpeg, file);
102 jpeg_start_compress(&jpeg, TRUE);
103 JSAMPROW rowPointer[1];
104 for (uint32_t i = 0; i < jpeg.image_height; i++) {
105 rowPointer[0] = const_cast<uint8_t *>(rgbData + i * jpeg.image_width * RGB888_PIXEL_BYTES);
106 (void)jpeg_write_scanlines(&jpeg, rowPointer, 1);
107 }
108 jpeg_finish_compress(&jpeg);
109 (void)fclose(file);
110 file = nullptr;
111
112 jpeg_destroy_compress(&jpeg);
113 return 0;
114 }
115
116 // only valid for little-endian order.
RGB565ToRGB888(const uint16_t * rgb565Buf,int32_t rgb565Size,uint8_t * rgb888Buf,int32_t rgb888Size)117 static int32_t RGB565ToRGB888(const uint16_t *rgb565Buf, int32_t rgb565Size, uint8_t *rgb888Buf, int32_t rgb888Size)
118 {
119 if (rgb565Buf == nullptr || rgb565Size <= 0 || rgb888Buf == nullptr || rgb888Size <= 0) {
120 return -1;
121 }
122
123 if (rgb888Size < rgb565Size * RGB888_PIXEL_BYTES) {
124 return -1;
125 }
126
127 for (int32_t i = 0; i < rgb565Size; i++) {
128 rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgb565Buf[i] & RGB565_MASK_RED);
129 rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgb565Buf[i] & RGB565_MASK_GREEN) >> SHIFT_5_BIT;
130 rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = (rgb565Buf[i] & RGB565_MASK_BLUE) >> SHIFT_11_BIT;
131 rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] <<= SHITF_3_BIT;
132 rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] <<= SHIFT_2_BIT;
133 rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] <<= SHITF_3_BIT;
134 }
135
136 return 0;
137 }
138
RGBA8888ToRGB888(const uint32_t * rgba8888Buf,int32_t rgba8888Size,uint8_t * rgb888Buf,int32_t rgb888Size)139 static int32_t RGBA8888ToRGB888(const uint32_t *rgba8888Buf, int32_t rgba8888Size,
140 uint8_t *rgb888Buf, int32_t rgb888Size)
141 {
142 if (rgba8888Buf == nullptr || rgba8888Size <= 0 || rgb888Buf == nullptr || rgb888Size <= 0) {
143 return -1;
144 }
145
146 if (rgb888Size < rgba8888Size * RGB888_PIXEL_BYTES) {
147 return -1;
148 }
149
150 for (int32_t i = 0; i < rgba8888Size; i++) {
151 rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgba8888Buf[i] & RGBA8888_MASK_RED) >> SHIFT_16_BIT;
152 rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgba8888Buf[i] & RGBA8888_MASK_GREEN) >> SHIFT_8_BIT;
153 rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = rgba8888Buf[i] & RGBA8888_MASK_BLUE;
154 }
155
156 return 0;
157 }
158
StrToInt64(const std::string & str,int64_t & value)159 bool StrToInt64(const std::string &str, int64_t &value)
160 {
161 if (str.empty() || (!isdigit(str.front()) && (str.front() != '-'))) {
162 return false;
163 }
164
165 char *end = nullptr;
166 errno = 0;
167 auto addr = str.data();
168 auto result = strtoll(addr, &end, 10); /* 10 means decimal */
169 if ((end == addr) || (end[0] != '\0') || (errno == ERANGE)) {
170 std::cout << "call StrToInt func false, input str is: " << str << std::endl;
171 return false;
172 }
173
174 value = result;
175 return true;
176 }
177
TrimStr(const std::string_view & str,const char cTrim=' ')178 std::string_view TrimStr(const std::string_view& str, const char cTrim = ' ')
179 {
180 std::string_view strTmp = str.substr(str.find_first_not_of(cTrim));
181 strTmp = strTmp.substr(0, strTmp.find_last_not_of(cTrim) + sizeof(char));
182 return strTmp;
183 }
184
MySplitStr(const std::string_view & str,const std::string_view & sep,std::queue<std::string_view> & strs)185 void MySplitStr(const std::string_view& str, const std::string_view &sep, std::queue<std::string_view> &strs)
186 {
187 std::string_view strTmp = TrimStr(str);
188 std::string_view strPart;
189 while (true) {
190 std::string::size_type pos = strTmp.find(sep);
191 if (pos == std::string::npos || sep.empty()) {
192 strPart = TrimStr(strTmp);
193 if (!strPart.empty()) {
194 strs.push(strPart);
195 }
196 break;
197 } else {
198 strPart = TrimStr(strTmp.substr(0, pos));
199 if (!strPart.empty()) {
200 strs.push(strPart);
201 }
202 strTmp = strTmp.substr(sep.size() + pos, strTmp.size() - sep.size() - pos);
203 }
204 }
205 }
206
GetMetadata(std::queue<std::string_view> & options)207 void AVMetadataHelperDemo::GetMetadata(std::queue<std::string_view> &options)
208 {
209 if (options.empty()) {
210 std::unordered_map<int32_t, std::string> metadataMap = avMetadataHelper_->ResolveMetadata();
211 for (const auto &[key, value] : metadataMap) {
212 std::string keyPrettyStr = "unknown key";
213 if (AVMETA_KEY_TO_STRING_MAP.find(key) != AVMETA_KEY_TO_STRING_MAP.end()) {
214 keyPrettyStr = AVMETA_KEY_TO_STRING_MAP.at(key);
215 }
216 std::cout << "key: " << keyPrettyStr << " metadata: " << value.c_str() << std::endl;
217 }
218 } else {
219 std::string keyStr = std::string(options.front());
220 options.pop();
221
222 int32_t key = -1;
223 if (!StrToInt(keyStr, key) || key < 0) {
224 std::cout << "You need to configure the key parameter properly" << std::endl;
225 return;
226 }
227
228 std::string keyPrettyStr = "unknown key";
229 if (AVMETA_KEY_TO_STRING_MAP.find(key) != AVMETA_KEY_TO_STRING_MAP.end()) {
230 keyPrettyStr = AVMETA_KEY_TO_STRING_MAP.at(key);
231 }
232 std::string metadata = avMetadataHelper_->ResolveMetadata(key);
233 std::cout << "key: " << keyPrettyStr << ", metadata: " << metadata.c_str() << std::endl;
234 }
235 }
236
SaveRGB565Image(const std::shared_ptr<PixelMap> & frame,const std::string_view & filepath)237 static int32_t SaveRGB565Image(const std::shared_ptr<PixelMap> &frame, const std::string_view &filepath)
238 {
239 int32_t rgb888Size = (frame->GetByteCount() / RGB565_PIXEL_BYTES) * RGB888_PIXEL_BYTES;
240 uint8_t *rgb888 = new (std::nothrow) uint8_t[rgb888Size];
241 if (rgb888 == nullptr) {
242 std::cout << "alloc mem failed" << std::endl;
243 return -1;
244 }
245 const uint16_t *rgb565Data = reinterpret_cast<const uint16_t *>(frame->GetPixels());
246 int32_t ret = RGB565ToRGB888(rgb565Data, frame->GetByteCount() / RGB565_PIXEL_BYTES, rgb888, rgb888Size);
247 if (ret != 0) {
248 std::cout << "convert rgb565 to rgb888 failed" << std::endl;
249 delete [] rgb888;
250 return ret;
251 }
252
253 ret = Rgb888ToJpeg(filepath, rgb888, frame->GetWidth(), frame->GetHeight());
254 delete [] rgb888;
255
256 return ret;
257 }
258
SaveRGBA8888Image(const std::shared_ptr<PixelMap> & frame,const std::string_view & filepath)259 static int32_t SaveRGBA8888Image(const std::shared_ptr<PixelMap> &frame, const std::string_view &filepath)
260 {
261 int32_t rgb888Size = (frame->GetByteCount() / RGBA8888_PIXEL_BYTES) * RGB888_PIXEL_BYTES;
262 uint8_t *rgb888 = new (std::nothrow) uint8_t[rgb888Size];
263 if (rgb888 == nullptr) {
264 std::cout << "alloc mem failed" << std::endl;
265 return -1;
266 }
267 const uint32_t *rgba8888Data = reinterpret_cast<const uint32_t *>(frame->GetPixels());
268 int32_t ret = RGBA8888ToRGB888(rgba8888Data, frame->GetByteCount() / RGBA8888_PIXEL_BYTES, rgb888, rgb888Size);
269 if (ret != 0) {
270 std::cout << "convert rgba8888 to rgb888 failed" << std::endl;
271 delete [] rgb888;
272 return ret;
273 }
274
275 ret = Rgb888ToJpeg(filepath, rgb888, frame->GetWidth(), frame->GetHeight());
276 delete [] rgb888;
277
278 return ret;
279 }
280
DoFetchFrame(int64_t timeUs,int32_t queryOption,const PixelMapParams & param)281 void AVMetadataHelperDemo::DoFetchFrame(int64_t timeUs, int32_t queryOption, const PixelMapParams ¶m)
282 {
283 std::shared_ptr<PixelMap> frame = avMetadataHelper_->FetchFrameAtTime(timeUs, queryOption, param);
284 if (frame == nullptr) {
285 std::cout << "Fetch Frame failed" << std::endl;
286 return;
287 }
288
289 constexpr uint8_t maxFilePathLength = 255;
290 char filePath[maxFilePathLength];
291 auto ret = sprintf_s(filePath, maxFilePathLength,
292 "/data/media/test/time_%" PRIi64 "_option_%d_width_%d_height_%d_color_%d.jpg",
293 timeUs, queryOption, param.dstWidth, param.dstHeight, param.colorFormat);
294 if (ret <= 0) {
295 std::cout << "generate file path failed" << std::endl;
296 return;
297 }
298
299 if (param.colorFormat == PixelFormat::RGB_565) {
300 ret = SaveRGB565Image(frame, filePath);
301 } else if (param.colorFormat == PixelFormat::RGBA_8888) {
302 ret = SaveRGBA8888Image(frame, filePath);
303 } else if (param.colorFormat == PixelFormat::RGB_888) {
304 ret = Rgb888ToJpeg(filePath, frame->GetPixels(), frame->GetWidth(), frame->GetHeight());
305 } else {
306 std::cout << "invalid pixel format" << std::endl;
307 return;
308 }
309
310 if (ret != 0) {
311 std::cout << "pack image failed" << std::endl;
312 }
313 std::cout << "save to " << filePath << std::endl;
314 }
315
FetchFrame(std::queue<std::string_view> & options)316 void AVMetadataHelperDemo::FetchFrame(std::queue<std::string_view> &options)
317 {
318 PixelMapParams param;
319 int32_t queryOption = 0;
320 int64_t timeUs = 0;
321
322 while (!options.empty()) {
323 auto option = options.front();
324 options.pop();
325
326 std::queue<std::string_view> group;
327 MySplitStr(option, ":", group);
328
329 static const size_t OPTION_GROUP_ITEM_SIZE = 2;
330 if (group.size() != OPTION_GROUP_ITEM_SIZE) {
331 continue;
332 }
333
334 auto name = group.front();
335 auto val = group.back();
336
337 if (name.compare("time") == 0) {
338 (void)StrToInt64(std::string(val), timeUs);
339 continue;
340 }
341
342 if (name.compare("option") == 0) {
343 (void)StrToInt(std::string(val), queryOption);
344 continue;
345 }
346
347 if (name.compare("width") == 0) {
348 (void)StrToInt(std::string(val), param.dstWidth);
349 continue;
350 }
351
352 if (name.compare("height") == 0) {
353 (void)StrToInt(std::string(val), param.dstHeight);
354 continue;
355 }
356
357 if (name.compare("color") == 0) {
358 if (val.compare("rgb565") == 0) {
359 param.colorFormat = PixelFormat::RGB_565;
360 }
361 if (val.compare("rgb888") == 0) {
362 param.colorFormat = PixelFormat::RGB_888;
363 }
364 if (val.compare("rgba8888") == 0) {
365 param.colorFormat = PixelFormat::RGBA_8888;
366 }
367 continue;
368 }
369 }
370 std::cout << "time: " << timeUs << " option: " << queryOption << " width: " << param.dstWidth
371 << " height:" << param.dstHeight << " color: " << static_cast<int32_t>(param.colorFormat) << std::endl;
372
373 DoFetchFrame(timeUs, queryOption, param);
374 }
375
FetchArtPicture(std::queue<std::string_view> & options)376 void AVMetadataHelperDemo::FetchArtPicture(std::queue<std::string_view> &options)
377 {
378 (void)options;
379 auto result = avMetadataHelper_->FetchArtPicture();
380 if (result == nullptr) {
381 std::cout << "Fetch art picture failed" << std::endl;
382 return;
383 }
384
385 std::ofstream ofs("/data/media/cover.img");
386 if (!ofs.is_open()) {
387 std::cout << "open /data/media/cover.img failed" << std::endl;
388 return;
389 }
390
391 ofs.write(reinterpret_cast<char *>(result->GetBase()), result->GetSize());
392 ofs.close();
393 std::cout << "save picture to /data/media/cover.img" << std::endl;
394 }
395
DoNext()396 void AVMetadataHelperDemo::DoNext()
397 {
398 std::string cmd;
399 do {
400 std::cout << "Enter your step:" << std::endl;
401 (void)std::getline(std::cin, cmd);
402
403 std::queue<std::string_view> options;
404 MySplitStr(cmd, " ", options);
405
406 if (options.empty()) {
407 continue;
408 }
409
410 std::string_view funcName = options.front();
411 options.pop();
412
413 if (funcName.compare("metadata") == 0) {
414 GetMetadata(options);
415 continue;
416 }
417
418 if (funcName.compare("fetchframe") == 0) {
419 FetchFrame(options);
420 continue;
421 }
422
423 if (funcName.compare("artpicture") == 0) {
424 FetchArtPicture(options);
425 continue;
426 }
427
428 if (funcName.compare("quit") == 0 || funcName.compare("q") == 0) {
429 avMetadataHelper_->Release();
430 break;
431 }
432 } while (1);
433 }
434
SetSource(const std::string & pathOuter)435 int32_t AVMetadataHelperDemo::SetSource(const std::string &pathOuter)
436 {
437 std::string path;
438 if (pathOuter == "") {
439 std::cout << "Please enter the video/audio path: " << std::endl;
440 (void)getline(std::cin, path);
441 } else {
442 path = pathOuter;
443 }
444 std::cout << "Path is " << path << std::endl;
445
446 UriHelper uriHelper(path);
447 if (uriHelper.UriType() != UriHelper::URI_TYPE_FILE && !uriHelper.AccessCheck(UriHelper::URI_READ)) {
448 std::cout << "Invalid file Path" << std::endl;
449 return -1;
450 }
451
452 std::string rawFile = uriHelper.FormattedUri();
453 rawFile = rawFile.substr(strlen("file://"));
454 int32_t fd = open(rawFile.c_str(), O_RDONLY);
455 if (fd <= 0) {
456 std::cout << "Open file failed" << std::endl;
457 return -1;
458 }
459
460 struct stat64 st;
461 if (fstat64(fd, &st) != 0) {
462 std::cout << "Get file state failed" << std::endl;
463 (void)close(fd);
464 return -1;
465 }
466 int64_t length = static_cast<int64_t>(st.st_size);
467
468 int32_t ret = avMetadataHelper_->SetSource(fd, 0, length, AVMetadataUsage::AV_META_USAGE_PIXEL_MAP);
469 if (ret != 0) {
470 std::cout << "SetSource fail" << std::endl;
471 (void)close(fd);
472 return -1;
473 }
474
475 (void)close(fd);
476 return 0;
477 }
478
RunCase(const std::string & pathOuter)479 void AVMetadataHelperDemo::RunCase(const std::string &pathOuter)
480 {
481 avMetadataHelper_ = OHOS::Media::AVMetadataHelperFactory::CreateAVMetadataHelper();
482 if (avMetadataHelper_ == nullptr) {
483 std::cout << "avMetadataHelper_ is null" << std::endl;
484 return;
485 }
486
487 if (SetSource(pathOuter) != 0) {
488 return;
489 }
490
491 DoNext();
492 }
493 } // namespace Media
494 } // namespace OHOS
495