1 /*
2 * Copyright (c) 2023 Shenzhen Kaihong DID 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 "codec_jpeg_helper.h"
17 #include <ashmem.h>
18 #include <cerrno>
19 #include <cstring>
20 #include <securec.h>
21 #include "hdf_log.h"
22
23 using namespace OHOS::HDI::Codec::Image::V1_0;
JpegAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t fd)24 int32_t CodecJpegHelper::JpegAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t fd)
25 {
26 HDF_LOGI("enter");
27 int32_t curPos = 0;
28 // SOI
29 curPos = PutInt16(buffer, curPos, 0xffd8);
30 if (curPos < 0) {
31 HDF_LOGE("assemble SOI error");
32 return -1;
33 }
34
35 // DQT
36 curPos = JpegDqtAssemble(decInfo, buffer, curPos);
37 if (curPos < 0) {
38 HDF_LOGE("assemble DQT error");
39 return -1;
40 }
41
42 // DHT
43 curPos = JpegDhtAssemble(decInfo, buffer, curPos);
44 if (curPos < 0) {
45 HDF_LOGE("assemble DHT error");
46 return -1;
47 }
48 // DRI
49 curPos = JpegDriAssemble(decInfo, buffer, curPos);
50 if (curPos < 0) {
51 HDF_LOGE("assemble DRI error");
52 return -1;
53 }
54
55 // SOF
56 curPos = JpegSofAssemble(decInfo, buffer, curPos);
57 if (curPos < 0) {
58 HDF_LOGE("assemble SOF error");
59 return -1;
60 }
61 // SOS
62 curPos = JpegSosAssemble(decInfo, buffer, curPos);
63 if (curPos < 0) {
64 HDF_LOGE("assemble SOS error");
65 return -1;
66 }
67 // DATA
68 curPos = JpegDataAssemble(buffer, curPos, fd);
69 if (curPos < 0) {
70 HDF_LOGE("assemble CompressedData error");
71 return -1;
72 }
73 // EOI
74 curPos = PutInt16(buffer, curPos, 0xffd9);
75 if (curPos < 0) {
76 HDF_LOGE("assemble EOI error");
77 return -1;
78 }
79 return curPos;
80 }
81
DessambleJpeg(int8_t * buffer,size_t bufferLen,struct CodecJpegDecInfo & decInfo,std::unique_ptr<int8_t[]> & compressBuffer,uint32_t & comBufLen)82 bool CodecJpegHelper::DessambleJpeg(int8_t *buffer, size_t bufferLen, struct CodecJpegDecInfo &decInfo,
83 std::unique_ptr<int8_t[]> &compressBuffer, uint32_t &comBufLen)
84 {
85 HDF_LOGI("enter");
86 int8_t *start = buffer;
87 const int8_t *end = buffer + bufferLen;
88 while (start < end) {
89 JpegMarker marker = (JpegMarker)FindMarker(start);
90 start += 2; // 2: marker len
91 switch (marker) {
92 case SOI:
93 case EOI:
94 break;
95 case SOF0:
96 start += DessambleSof(start, decInfo);
97 break;
98 case DHT:
99 start += DessambleDht(start, decInfo);
100 break;
101 case SOS: {
102 start += DessambleSos(start, decInfo);
103 // compressed data start
104 auto len = DessambleCompressData(start, compressBuffer, comBufLen);
105 if (len < 0) {
106 HDF_LOGE("copy compressed data error");
107 return false;
108 }
109 start += len;
110 break;
111 }
112
113 case DQT:
114 start += DessambleDqt(start, decInfo);
115 break;
116 case DRI: {
117 start += 2; // 2: marker len
118 decInfo.restartInterval = GetInt16(start);
119 start += 2; // 2: interval len
120 break;
121 }
122 default: {
123 short len = GetInt16(start);
124 start += len;
125 HDF_LOGW("skip marker[%{public}x], len[%{public}d]", marker, len);
126 break;
127 }
128 }
129 }
130 return true;
131 }
JpegDqtAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t curPos)132 int32_t CodecJpegHelper::JpegDqtAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t curPos)
133 {
134 HDF_LOGI("enter. curPos = %{public}d", curPos);
135 // flag
136 curPos = PutInt16(buffer, curPos, 0xffdb);
137 if (curPos < 0) {
138 HDF_LOGE("assemble DQT flag error");
139 return curPos;
140 }
141
142 // skip len first
143 int32_t lenPos = curPos;
144 curPos += 2; // 2: marker len
145
146 // data
147 for (size_t i = 0; i < decInfo.quantTbl.size(); i++) {
148 if (!decInfo.quantTbl[i].tableFlag) {
149 break;
150 }
151 uint8_t index = 0; // precision 1:16bit, 0: 8bit, deault:1
152 index = (index << 4) | i; // precision << 4 | tableid
153 curPos = PutInt8(buffer, curPos, index);
154 if (curPos < 0) {
155 HDF_LOGE("assemble precision and tableid error");
156 return curPos;
157 }
158
159 for (size_t j = 0; j < decInfo.quantTbl[i].quantVal.size(); j++) {
160 HDF_LOGI("decInfo.quantTbl[%{public}zu].quantVal[%{public}zu] = %{public}d", i, j,
161 decInfo.quantTbl[i].quantVal[j]);
162 curPos = PutInt16(buffer, curPos, decInfo.quantTbl[i].quantVal[j]);
163 }
164 }
165 int16_t bufferLen = static_cast<int16_t>(curPos - lenPos);
166 auto ret = PutInt16(buffer, lenPos, bufferLen);
167 if (ret < 0) {
168 HDF_LOGE("assemble len error");
169 return ret;
170 }
171 return curPos;
172 }
JpegDriAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t curPos)173 int32_t CodecJpegHelper::JpegDriAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t curPos)
174 {
175 HDF_LOGI("enter, restartInterval = %{public}d curPos = %{public}d", decInfo.restartInterval, curPos);
176 if (decInfo.restartInterval <= 0) {
177 return curPos;
178 }
179 curPos = PutInt16(buffer, curPos, 0xffdd);
180 if (curPos < 0) {
181 HDF_LOGE("assemble DRI flag error");
182 return curPos;
183 }
184
185 curPos = PutInt16(buffer, curPos, 4); // 4: dri data len( marker len included)
186 if (curPos < 0) {
187 HDF_LOGE("assemble DRI len error");
188 return curPos;
189 }
190
191 curPos = PutInt16(buffer, curPos, decInfo.restartInterval);
192 if (curPos < 0) {
193 HDF_LOGE("assemble dri value error");
194 return curPos;
195 }
196 return curPos;
197 }
JpegDhtAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t curPos)198 int32_t CodecJpegHelper::JpegDhtAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t curPos)
199 {
200 HDF_LOGI("enter. curPos = %{public}d", curPos);
201 curPos = JpegDhtAssemble(decInfo.dcHuffTbl, buffer, curPos);
202 if (curPos < 0) {
203 HDF_LOGE("assemble dc hufman error");
204 return curPos;
205 }
206
207 curPos = JpegDhtAssemble(decInfo.acHuffTbl, buffer, curPos, false);
208 if (curPos < 0) {
209 HDF_LOGE("assemble ac hufman error");
210 }
211 return curPos;
212 }
JpegDhtAssemble(const std::vector<CodecJpegHuffTable> & table,int8_t * buffer,int32_t curPos,bool dc)213 int32_t CodecJpegHelper::JpegDhtAssemble(const std::vector<CodecJpegHuffTable> &table, int8_t *buffer, int32_t curPos,
214 bool dc)
215 {
216 HDF_LOGI("enter. curPos = %{public}d", curPos);
217 // DC Hufman
218 curPos = PutInt16(buffer, curPos, 0xffc4);
219 if (curPos < 0) {
220 HDF_LOGE("assemble hufman flag error");
221 return curPos;
222 }
223 // skip len
224 int32_t lenPos = curPos;
225 curPos += 2; // 2: marker len
226 for (size_t i = 0; i < table.size(); i++) {
227 if (!table[i].tableFlag) {
228 break;
229 }
230 uint8_t type = 0; // type 0:DC, 1:AC
231 if (!dc) {
232 type = 1;
233 }
234 type = (type << 4) | i; // type << 4 | tableid
235 curPos = PutInt8(buffer, curPos, type);
236 if (curPos < 0) {
237 HDF_LOGE("assemble tableid and dc/ac error");
238 return curPos;
239 }
240 // bits
241 auto ret = memcpy_s(buffer + curPos, table[i].bits.size(), table[i].bits.data(), table[i].bits.size());
242 if (ret != EOK) {
243 char buf[MAX_BUFFER_LEN] = {0};
244 strerror_r(errno, buf, sizeof(buf));
245 HDF_LOGE("assemble bits error ret = %{public}s", buf);
246 return ret;
247 }
248 curPos += table[i].bits.size();
249 // val
250 ret = memcpy_s(buffer + curPos, table[i].huffVal.size(), table[i].huffVal.data(), table[i].huffVal.size());
251 if (ret != EOK) {
252 HDF_LOGE("assemble huffVal error, ret = %{public}d", ret);
253 return ret;
254 }
255 curPos += table[i].huffVal.size();
256 }
257 int16_t bufferLen = static_cast<int16_t>(curPos - lenPos);
258 auto ret = PutInt16(buffer, lenPos, bufferLen);
259 if (ret < 0) {
260 HDF_LOGE("assemble len error");
261 return ret;
262 }
263 return curPos;
264 }
265
JpegSofAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t curPos)266 int32_t CodecJpegHelper::JpegSofAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t curPos)
267 {
268 HDF_LOGI("enter. curPos = %{public}d", curPos);
269 // flag
270 curPos = PutInt16(buffer, curPos, 0xffc0);
271 if (curPos < 0) {
272 HDF_LOGE("assemble SOF flag error");
273 return curPos;
274 }
275
276 int16_t len = decInfo.numComponents * 3 + 8; // * rgb channel + other data
277 curPos = PutInt16(buffer, curPos, len);
278 if (curPos < 0) {
279 HDF_LOGE("assemble SOF len error");
280 return curPos;
281 }
282
283 int8_t precision = decInfo.dataPrecision & 0xFF;
284 curPos = PutInt8(buffer, curPos, precision);
285 if (curPos < 0) {
286 HDF_LOGE("assemble SOF precision error");
287 return curPos;
288 }
289
290 // width
291 int16_t width = decInfo.imageHeight & 0xFFFF;
292 curPos = PutInt16(buffer, curPos, width);
293 if (curPos < 0) {
294 HDF_LOGE("assemble SOF width error");
295 return curPos;
296 }
297
298 // height
299 int16_t height = decInfo.imageWidth & 0xFFFF;
300 curPos = PutInt16(buffer, curPos, height);
301 if (curPos < 0) {
302 HDF_LOGE("assemble SOF width error");
303 return curPos;
304 }
305 // components
306 int8_t components = decInfo.numComponents & 0xFF;
307 curPos = PutInt8(buffer, curPos, components);
308 if (curPos < 0) {
309 HDF_LOGE("assemble SOF components error");
310 return curPos;
311 }
312 for (size_t i = 0; i < decInfo.compInfo.size(); i++) {
313 int8_t componentId = decInfo.compInfo[i].componentId;
314 // byte offset
315 int8_t sampFactor = ((decInfo.compInfo[i].hSampFactor & 0xFF) << 4) | (decInfo.compInfo[i].vSampFactor & 0xFF);
316 int8_t quantity = decInfo.compInfo[i].quantTableNo;
317 int8_t bufferValue[] = {componentId, sampFactor, quantity};
318 auto ret = memcpy_s(buffer + curPos, sizeof(bufferValue), bufferValue, sizeof(bufferValue));
319 if (ret != EOK) {
320 HDF_LOGE("assemble component error, ret = %{public}d", ret);
321 return ret;
322 }
323 curPos += sizeof(bufferValue);
324 }
325 return curPos;
326 }
JpegSosAssemble(const struct CodecJpegDecInfo & decInfo,int8_t * buffer,int32_t curPos)327 int32_t CodecJpegHelper::JpegSosAssemble(const struct CodecJpegDecInfo &decInfo, int8_t *buffer, int32_t curPos)
328 {
329 HDF_LOGI("enter. curPos = %{public}d", curPos);
330 // flag
331 curPos = PutInt16(buffer, curPos, 0xffda);
332 if (curPos < 0) {
333 HDF_LOGE("assemble SOS flag error");
334 return curPos;
335 }
336
337 int16_t len = decInfo.numComponents * 2 + 6; // * rgb table length + other data
338 curPos = PutInt16(buffer, curPos, len);
339 if (curPos < 0) {
340 HDF_LOGE("assemble SOS len error");
341 return curPos;
342 }
343
344 int8_t components = decInfo.numComponents & 0xFF;
345 curPos = PutInt8(buffer, curPos, components);
346 if (curPos < 0) {
347 HDF_LOGE("assemble SOS components error");
348 return curPos;
349 }
350
351 for (size_t i = 0; i < decInfo.compInfo.size(); i++) {
352 int8_t componentId = decInfo.compInfo[i].componentId;
353 int8_t indexNo = ((decInfo.compInfo[i].dcTableNo & 0xFF) << 4) | (decInfo.compInfo[i].acTableNo & 0xFF);
354 int16_t value = ((componentId << 8) | indexNo) & 0xffff;
355 curPos = PutInt16(buffer, curPos, value);
356 if (curPos < 0) {
357 HDF_LOGE("assemble SOS component value error");
358 return curPos;
359 }
360 }
361 int8_t dataStart[] = {0x00, 0x3F, 0x00};
362 auto ret = memcpy_s(buffer + curPos, sizeof(dataStart), dataStart, sizeof(dataStart));
363 if (ret != EOK) {
364 HDF_LOGE("assemble SOS data flag error, ret = %{public}d", ret);
365 return ret;
366 }
367 curPos += sizeof(dataStart);
368 return curPos;
369 }
JpegDataAssemble(int8_t * buffer,int32_t curPos,int32_t fd)370 int32_t CodecJpegHelper::JpegDataAssemble(int8_t *buffer, int32_t curPos, int32_t fd)
371 {
372 HDF_LOGI("enter. curPos = %{public}d", curPos);
373 int32_t size = OHOS::AshmemGetSize(fd);
374 HDF_LOGI("get size %{public}d from fd %{public}d", size, fd);
375 OHOS::Ashmem mem(fd, size);
376 // check ret value
377 mem.MapReadOnlyAshmem();
378 auto addr = const_cast<void *>(mem.ReadFromAshmem(0, 0));
379 auto ret = memcpy_s(buffer + curPos, size, addr, size);
380 if (ret != EOK) {
381 HDF_LOGE("assemble compressed data error, ret = %{public}d", ret);
382 mem.UnmapAshmem();
383 if (ret > 0) {
384 return -ret;
385 }
386 return ret;
387 }
388 mem.UnmapAshmem();
389 mem.CloseAshmem();
390 curPos += size;
391 return curPos;
392 }
393
DessambleSof(int8_t * buffer,struct CodecJpegDecInfo & decInfo)394 int32_t CodecJpegHelper::DessambleSof(int8_t *buffer, struct CodecJpegDecInfo &decInfo)
395 {
396 HDF_LOGI("dessamble SOI");
397 // len
398 int32_t len = GetInt16(buffer);
399 buffer += 2; // 2: marker len
400 // precision
401 decInfo.dataPrecision = GetInt8(buffer);
402 buffer++;
403 // height
404 decInfo.imageHeight = GetInt16(buffer);
405 buffer += 2; // 2: height len
406 // width
407 decInfo.imageWidth = GetInt16(buffer);
408 buffer += 2; // 2: width len
409
410 decInfo.numComponents = GetInt8(buffer);
411 buffer++;
412
413 HDF_LOGI("image width[%{public}d],height[%{public}d],components[%{public}d]", decInfo.imageWidth,
414 decInfo.imageHeight, decInfo.numComponents);
415 for (size_t i = 0; i < decInfo.numComponents; i++) {
416 CodecJpegCompInfo comInfo;
417
418 comInfo.infoFlag = true;
419 comInfo.componentId = GetInt8(buffer);
420 buffer++;
421
422 int8_t sampFactor = GetInt8(buffer);
423 buffer++;
424 comInfo.hSampFactor = (sampFactor >> 4) & 0xFF; // 4: hsampfactor offset
425 comInfo.vSampFactor = sampFactor & 0x0F;
426
427 comInfo.quantTableNo = GetInt8(buffer);
428 buffer++;
429 decInfo.compInfo.push_back(std::move(comInfo));
430 HDF_LOGI("componentId[%{public}d],hSampFactor[%{public}d],vSampFactor[%{public}d],quantTableNo[%{public}d]",
431 comInfo.componentId, comInfo.hSampFactor, comInfo.vSampFactor, comInfo.quantTableNo);
432 }
433 return len;
434 }
DessambleSos(int8_t * buffer,struct CodecJpegDecInfo & decInfo)435 int32_t CodecJpegHelper::DessambleSos(int8_t *buffer, struct CodecJpegDecInfo &decInfo)
436 {
437 HDF_LOGI("dessamble SOS");
438 int32_t len = GetInt16(buffer);
439 buffer += 2; // 2:marker len
440
441 int32_t components = GetInt8(buffer);
442 buffer++;
443
444 for (int32_t i = 0; i < components; i++) {
445 decInfo.compInfo[i].infoFlag = true;
446
447 int32_t componentId = GetInt8(buffer);
448 (void)componentId;
449 buffer++;
450 // index not used
451 auto data = GetInt8(buffer);
452 buffer++;
453 decInfo.compInfo[i].dcTableNo = (data >> 4) & 0x0F; // 4: dctable offset
454 decInfo.compInfo[i].acTableNo = data & 0x0F;
455 HDF_LOGI("componentId[%{public}d],dcTableNo[%{public}d],acTableNo[%{public}d]", componentId,
456 decInfo.compInfo[i].dcTableNo, decInfo.compInfo[i].acTableNo);
457 }
458 buffer += 3; // skip 0x003F00
459 return len;
460 }
DessambleCompressData(int8_t * buffer,std::unique_ptr<int8_t[]> & compressBuffer,uint32_t & comBufLen)461 int32_t CodecJpegHelper::DessambleCompressData(int8_t *buffer, std::unique_ptr<int8_t[]> &compressBuffer,
462 uint32_t &comBufLen)
463 {
464 int8_t *dataStart = buffer;
465 do {
466 int32_t v = GetInt8(buffer);
467 buffer++;
468 if (v != 0xff) {
469 continue;
470 }
471 v = GetInt8(buffer);
472 buffer++;
473 if (v != 0xd9) {
474 continue;
475 }
476 buffer -= 2; // 2: marker len
477 break;
478 } while (1);
479 comBufLen = (int32_t)(buffer - dataStart) + 1;
480 compressBuffer = std::make_unique<int8_t[]>(comBufLen);
481 auto ret = memcpy_s(compressBuffer.get(), comBufLen, dataStart, comBufLen);
482 if (ret != EOK) {
483 HDF_LOGE("copy compressed data error, dataLen %{public}d, ret %{public}d", comBufLen, ret);
484 compressBuffer = nullptr;
485 return ret;
486 }
487 return comBufLen;
488 }
DessambleDqt(int8_t * buffer,struct CodecJpegDecInfo & decInfo)489 int32_t CodecJpegHelper::DessambleDqt(int8_t *buffer, struct CodecJpegDecInfo &decInfo)
490 {
491 HDF_LOGI("dessamble DQT");
492 int8_t *bufferOri = buffer;
493 int32_t len = GetInt16(buffer);
494 buffer += 2; // 2: marker len
495 // maybe has more dqt table
496 while ((buffer - bufferOri) < len) {
497 auto data = GetInt8(buffer);
498 buffer++;
499 int32_t tableId = data & 0x000f;
500 (void)tableId;
501 int32_t size = 32; // size
502 if (((data >> 4) & 0x0f) == 1) { // 4: low 4 bits, 1: for 16 bits
503 size *= 2; // 2: 16bits has double size
504 }
505 CodecJpegQuantTable table;
506 table.tableFlag = true;
507 HDF_LOGI("tableid[%{public}d]", tableId);
508 for (int32_t i = 0; i < size; i++) { // 2: 16bits has double size
509 table.quantVal.push_back(static_cast<int16_t>(GetInt16(buffer)));
510 buffer += 2; // 2: data offset
511 }
512 decInfo.quantTbl.push_back(std::move(table));
513 }
514 return len;
515 }
DessambleDht(int8_t * buffer,struct CodecJpegDecInfo & decInfo)516 int32_t CodecJpegHelper::DessambleDht(int8_t *buffer, struct CodecJpegDecInfo &decInfo)
517 {
518 HDF_LOGI("dessamble DHT");
519 int8_t *bufferOri = buffer;
520 int32_t len = GetInt16(buffer);
521 buffer += 2; // 2: marker len
522 // 可能存在多个表在同一个dht marker 中
523 while ((buffer - bufferOri) < len) {
524 auto data = GetInt8(buffer);
525 buffer++;
526 int32_t tableId = data & 0x000f;
527 (void)tableId;
528 int32_t acOrDc = (data >> 4) & 0x0f; // 0:DC, 1:AC, 4: ac/dc data offset
529 CodecJpegHuffTable table;
530 table.tableFlag = true;
531 int32_t num = 0;
532 for (size_t i = 0; i < 16; i++) { // 16: Data size
533 auto data = GetInt8(buffer);
534 buffer++;
535 table.bits.push_back(data);
536 num += data & 0x00ff;
537 }
538 HDF_LOGI("tableid[%{public}d], acOrDc[%{public}d], num[%{public}d]", tableId, acOrDc, num);
539 // val
540 for (int32_t i = 0; i < num; i++) {
541 table.huffVal.push_back(*buffer++);
542 }
543 if (acOrDc == 1) {
544 decInfo.acHuffTbl.push_back(std::move(table));
545 } else {
546 decInfo.dcHuffTbl.push_back(std::move(table));
547 }
548 }
549 return len;
550 }
551
FindMarker(int8_t * start)552 int32_t CodecJpegHelper::FindMarker(int8_t *start)
553 {
554 int32_t marker = GetInt16(start);
555 return marker;
556 }
557
PutInt16(int8_t * buffer,int32_t curPos,int16_t value)558 int32_t CodecJpegHelper::PutInt16(int8_t *buffer, int32_t curPos, int16_t value)
559 {
560 int8_t data[] = {value >> 8, value & 0xFF};
561 auto ret = memcpy_s(buffer + curPos, sizeof(data), data, sizeof(data));
562 if (ret != EOK) {
563 HDF_LOGE("memcpy ret err %{public}d", ret);
564 return -1;
565 }
566 return curPos + sizeof(data);
567 }
568
PutInt8(int8_t * buffer,int32_t curPos,int8_t value)569 int32_t CodecJpegHelper::PutInt8(int8_t *buffer, int32_t curPos, int8_t value)
570 {
571 auto ret = memcpy_s(buffer + curPos, sizeof(value), &value, sizeof(value));
572 if (ret != EOK) {
573 HDF_LOGE("memcpy ret err %{public}d", ret);
574 return -1;
575 }
576 return curPos + sizeof(value);
577 }
578
GetInt8(int8_t * buffer)579 int32_t CodecJpegHelper::GetInt8(int8_t *buffer)
580 {
581 return buffer[0] & 0x00ff;
582 }
583
GetInt16(int8_t * buffer)584 int32_t CodecJpegHelper::GetInt16(int8_t *buffer)
585 {
586 return ((buffer[0] << 8) & 0x00ff00) | (buffer[1] & 0x00ff); // 8:data offset
587 }
588