1 /* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. 2 * 3 * Redistribution and use in source and binary forms, with or without 4 * modification, are permitted provided that the following conditions are 5 * met: 6 * * Redistributions of source code must retain the above copyright 7 * notice, this list of conditions and the following disclaimer. 8 * * Redistributions in binary form must reproduce the above 9 * copyright notice, this list of conditions and the following 10 * disclaimer in the documentation and/or other materials provided 11 * with the distribution. 12 * * Neither the name of The Linux Foundation nor the names of its 13 * contributors may be used to endorse or promote products derived 14 * from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 25 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 26 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 */ 29 30 // System dependencies 31 #include <dlfcn.h> 32 #include <stdbool.h> 33 #include <stdlib.h> 34 #include <sys/time.h> 35 36 // Camera dependencies 37 #include "img_buffer.h" 38 #include "mm_lib2d.h" 39 40 41 #define ENABLE_OUTPUT_DUMP 1 42 #define ALIGN4K 4032 43 #define ALIGN(a, b) (((a) + (b)) & ~(b)) 44 45 46 /** DUMP_TO_FILE: 47 * @filename: file name 48 * @p_addr: address of the buffer 49 * @len: buffer length 50 * 51 * dump the image to the file 52 **/ 53 #define DUMP_TO_FILE(filename, p_addr, len) ({ \ 54 size_t rc = 0; \ 55 FILE *fp = fopen(filename, "w+"); \ 56 if (fp) { \ 57 rc = fwrite(p_addr, 1, len, fp); \ 58 printf(" ] written size %zu \n", __LINE__, len); \ 59 fclose(fp); \ 60 } else { \ 61 printf(" ] open %s failed \n", __LINE__, filename); \ 62 } \ 63 }) 64 65 /** DUMP_TO_FILE2: 66 * @filename: file name 67 * @p_addr: address of the buffer 68 * @len: buffer length 69 * 70 * dump the image to the file if the memory is non-contiguous 71 **/ 72 #define DUMP_TO_FILE2(filename, p_addr1, len1, p_addr2, len2) ({ \ 73 size_t rc = 0; \ 74 FILE *fp = fopen(filename, "w+"); \ 75 if (fp) { \ 76 rc = fwrite(p_addr1, 1, len1, fp); \ 77 rc = fwrite(p_addr2, 1, len2, fp); \ 78 printf(" ] written %zu %zu \n", __LINE__, len1, len2); \ 79 fclose(fp); \ 80 } else { \ 81 printf(" ] open %s failed \n", __LINE__, filename); \ 82 } \ 83 }) 84 85 /** img_lib_buffert 86 * @ptr: handle to the imglib library 87 * @img_buffer_get: function pointer to img_buffer_get 88 * @img_buffer_release: function pointer to img_buffer_release 89 * @img_buffer_cacheops: function pointer to img_buffer_cacheops 90 **/ 91 typedef struct { 92 void *ptr; 93 int (*img_buffer_get)(img_buf_type_t type, int heapid, int8_t cached, int length, 94 img_mem_handle_t *p_handle); 95 int (*img_buffer_release)(img_mem_handle_t *p_handle); 96 int (*img_buffer_cacheops)(img_mem_handle_t *p_handle, img_cache_ops_t ops, 97 img_mem_alloc_type_t mem_alloc_type); 98 } img_lib_buffert; 99 100 /** input_yuv_data 101 * @filename: input test filename 102 * @format: format of the input yuv frame 103 * @wdith: wdith of the input yuv frame 104 * @height: height of the input yuv frame 105 * @stride: stride of the input yuv frame 106 * @offset: offset to the yuv data in the input file 107 **/ 108 typedef struct input_yuv_data_t { 109 char filename[512]; 110 cam_format_t format; 111 int32_t wdith; 112 int32_t height; 113 int32_t stride; 114 int32_t offset; 115 } input_yuv_data; 116 117 input_yuv_data input_nv21[] = { 118 {"sample0_768x512.yuv", CAM_FORMAT_YUV_420_NV21, 768, 512, 768, 0}, 119 {"sample1_3200x2400.yuv", CAM_FORMAT_YUV_420_NV21, 3200, 2400, 3200, 0}, 120 {"sample2_1920x1080.yuv", CAM_FORMAT_YUV_420_NV21, 1920, 1080, 1920, 0}, 121 {"sample3_3200x2400.yuv", CAM_FORMAT_YUV_420_NV21, 3200, 2400, 3200, 0}, 122 {"sample4_4208x3120.yuv", CAM_FORMAT_YUV_420_NV21, 4208, 3120, 4208, 0}, 123 {"sample5_1984x2592.yuv", CAM_FORMAT_YUV_420_NV21, 1984, 2592, 1984, 0}, 124 {"sample6_4000_3000.yuv", CAM_FORMAT_YUV_420_NV21, 4000, 3000, 4000, 0}, 125 {"sample7_3200_2400.yuv", CAM_FORMAT_YUV_420_NV21, 3200, 2400, 3200, 0}, 126 {"sample8_3008_4000.yuv", CAM_FORMAT_YUV_420_NV21, 3008, 4000, 3008, 0}, 127 {"sample9_5312x2988.yuv", CAM_FORMAT_YUV_420_NV21, 5312, 2988, 5312, 0}, 128 {"sample10_4128x3096.yuv", CAM_FORMAT_YUV_420_NV21, 4128, 3096, 4128, 0}, 129 {"sample11_4208x3120.yuv", CAM_FORMAT_YUV_420_NV21, 4208, 3120, 4208, 0}, 130 {"sample12_3200x2400.yuv", CAM_FORMAT_YUV_420_NV21, 3200, 2400, 3200, 0}, 131 {"sample13_width_1080_height_1440_stride_1088.yuv", CAM_FORMAT_YUV_420_NV21, 1080, 1440, 1088, 0}, 132 {"sample14_width_1080_height_1920_stride_1088.yuv", CAM_FORMAT_YUV_420_NV21, 1080, 1920, 1088, 0}, 133 {"sample15_width_1944_height_2592_stride_1984.yuv", CAM_FORMAT_YUV_420_NV21, 1944, 2592, 1984, 0}, 134 {"sample16_width_3000_height_4000_stride_3008.yuv", CAM_FORMAT_YUV_420_NV21, 3000, 4000, 3008, 0}, 135 {"sample17_width_3120_height_4208_stride_3136.yuv", CAM_FORMAT_YUV_420_NV21, 3120, 4208, 3136, 0}, 136 {"sample18_width_3200_height_2400_stride_3200.yuv", CAM_FORMAT_YUV_420_NV21, 3200, 2400, 3200, 0}, 137 {"sample19_width_1944_height_2592_stride_1984.yuv", CAM_FORMAT_YUV_420_NV21, 1944, 2592, 1984, 0}, 138 }; 139 140 // assuming buffer format is always ARGB 141 void lib2d_dump_tga(void *addr, cam_format_t format, int width, 142 int height, int stride, char *fname) 143 { 144 int i, j; 145 FILE *f; 146 unsigned char *pb = (unsigned char *)addr; 147 uint32_t *pd = (uint32_t *)addr; 148 int bpp = 32; 149 150 f = fopen(fname, "wb"); 151 if (f) { 152 // header 153 fprintf(f, "%c%c%c%c", 0, 0, 2, 0); 154 fprintf(f, "%c%c%c%c", 0, 0, 0, 0); 155 fprintf(f, "%c%c%c%c", 0, 0, 0, 0); 156 fprintf(f, "%c%c%c%c", width & 0xff, width >> 8, height & 0xff, height >> 8); 157 fprintf(f, "%c%c", bpp, 32); 158 159 for (i = 0; i < height; i++) { 160 for (j = 0; j < width; j++) { 161 fprintf(f, "%c%c%c%c", 162 pd[(i*stride>>2)+j] & 0xff, // b 163 (pd[(i*stride>>2)+j] >> 8) & 0xff, // g 164 (pd[(i*stride>>2)+j] >> 16) & 0xff, // r 165 (pd[(i*stride>>2)+j] >> 24) & 0xff); // a 166 } 167 } 168 fclose(f); 169 } 170 } 171 172 /** 173 * Function: lib2d_test_client_cb 174 * 175 * Description: Callback that is called on completion of requested job. 176 * 177 * Input parameters: 178 * userdata - App userdata 179 * jobid - job id that is finished execution 180 * 181 * Return values: 182 * MM_LIB2D_SUCCESS 183 * MM_LIB2D_ERR_GENERAL 184 * 185 * Notes: none 186 **/ 187 lib2d_error lib2d_test_client_cb(void *userdata, int jobid) 188 { 189 printf("%s %d, jobid=%d \n", __LINE__, jobid); 190 return MM_LIB2D_SUCCESS; 191 } 192 193 /** 194 * Function: lib2d_test_load_input_yuv_data 195 * 196 * Description: Loads yuv data from input file. 197 * 198 * Input parameters: 199 * fileName - input yuv filename 200 * offset - offset to the yuv data in the input file 201 * y_size - y plane size in input yuv file 202 * crcb_size - crcb plane size in input yuv file 203 * crcb_offset - crcb offset in the memory at 204 * which crcb data need to be loaded 205 * addr - y plane memory address where y plane 206 * data need to be loaded. 207 * 208 * Return values: 209 * MM_LIB2D_SUCCESS 210 * MM_LIB2D_ERR_GENERAL 211 * 212 * Notes: none 213 **/ 214 lib2d_error lib2d_test_load_input_yuv_data(char *fileName, int offset, 215 int32_t y_size, int32_t crcb_size, int32_t crcb_offset, 216 void *addr) 217 { 218 size_t i; 219 FILE *fp = 0; 220 void *y_ptr = addr; 221 void *crcb_ptr = (uint8_t *)addr + crcb_offset; 222 223 printf("y_ptr=%p, crcb_ptr=%p \n", y_ptr, crcb_ptr); 224 225 fp = fopen(fileName, "rb"); 226 if(fp) { 227 if(offset) { 228 fseek(fp, offset, SEEK_SET); 229 } 230 i = fread(y_ptr, 1, y_size, fp); 231 i = fread(crcb_ptr, 1, crcb_size, fp); 232 233 fclose( fp ); 234 } else { 235 printf("failed to open file %s \n", fileName); 236 return MM_LIB2D_ERR_GENERAL; 237 } 238 239 return MM_LIB2D_SUCCESS; 240 } 241 242 /** 243 * Function: lib2d_test_load_input_yuv_data 244 * 245 * Description: Loads yuv data from input file. 246 * 247 * Input parameters: 248 * fileName - input yuv filename 249 * offset - offset to the yuv data in the input file 250 * input_yuv_stride - y plane stride in input yuv file 251 * y_plane_stride - y plane stride in buffer memory 252 * height - height of yuv image 253 * crcb_offset - crcb offset in the memory at 254 * which crcb data need to be loaded 255 * addr - y plane memory address where y plane 256 * data need to be loaded. 257 * 258 * Return values: 259 * MM_LIB2D_SUCCESS 260 * MM_LIB2D_ERR_GENERAL 261 * 262 * Notes: none 263 **/ 264 lib2d_error lib2d_test_load_input_yuv_data_linebyline(char *fileName, 265 int offset, int32_t input_yuv_stride, int32_t y_plane_stride, 266 int32_t height, int32_t crcb_offset, void *addr) 267 { 268 size_t i; 269 FILE *fp = 0; 270 void *y_ptr = addr; 271 void *crcb_ptr = (uint8_t *)addr + crcb_offset; 272 273 printf("y_ptr=%p, crcb_ptr=%p \n", y_ptr, crcb_ptr); 274 275 fp = fopen(fileName, "rb"); 276 if(fp) { 277 if(offset) { 278 fseek(fp, offset, SEEK_SET); 279 } 280 if (input_yuv_stride == y_plane_stride) { 281 //load y plane 282 i = fread(y_ptr, 1, (input_yuv_stride * height), fp); 283 // load UV plane 284 i = fread(crcb_ptr, 1, (input_yuv_stride * height / 2), fp); 285 } else { 286 int line = 0; 287 // load Y plane 288 for (line = 0;line < height; line++) { 289 i = fread(y_ptr, 1, input_yuv_stride, fp); 290 y_ptr = (void *)((uint8_t *)y_ptr + y_plane_stride); 291 } 292 for (line = 0;line < height; line++) { 293 i = fread(crcb_ptr, 1, input_yuv_stride, fp); 294 crcb_ptr = (void *)((uint8_t *)crcb_ptr + y_plane_stride); 295 } 296 } 297 298 fclose( fp ); 299 } else { 300 printf("failed to open file %s \n", fileName); 301 return MM_LIB2D_ERR_GENERAL; 302 } 303 304 return MM_LIB2D_SUCCESS; 305 } 306 307 /** 308 * Function: main 309 * 310 * Description: main function for execution 311 * 312 * Input parameters: 313 * argc - no.of input arguments 314 * argv - list of arguments 315 * 316 * Return values: 317 * 0 on success 318 * -1 on failure 319 * 320 * Notes: none 321 **/ 322 int main(int32_t argc, const char * argv[]) 323 { 324 void *lib2d_handle = NULL; 325 lib2d_error lib2d_err = MM_LIB2D_SUCCESS; 326 mm_lib2d_buffer src_buffer = {0}; 327 mm_lib2d_buffer dst_buffer = {0}; 328 int8_t ret = IMG_SUCCESS; 329 int32_t width = 0; 330 int32_t height = 0; 331 int32_t input_yuv_stride = 0; 332 int32_t stride = 0; 333 int32_t y_plane_stride = 0; 334 int32_t crcb_plane_stride = 0; 335 int32_t y_plane_size = 0; 336 int32_t y_plane_size_align = 0; 337 int32_t crcb_plane_size = 0; 338 int32_t yuv_size = 0; 339 int32_t rgb_size = 0; 340 img_mem_handle_t m_yuv_memHandle = { 0 }; 341 img_mem_handle_t m_rgb_memHandle = { 0 }; 342 char filename_in[512] = { 0 }; 343 char filename_out[512] = { 0 }; 344 char filename_raw[512] = { 0 }; 345 int32_t offset = 0; 346 unsigned int total_tests = 1; 347 cam_format_t format = CAM_FORMAT_YUV_420_NV21; 348 unsigned int index; 349 const char *filename; 350 351 // Open Imglib library and get the function pointers for 352 // buffer allocation, free, cacheops 353 img_lib_buffert img_lib; 354 img_lib.ptr = dlopen("libmmcamera_imglib.so", RTLD_NOW); 355 if (!img_lib.ptr) { 356 printf("%s ERROR: couldn't dlopen libmmcamera_imglib.so: %s", 357 dlerror()); 358 return -1; 359 } 360 361 /* Get function pointer for functions to allocate ion memory */ 362 *(void **)&img_lib.img_buffer_get = 363 dlsym(img_lib.ptr, "img_buffer_get"); 364 *(void **)&img_lib.img_buffer_release = 365 dlsym(img_lib.ptr, "img_buffer_release"); 366 *(void **)&img_lib.img_buffer_cacheops = 367 dlsym(img_lib.ptr, "img_buffer_cacheops"); 368 369 /* Validate function pointers */ 370 if ((img_lib.img_buffer_get == NULL) || 371 (img_lib.img_buffer_release == NULL) || 372 (img_lib.img_buffer_cacheops == NULL)) { 373 printf(" ERROR mapping symbols from libmmcamera_imglib.so"); 374 dlclose(img_lib.ptr); 375 return -1; 376 } 377 378 lib2d_err = mm_lib2d_init(MM_LIB2D_SYNC_MODE, CAM_FORMAT_YUV_420_NV21, 379 CAM_FORMAT_8888_ARGB, &lib2d_handle); 380 if ((lib2d_err != MM_LIB2D_SUCCESS) || (lib2d_handle == NULL)) { 381 return -1; 382 } 383 384 bool run_default = FALSE; 385 386 if ( argc == 7) { 387 filename = argv[1]; 388 width = (uint32_t)atoi(argv[2]); 389 height = (uint32_t)atoi(argv[3]); 390 input_yuv_stride = (uint32_t)atoi(argv[4]); 391 offset = (uint32_t)atoi(argv[5]); 392 format = (uint32_t)atoi(argv[6]); 393 run_default = TRUE; 394 printf("Running user provided conversion \n"); 395 } 396 else { 397 total_tests = sizeof(input_nv21)/sizeof(input_yuv_data); 398 printf("usage: <binary> <filname> <width> <height> " 399 "<stride> <offset> <format> \n"); 400 } 401 402 for (index = 0; index < total_tests; index++) 403 { 404 if(run_default == FALSE) { 405 filename = input_nv21[index].filename; 406 width = input_nv21[index].wdith; 407 height = input_nv21[index].height; 408 input_yuv_stride = input_nv21[index].stride; 409 offset = input_nv21[index].offset; 410 format = input_nv21[index].format; 411 } 412 413 snprintf(filename_in, 512, "/data/lib2d/input/%s", filename); 414 snprintf(filename_out, 512, "/data/lib2d/output/%s.tga", filename); 415 snprintf(filename_raw, 512, "/data/lib2d/output/%s.rgba", filename); 416 417 printf("-----------------Running test=%d/%d------------------------- \n", 418 index+1, total_tests); 419 printf("filename=%s, full path=%s, width=%d, height=%d, stride=%d \n", 420 filename, filename_in, width, height, stride); 421 422 // Allocate NV12 buffer 423 y_plane_stride = ALIGN(width, 32); 424 y_plane_size = y_plane_stride * height; 425 y_plane_size_align = ALIGN(y_plane_size, ALIGN4K); 426 crcb_plane_stride = y_plane_stride; 427 crcb_plane_size = crcb_plane_stride * height / 2; 428 yuv_size = y_plane_size_align + crcb_plane_size; 429 ret = img_lib.img_buffer_get(IMG_BUFFER_ION_IOMMU, -1, TRUE, 430 yuv_size, &m_yuv_memHandle); 431 if (ret != IMG_SUCCESS) { 432 printf(" ] Error, img buf get failed \n"); 433 goto deinit; 434 } 435 436 printf("%s %d yuv buffer properties : w=%d, h=%d, y_stride=%d, " 437 "crcb_stride=%d, y_size=%d, crcb_size=%d, yuv_size=%d, " 438 "crcb_offset=%d \n", 439 __LINE__, 440 width, height, y_plane_stride, crcb_plane_stride, y_plane_size, 441 crcb_plane_size, yuv_size, y_plane_size_align); 442 printf("%s %d yuv buffer properties : fd=%d, ptr=%p, size=%d \n", 443 __LINE__, m_yuv_memHandle.fd, m_yuv_memHandle.vaddr, 444 m_yuv_memHandle.length); 445 446 // Allocate ARGB buffer 447 stride = width * 4; 448 stride = ALIGN(stride, 32); 449 rgb_size = stride * height; 450 ret = img_lib.img_buffer_get(IMG_BUFFER_ION_IOMMU, -1, TRUE, 451 rgb_size, &m_rgb_memHandle); 452 if (ret != IMG_SUCCESS) { 453 printf(" ] Error, img buf get failed"); 454 img_lib.img_buffer_release(&m_yuv_memHandle); 455 goto deinit; 456 } 457 458 printf("%s %d rgb buffer properties : w=%d, h=%d, stride=%d, size=%d \n", 459 __LINE__, width, height, stride, rgb_size); 460 printf("%s %d rgb buffer properties : fd=%d, ptr=%p, size=%d \n", 461 __LINE__, m_rgb_memHandle.fd, m_rgb_memHandle.vaddr, 462 m_rgb_memHandle.length); 463 464 #if 0 465 lib2d_err = lib2d_test_load_input_yuv_data(filename_in, offset, 466 (input_yuv_stride * height), (input_yuv_stride * height / 2), y_plane_size_align, 467 m_yuv_memHandle.vaddr); 468 if (lib2d_err != MM_LIB2D_SUCCESS) { 469 printf(" ] Error loading the input buffer \n"); 470 goto release; 471 } 472 #else 473 lib2d_err = lib2d_test_load_input_yuv_data_linebyline(filename_in, offset, 474 input_yuv_stride, y_plane_stride,height, y_plane_size_align, 475 m_yuv_memHandle.vaddr); 476 if (lib2d_err != MM_LIB2D_SUCCESS) { 477 printf(" ] Error loading the input buffer \n"); 478 goto release; 479 } 480 #endif 481 // Setup source buffer 482 src_buffer.buffer_type = MM_LIB2D_BUFFER_TYPE_YUV; 483 src_buffer.yuv_buffer.fd = m_yuv_memHandle.fd; 484 src_buffer.yuv_buffer.format = format; 485 src_buffer.yuv_buffer.width = width; 486 src_buffer.yuv_buffer.height = height; 487 src_buffer.yuv_buffer.plane0 = m_yuv_memHandle.vaddr; 488 src_buffer.yuv_buffer.stride0 = y_plane_stride; 489 src_buffer.yuv_buffer.plane1 = (int8_t *)m_yuv_memHandle.vaddr + 490 y_plane_size_align; 491 src_buffer.yuv_buffer.stride1 = crcb_plane_stride; 492 493 // Setup dst buffer 494 dst_buffer.buffer_type = MM_LIB2D_BUFFER_TYPE_RGB; 495 dst_buffer.rgb_buffer.fd = m_rgb_memHandle.fd; 496 dst_buffer.rgb_buffer.format = CAM_FORMAT_8888_ARGB; 497 dst_buffer.rgb_buffer.width = width; 498 dst_buffer.rgb_buffer.height = height; 499 dst_buffer.rgb_buffer.buffer = m_rgb_memHandle.vaddr; 500 dst_buffer.rgb_buffer.stride = stride; 501 502 img_lib.img_buffer_cacheops(&m_yuv_memHandle, 503 IMG_CACHE_CLEAN_INV, IMG_INTERNAL); 504 505 lib2d_err = mm_lib2d_start_job(lib2d_handle, &src_buffer, &dst_buffer, 506 index, NULL, lib2d_test_client_cb); 507 if (lib2d_err != MM_LIB2D_SUCCESS) { 508 printf(" ] Error in mm_lib2d_start_job \n"); 509 goto release; 510 } 511 512 img_lib.img_buffer_cacheops(&m_rgb_memHandle, 513 IMG_CACHE_CLEAN_INV, IMG_INTERNAL); 514 515 #ifdef ENABLE_OUTPUT_DUMP 516 // Dump output files 517 // snprintf(filename_in, 512, "/data/lib2d/output/%s", filename); 518 // DUMP_TO_FILE2(filename_in, src_buffer.yuv_buffer.plane0, y_plane_size, src_buffer.yuv_buffer.plane1, crcb_plane_size); 519 // DUMP_TO_FILE(filename_raw, dst_buffer.rgb_buffer.buffer, rgb_size); 520 printf("Dumping output file %s \n", filename_out); 521 lib2d_dump_tga(dst_buffer.rgb_buffer.buffer, 1, 522 width, height, stride, filename_out); 523 #endif 524 525 img_lib.img_buffer_release(&m_rgb_memHandle); 526 img_lib.img_buffer_release(&m_yuv_memHandle); 527 } 528 529 mm_lib2d_deinit(lib2d_handle); 530 531 return 0; 532 533 release: 534 img_lib.img_buffer_release(&m_rgb_memHandle); 535 img_lib.img_buffer_release(&m_yuv_memHandle); 536 deinit: 537 mm_lib2d_deinit(lib2d_handle); 538 printf("%s %d some error happened, tests completed = %d/%d \n", 539 __LINE__, index - 1, total_tests); 540 return -1; 541 } 542 543 544