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