tizen 2.4 release
[framework/api/mediacodec.git] / src / media_codec.c
1 /*
2 * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <media_codec.h>
21 #include <media_codec_private.h>
22 #include <media_codec_port.h>
23 #include <system_info.h>
24
25 #include <dlog.h>
26
27 static gboolean  __mediacodec_empty_buffer_cb(media_packet_h pkt, void *user_data);
28 static gboolean __mediacodec_fill_buffer_cb(media_packet_h pkt, void *user_data);
29 static gboolean __mediacodec_error_cb(mediacodec_error_e error, void *user_data);
30 static gboolean __mediacodec_eos_cb(void *user_data);
31 static gboolean __mediacodec_supported_codec_cb(mediacodec_codec_type_e codec_type, void *user_data);
32 static gboolean __mediacodec_buffer_status_cb(mediacodec_status_e status, void *user_data);
33
34
35 /*
36 * Internal Implementation
37 */
38 int __convert_error_code(int code, char *func_name)
39 {
40     int ret = MEDIACODEC_ERROR_INVALID_OPERATION;
41     char *msg = "MEDIACOODEC_INVALID_OPERATION";
42
43     switch (code) {
44         case MC_ERROR_NONE:
45             ret = MEDIACODEC_ERROR_NONE;
46             msg = "MEDIACODEC_ERROR_NONE";
47             break;
48         case MC_PARAM_ERROR:
49         case MC_INVALID_ARG:
50             ret = MEDIACODEC_ERROR_INVALID_PARAMETER;
51             msg = "MEDIACODEC_ERROR_INVALID_PARAMETER";
52             break;
53         case MC_PERMISSION_DENIED:
54             ret = MEDIACODEC_ERROR_PERMISSION_DENIED;
55             msg = "MEDIACODEC_ERROR_PERMISSION_DENIED";
56             break;
57         case MC_INVALID_STATUS:
58             ret = MEDIACODEC_ERROR_INVALID_STATE;
59             msg = "MEDIACODEC_ERROR_INVALID_STATE";
60             break;
61         case MC_NOT_SUPPORTED:
62             ret = MEDIACODEC_ERROR_NOT_SUPPORTED_ON_DEVICE;
63             msg = "MEDIACODEC_ERROR_NOT_SUPPORTED_ON_DEVICE";
64             break;
65         case MC_ERROR:
66         case MC_INTERNAL_ERROR:
67         case MC_HW_ERROR:
68             ret = MEDIACODEC_ERROR_INVALID_OPERATION;
69             msg = "MEDIACODEC_ERROR_INVALID_OPERATION";
70             break;
71         case MC_INVALID_STREAM:
72             ret = MEDIACODEC_ERROR_INVALID_STREAM;
73             msg = "MEDIACODEC_ERROR_INVALID_STREAM";
74             break;
75         case MC_CODEC_NOT_FOUND:
76             ret = MEDIACODEC_ERROR_CODEC_NOT_FOUND;
77             msg = "MEDIACODEC_ERROR_CODEC_NOT_FOUND";
78             break;
79         case MC_ERROR_DECODE:
80             ret = MEDIACODEC_ERROR_DECODE;
81             msg = "MEDIACODEC_ERROR_DECODE";
82             break;
83         case MC_INVALID_IN_BUF:
84             ret = MEDIACODEC_ERROR_INVALID_INBUFFER;
85             msg = "MEDIACODEC_ERROR_INVALID_INBUFFER";
86             break;
87         case MC_INVALID_OUT_BUF:
88             ret = MEDIACODEC_ERROR_INVALID_OUTBUFFER;
89             msg = "MEDIACODEC_ERROR_INVALID_OUTBUFFER";
90             break;
91         case MC_NOT_INITIALIZED:
92             ret = MEDIACODEC_ERROR_NOT_INITIALIZED;
93             msg = "MEDIACODEC_ERROR_NOT_INITIALIZED";
94             break;
95         case MC_OUTPUT_BUFFER_EMPTY:
96         case MC_OUTPUT_BUFFER_OVERFLOW:
97             ret = MEDIACODEC_ERROR_BUFFER_NOT_AVAILABLE;
98             msg = "MEDIACODEC_ERROR_BUFFER_NOT_AVAILABLE";
99             break;
100         default:
101             ret = MEDIACODEC_ERROR_INTERNAL;
102             msg = "MEDIACODEC_ERROR_INTERNAL";
103             break;
104     }
105     LOGD("[%s] %s(0x%08x) : core fw error(0x%x)", func_name, msg, ret, code);
106     return ret;
107 }
108
109 /*
110  *  Public Implementation
111  */
112
113 int mediacodec_create(mediacodec_h *mediacodec)
114 {
115     MEDIACODEC_INSTANCE_CHECK(mediacodec);
116     mediacodec_s *handle;
117     int ret;
118
119     LOGD("mediacodec_create..");
120
121     handle = (mediacodec_s *)malloc(sizeof(mediacodec_s));
122     if (handle != NULL) {
123         memset(handle, 0 , sizeof(mediacodec_s));
124     } else {
125         LOGE("MEDIACODEC_ERROR_OUT_OF_MEMORY(0x%08x)", MEDIACODEC_ERROR_OUT_OF_MEMORY);
126         return MEDIACODEC_ERROR_OUT_OF_MEMORY;
127     }
128
129     ret = mc_create(&handle->mc_handle);
130     if (ret != MEDIACODEC_ERROR_NONE) {
131         LOGE("MEDIACODEC_ERROR_INVALID_OPERATION(0x%08x)", MEDIACODEC_ERROR_INVALID_OPERATION);
132         handle->state = MEDIACODEC_STATE_NONE;
133         free(handle);
134         handle = NULL;
135         return MEDIACODEC_ERROR_INVALID_OPERATION;
136     } else {
137         *mediacodec = (mediacodec_h)handle;
138         handle->state = MEDIACODEC_STATE_IDLE;
139         LOGD("new handle : %p", *mediacodec);
140     }
141
142     /* set callback */
143     mc_set_empty_buffer_cb(handle->mc_handle, (mediacodec_input_buffer_used_cb)__mediacodec_empty_buffer_cb, handle);
144     mc_set_fill_buffer_cb(handle->mc_handle, (mediacodec_output_buffer_available_cb)__mediacodec_fill_buffer_cb, handle);
145     mc_set_error_cb(handle->mc_handle, (mediacodec_error_cb)__mediacodec_error_cb, handle);
146     mc_set_eos_cb(handle->mc_handle, (mediacodec_eos_cb)__mediacodec_eos_cb, handle);
147     mc_set_buffer_status_cb(handle->mc_handle, (mediacodec_buffer_status_cb)__mediacodec_buffer_status_cb, handle);
148     mc_set_supported_codec_cb(handle->mc_handle, (mediacodec_supported_codec_cb)__mediacodec_supported_codec_cb, handle);
149
150     return MEDIACODEC_ERROR_NONE;
151
152 }
153
154 int mediacodec_destroy(mediacodec_h mediacodec)
155 {
156     LOGD("[%s] Start, handle to destroy : %p", __FUNCTION__, mediacodec);
157     MEDIACODEC_INSTANCE_CHECK(mediacodec);
158     mediacodec_s *handle = (mediacodec_s *)mediacodec;
159
160     int ret = mc_destroy(handle->mc_handle);
161     if (ret != MEDIACODEC_ERROR_NONE) {
162         LOGD("MEDIACODEC_ERROR_INVALID_OPERATION(0x%08x)", MEDIACODEC_ERROR_INVALID_OPERATION);
163         return MEDIACODEC_ERROR_INVALID_OPERATION;
164     } else {
165         handle->state = MEDIACODEC_STATE_NONE;
166         free(handle);
167         handle = NULL;
168         return MEDIACODEC_ERROR_NONE;
169     }
170 }
171
172 int mediacodec_set_codec(mediacodec_h mediacodec, mediacodec_codec_type_e codec_id, mediacodec_support_type_e flags)
173 {
174     MEDIACODEC_INSTANCE_CHECK(mediacodec);
175     mediacodec_s *handle = (mediacodec_s *)mediacodec;
176     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
177
178     int ret = mc_set_codec(handle->mc_handle, codec_id, flags);
179
180     if (ret != MEDIACODEC_ERROR_NONE) {
181         return __convert_error_code(ret, (char *)__FUNCTION__);
182     } else {
183         handle->state = MEDIACODEC_STATE_IDLE;
184         return MEDIACODEC_ERROR_NONE;
185     }
186 }
187
188 int mediacodec_set_vdec_info(mediacodec_h mediacodec, int width, int height)
189 {
190     MEDIACODEC_INSTANCE_CHECK(mediacodec);
191     mediacodec_s *handle = (mediacodec_s *)mediacodec;
192     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
193
194     int ret = mc_set_vdec_info(handle->mc_handle, width, height);
195
196     if (ret != MEDIACODEC_ERROR_NONE) {
197         return __convert_error_code(ret, (char *)__FUNCTION__);
198     } else {
199         handle->state = MEDIACODEC_STATE_IDLE;
200         return MEDIACODEC_ERROR_NONE;
201     }
202 }
203
204 int mediacodec_set_venc_info(mediacodec_h mediacodec, int width, int height, int fps, int target_bits)
205 {
206     MEDIACODEC_INSTANCE_CHECK(mediacodec);
207     mediacodec_s *handle = (mediacodec_s *)mediacodec;
208     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
209
210     int ret = mc_set_venc_info(handle->mc_handle, width, height, fps, target_bits);
211
212     if (ret != MEDIACODEC_ERROR_NONE) {
213         return __convert_error_code(ret, (char *)__FUNCTION__);
214     } else {
215         handle->state = MEDIACODEC_STATE_IDLE;
216         return MEDIACODEC_ERROR_NONE;
217     }
218 }
219
220 int mediacodec_set_adec_info(mediacodec_h mediacodec, int samplerate, int channel, int bit)
221 {
222     MEDIACODEC_INSTANCE_CHECK(mediacodec);
223     mediacodec_s *handle = (mediacodec_s *)mediacodec;
224     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
225
226     int ret = mc_set_adec_info(handle->mc_handle, samplerate, channel, bit);
227
228     if (ret != MEDIACODEC_ERROR_NONE) {
229         return __convert_error_code(ret, (char *)__FUNCTION__);
230     } else {
231         handle->state = MEDIACODEC_STATE_IDLE;
232         return MEDIACODEC_ERROR_NONE;
233     }
234 }
235
236 int mediacodec_set_aenc_info(mediacodec_h mediacodec, int samplerate, int channel, int bit, int bitrate)
237 {
238     MEDIACODEC_INSTANCE_CHECK(mediacodec);
239     mediacodec_s *handle = (mediacodec_s *)mediacodec;
240     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
241
242     int ret = mc_set_aenc_info(handle->mc_handle, samplerate, channel, bit, bitrate);
243
244     if (ret != MEDIACODEC_ERROR_NONE) {
245         return __convert_error_code(ret, (char *)__FUNCTION__);
246     } else {
247         handle->state = MEDIACODEC_STATE_IDLE;
248         return MEDIACODEC_ERROR_NONE;
249     }
250 }
251
252 int mediacodec_prepare(mediacodec_h mediacodec)
253 {
254     MEDIACODEC_INSTANCE_CHECK(mediacodec);
255     mediacodec_s *handle = (mediacodec_s *)mediacodec;
256     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
257
258     int ret = mc_prepare(handle->mc_handle);
259
260     if (ret != MEDIACODEC_ERROR_NONE) {
261         return __convert_error_code(ret, (char *)__FUNCTION__);
262     } else {
263         handle->state = MEDIACODEC_STATE_READY;
264         return MEDIACODEC_ERROR_NONE;
265     }
266 }
267
268 int mediacodec_unprepare(mediacodec_h mediacodec)
269 {
270     MEDIACODEC_INSTANCE_CHECK(mediacodec);
271     mediacodec_s *handle = (mediacodec_s *)mediacodec;
272
273     int ret = mc_unprepare(handle->mc_handle);
274
275     if (ret != MEDIACODEC_ERROR_NONE) {
276         return __convert_error_code(ret, (char *)__FUNCTION__);
277     } else {
278         handle->state = MEDIACODEC_STATE_IDLE;
279         return MEDIACODEC_ERROR_NONE;
280     }
281 }
282
283 int mediacodec_process_input(mediacodec_h mediacodec, media_packet_h inbuf, uint64_t timeOutUs)
284 {
285     MEDIACODEC_INSTANCE_CHECK(mediacodec);
286     mediacodec_s *handle = (mediacodec_s *)mediacodec;
287     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_READY);
288
289     int ret = mc_process_input(handle->mc_handle, inbuf, timeOutUs);
290
291     if (ret != MEDIACODEC_ERROR_NONE) {
292         return __convert_error_code(ret, (char *)__FUNCTION__);
293     } else {
294         return MEDIACODEC_ERROR_NONE;
295     }
296 }
297
298 int mediacodec_get_output(mediacodec_h mediacodec, media_packet_h *outbuf, uint64_t timeOutUs)
299 {
300     MEDIACODEC_INSTANCE_CHECK(mediacodec);
301     mediacodec_s *handle = (mediacodec_s *)mediacodec;
302     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_READY);
303
304     int ret = mc_get_output(handle->mc_handle, outbuf, timeOutUs);
305
306     if (ret != MEDIACODEC_ERROR_NONE) {
307         return __convert_error_code(ret, (char *)__FUNCTION__);
308     } else {
309         return MEDIACODEC_ERROR_NONE;
310     }
311 }
312
313 int mediacodec_flush_buffers(mediacodec_h mediacodec)
314 {
315     MEDIACODEC_INSTANCE_CHECK(mediacodec);
316     mediacodec_s *handle = (mediacodec_s *)mediacodec;
317
318     int ret = mc_flush_buffers(handle->mc_handle);
319
320     if (ret != MEDIACODEC_ERROR_NONE) {
321         return __convert_error_code(ret, (char *)__FUNCTION__);
322     } else {
323         return MEDIACODEC_ERROR_NONE;
324     }
325 }
326
327 int mediacodec_get_supported_type(mediacodec_h mediacodec, mediacodec_codec_type_e codec_type, bool encoder, int *support_type)
328 {
329     MEDIACODEC_INSTANCE_CHECK(mediacodec);
330     mediacodec_s *handle = (mediacodec_s *)mediacodec;
331     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
332
333     int ret = mc_get_supported_type(handle->mc_handle, codec_type, encoder, support_type);
334
335     if (ret != MEDIACODEC_ERROR_NONE) {
336         return __convert_error_code(ret, (char *)__FUNCTION__);
337     } else {
338         handle->state = MEDIACODEC_STATE_IDLE;
339         return MEDIACODEC_ERROR_NONE;
340     }
341 }
342
343 int mediacodec_set_input_buffer_used_cb(mediacodec_h mediacodec, mediacodec_input_buffer_used_cb callback, void *user_data)
344 {
345     MEDIACODEC_INSTANCE_CHECK(mediacodec);
346     mediacodec_s *handle = (mediacodec_s *)mediacodec;
347     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
348
349     handle->empty_buffer_cb = callback;
350     handle->empty_buffer_cb_userdata = user_data;
351
352     LOGD("set empty_buffer_cb(%p)", callback);
353
354     return MEDIACODEC_ERROR_NONE;
355 }
356
357 int mediacodec_unset_input_buffer_used_cb(mediacodec_h mediacodec)
358 {
359     MEDIACODEC_INSTANCE_CHECK(mediacodec);
360     mediacodec_s *handle = (mediacodec_s *)mediacodec;
361
362     handle->empty_buffer_cb = NULL;
363     handle->empty_buffer_cb_userdata = NULL;
364
365     return MEDIACODEC_ERROR_NONE;
366 }
367
368
369 int mediacodec_set_output_buffer_available_cb(mediacodec_h mediacodec, mediacodec_output_buffer_available_cb callback, void *user_data)
370 {
371     MEDIACODEC_INSTANCE_CHECK(mediacodec);
372     mediacodec_s *handle = (mediacodec_s *)mediacodec;
373     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
374
375     handle->fill_buffer_cb = callback;
376     handle->fill_buffer_cb_userdata = user_data;
377
378     LOGD("set fill_buffer_cb(%p)", callback);
379
380     return MEDIACODEC_ERROR_NONE;
381
382 }
383
384 int mediacodec_unset_output_buffer_available_cb(mediacodec_h mediacodec)
385 {
386     MEDIACODEC_INSTANCE_CHECK(mediacodec);
387     mediacodec_s *handle = (mediacodec_s *)mediacodec;
388
389     handle->fill_buffer_cb = NULL;
390     handle->fill_buffer_cb_userdata = NULL;
391
392     return MEDIACODEC_ERROR_NONE;
393 }
394
395 int mediacodec_set_error_cb(mediacodec_h mediacodec, mediacodec_error_cb callback, void *user_data)
396 {
397     MEDIACODEC_INSTANCE_CHECK(mediacodec);
398     mediacodec_s *handle = (mediacodec_s *)mediacodec;
399     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
400
401     handle->error_cb = callback;
402     handle->error_cb_userdata = user_data;
403
404     LOGD("set error_cb(%p)", callback);
405
406     return MEDIACODEC_ERROR_NONE;
407
408 }
409
410 int mediacodec_unset_error_cb(mediacodec_h mediacodec)
411 {
412     MEDIACODEC_INSTANCE_CHECK(mediacodec);
413     mediacodec_s *handle = (mediacodec_s *)mediacodec;
414
415     handle->error_cb = NULL;
416     handle->error_cb_userdata = NULL;
417
418     return MEDIACODEC_ERROR_NONE;
419 }
420
421 int mediacodec_set_eos_cb(mediacodec_h mediacodec, mediacodec_eos_cb callback, void *user_data)
422 {
423     MEDIACODEC_INSTANCE_CHECK(mediacodec);
424     mediacodec_s *handle = (mediacodec_s *)mediacodec;
425     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
426
427     handle->eos_cb = callback;
428     handle->eos_cb_userdata = user_data;
429
430     LOGD("set eos_cb(%p)", callback);
431
432     return MEDIACODEC_ERROR_NONE;
433
434 }
435
436 int mediacodec_unset_eos_cb(mediacodec_h mediacodec)
437 {
438     MEDIACODEC_INSTANCE_CHECK(mediacodec);
439     mediacodec_s *handle = (mediacodec_s *)mediacodec;
440
441     handle->eos_cb = NULL;
442     handle->eos_cb_userdata = NULL;
443
444     return MEDIACODEC_ERROR_NONE;
445 }
446
447 int mediacodec_set_buffer_status_cb(mediacodec_h mediacodec, mediacodec_buffer_status_cb callback, void *user_data)
448 {
449     MEDIACODEC_INSTANCE_CHECK(mediacodec);
450     mediacodec_s *handle = (mediacodec_s *)mediacodec;
451     MEDIACODEC_STATE_CHECK(handle, MEDIACODEC_STATE_IDLE);
452
453     handle->buffer_status_cb = callback;
454     handle->buffer_status_cb_userdata = user_data;
455
456     LOGD("set buffer_status_cb(%p)", callback);
457
458     return MEDIACODEC_ERROR_NONE;
459
460 }
461
462 int mediacodec_unset_buffer_status_cb(mediacodec_h mediacodec)
463 {
464     MEDIACODEC_INSTANCE_CHECK(mediacodec);
465     mediacodec_s *handle = (mediacodec_s *)mediacodec;
466
467     handle->buffer_status_cb = NULL;
468     handle->buffer_status_cb_userdata = NULL;
469
470     return MEDIACODEC_ERROR_NONE;
471 }
472
473 int mediacodec_foreach_supported_codec(mediacodec_h mediacodec, mediacodec_supported_codec_cb callback, void *user_data)
474 {
475     MEDIACODEC_INSTANCE_CHECK(mediacodec);
476     mediacodec_s *handle = (mediacodec_s *)mediacodec;
477
478     handle->supported_codec_cb = callback;
479     handle->supported_codec_cb_userdata = user_data;
480
481     LOGD("set supported_codec_cb(%p)", callback);
482     int ret = _mediacodec_foreach_supported_codec(handle->mc_handle, callback, handle);
483
484     if (ret != MEDIACODEC_ERROR_NONE) {
485         return __convert_error_code(ret, (char *)__FUNCTION__);
486     } else {
487         return MEDIACODEC_ERROR_NONE;
488     }
489
490     return MEDIACODEC_ERROR_NONE;
491
492 }
493
494 static gboolean __mediacodec_empty_buffer_cb(media_packet_h pkt, void *user_data)
495 {
496     if (user_data == NULL || pkt == NULL)
497         return 0;
498
499     mediacodec_s *handle = (mediacodec_s *)user_data;
500
501     if (handle->empty_buffer_cb) {
502         ((mediacodec_input_buffer_used_cb)handle->empty_buffer_cb)(pkt, handle->empty_buffer_cb_userdata);
503     }
504
505     return 1;
506 }
507
508 static gboolean  __mediacodec_fill_buffer_cb(media_packet_h pkt, void *user_data)
509 {
510     if (user_data == NULL || pkt == NULL)
511         return 0;
512
513     mediacodec_s *handle = (mediacodec_s *)user_data;
514
515     if (handle->fill_buffer_cb) {
516         ((mediacodec_output_buffer_available_cb)handle->fill_buffer_cb)(pkt, handle->fill_buffer_cb_userdata);
517     }
518
519     return 1;
520 }
521
522 static gboolean __mediacodec_error_cb(mediacodec_error_e error, void *user_data)
523 {
524     if (user_data == NULL)
525         return 0;
526
527     mediacodec_s *handle = (mediacodec_s *)user_data;
528
529     if (handle->error_cb) {
530         ((mediacodec_error_cb)handle->error_cb)(error, handle->error_cb_userdata);
531     }
532
533     return 1;
534 }
535
536 static gboolean __mediacodec_eos_cb(void *user_data)
537 {
538     if (user_data == NULL)
539         return 0;
540
541     mediacodec_s *handle = (mediacodec_s *)user_data;
542
543     if (handle->eos_cb) {
544         ((mediacodec_eos_cb)handle->eos_cb)(handle->eos_cb_userdata);
545     }
546
547     return 1;
548 }
549
550 static gboolean __mediacodec_supported_codec_cb(mediacodec_codec_type_e codec_type, void *user_data)
551 {
552     if (user_data == NULL)
553         return 0;
554
555     mediacodec_s *handle = (mediacodec_s *)user_data;
556
557     if (handle->supported_codec_cb) {
558         return ((mediacodec_supported_codec_cb)handle->supported_codec_cb)(codec_type, handle->supported_codec_cb_userdata);
559     }
560     return false;
561 }
562
563 static gboolean __mediacodec_buffer_status_cb(mediacodec_status_e status, void *user_data)
564 {
565     if (user_data == NULL)
566         return 0;
567
568     mediacodec_s *handle = (mediacodec_s *)user_data;
569
570     if (handle->buffer_status_cb) {
571         ((mediacodec_buffer_status_cb)handle->buffer_status_cb)(status, handle->buffer_status_cb_userdata);
572     }
573
574     return 1;
575 }
576