1 /*****************************************************************************
2 * daala.c: daala codec module making use of libdaala.
3 *****************************************************************************
4 * Copyright (C) 2014 VLC authors and VideoLAN
6 * Authors: Tristan Matthews <le.businessman@gmail.com>
7 * * Based on theora.c by: Gildas Bazin <gbazin@videolan.org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_codec.h>
34 #include <vlc_input.h>
35 #include "../demux/xiph.h"
39 #include <daala/codec.h>
40 #include <daala/daaladec.h>
42 #include <daala/daalaenc.h>
47 /*****************************************************************************
48 * decoder_sys_t : daala decoder descriptor
49 *****************************************************************************/
63 daala_info di; /* daala bitstream settings */
64 daala_comment dc; /* daala comment information */
65 daala_dec_ctx *dcx; /* daala decoder context */
70 bool b_decoded_first_keyframe;
78 /*****************************************************************************
80 *****************************************************************************/
81 static int OpenDecoder ( vlc_object_t * );
82 static int OpenPacketizer( vlc_object_t * );
83 static void CloseDecoder ( vlc_object_t * );
85 static void *DecodeBlock ( decoder_t *, block_t ** );
86 static int ProcessHeaders( decoder_t * );
87 static void *ProcessPacket ( decoder_t *, ogg_packet *, block_t ** );
89 static picture_t *DecodePacket( decoder_t *, ogg_packet * );
91 static void ParseDaalaComments( decoder_t * );
92 static void daala_CopyPicture( picture_t *, od_img * );
95 static int OpenEncoder( vlc_object_t *p_this );
96 static void CloseEncoder( vlc_object_t *p_this );
97 static block_t *Encode( encoder_t *p_enc, picture_t *p_pict );
99 static const char *const enc_chromafmt_list[] = {
102 static const char *const enc_chromafmt_list_text[] = {
107 /*****************************************************************************
109 *****************************************************************************/
110 #define ENC_QUALITY_TEXT N_("Encoding quality")
111 #define ENC_QUALITY_LONGTEXT N_( \
112 "Enforce a quality between 0 (lossless) and 511 (worst)." )
113 #define ENC_KEYINT_TEXT N_("Keyframe interval")
114 #define ENC_KEYINT_LONGTEXT N_( \
115 "Enforce a keyframe interval between 1 and 1000." )
118 set_category( CAT_INPUT )
119 set_subcategory( SUBCAT_INPUT_VCODEC )
120 set_shortname( "Daala" )
121 set_description( N_("Daala video decoder") )
122 set_capability( "decoder", 100 )
123 set_callbacks( OpenDecoder, CloseDecoder )
124 add_shortcut( "daala" )
126 set_description( N_("Daala video packetizer") )
127 set_capability( "packetizer", 100 )
128 set_callbacks( OpenPacketizer, CloseDecoder )
129 add_shortcut( "daala" )
133 set_description( N_("Daala video encoder") )
134 set_capability( "encoder", 150 )
135 set_callbacks( OpenEncoder, CloseEncoder )
136 add_shortcut( "daala" )
138 # define ENC_CFG_PREFIX "sout-daala-"
139 add_integer_with_range( ENC_CFG_PREFIX "quality", 30, 0, 511,
140 ENC_QUALITY_TEXT, ENC_QUALITY_LONGTEXT, false )
141 add_integer_with_range( ENC_CFG_PREFIX "keyint", 256, 1, 1000,
142 ENC_KEYINT_TEXT, ENC_KEYINT_LONGTEXT, false )
144 # define ENC_CHROMAFMT_TEXT N_("Chroma format")
145 # define ENC_CHROMAFMT_LONGTEXT N_("Picking chroma format will force a " \
146 "conversion of the video into that format")
148 add_string( ENC_CFG_PREFIX "chroma-fmt", "420", ENC_CHROMAFMT_TEXT,
149 ENC_CHROMAFMT_LONGTEXT, false )
150 change_string_list( enc_chromafmt_list, enc_chromafmt_list_text )
153 static const char *const ppsz_enc_options[] = {
154 "quality", "keyint", "chroma-fmt", NULL
158 /*****************************************************************************
159 * OpenDecoder: probe the decoder and return score
160 *****************************************************************************/
161 static int OpenDecoder( vlc_object_t *p_this )
163 decoder_t *p_dec = (decoder_t*)p_this;
164 decoder_sys_t *p_sys;
166 if( p_dec->fmt_in.i_codec != VLC_CODEC_DAALA )
171 /* Allocate the memory needed to store the decoder's structure */
172 p_sys = malloc(sizeof(*p_sys));
176 p_dec->p_sys = p_sys;
177 p_dec->p_sys->b_packetizer = false;
178 p_sys->b_has_headers = false;
179 p_sys->i_pts = VLC_TS_INVALID;
180 p_sys->b_decoded_first_keyframe = false;
183 /* Set output properties */
184 p_dec->fmt_out.i_cat = VIDEO_ES;
185 p_dec->fmt_out.i_codec = VLC_CODEC_I420;
188 p_dec->pf_decode_video = (picture_t *(*)(decoder_t *, block_t **))
190 p_dec->pf_packetize = (block_t *(*)(decoder_t *, block_t **))
193 /* Init supporting Daala structures needed in header parsing */
194 daala_comment_init( &p_sys->dc );
195 daala_info_init( &p_sys->di );
200 static int OpenPacketizer( vlc_object_t *p_this )
202 decoder_t *p_dec = (decoder_t*)p_this;
204 int i_ret = OpenDecoder( p_this );
206 if( i_ret == VLC_SUCCESS )
208 p_dec->p_sys->b_packetizer = true;
209 p_dec->fmt_out.i_codec = VLC_CODEC_DAALA;
215 /****************************************************************************
216 * DecodeBlock: the whole thing
217 ****************************************************************************
218 * This function must be fed with ogg packets.
219 ****************************************************************************/
220 static void *DecodeBlock( decoder_t *p_dec, block_t **pp_block )
222 decoder_sys_t *p_sys = p_dec->p_sys;
224 ogg_packet oggpacket;
226 if( !pp_block || !*pp_block ) return NULL;
230 /* Block to Ogg packet */
231 oggpacket.packet = p_block->p_buffer;
232 oggpacket.bytes = p_block->i_buffer;
233 oggpacket.granulepos = p_block->i_dts;
236 oggpacket.packetno = 0;
238 /* Check for headers */
239 if( !p_sys->b_has_headers )
241 if( ProcessHeaders( p_dec ) )
243 block_Release( p_block );
246 p_sys->b_has_headers = true;
249 /* If we haven't seen a single keyframe yet, set to preroll,
250 * otherwise we'll get display artifacts. (This is impossible
251 * in the general case, but can happen if e.g. we play a network stream
252 * using a timed URL, such that the server doesn't start the video with a
254 if( !p_sys->b_decoded_first_keyframe )
255 p_block->i_flags |= BLOCK_FLAG_PREROLL; /* Wait until we've decoded the first keyframe */
257 return ProcessPacket( p_dec, &oggpacket, pp_block );
260 /*****************************************************************************
261 * ProcessHeaders: process Daala headers.
262 *****************************************************************************/
263 static int ProcessHeaders( decoder_t *p_dec )
265 int ret = VLC_SUCCESS;
266 decoder_sys_t *p_sys = p_dec->p_sys;
267 ogg_packet oggpacket;
268 daala_setup_info *ds = NULL; /* daala setup information */
270 unsigned pi_size[XIPH_MAX_HEADER_COUNT];
271 void *pp_data[XIPH_MAX_HEADER_COUNT];
273 if( xiph_SplitHeaders( pi_size, pp_data, &i_count,
274 p_dec->fmt_in.i_extra, p_dec->fmt_in.p_extra) )
279 oggpacket.granulepos = -1;
281 oggpacket.packetno = 0;
283 /* Take care of the initial info header */
284 oggpacket.b_o_s = 1; /* yes this actually is a b_o_s packet :) */
285 oggpacket.bytes = pi_size[0];
286 oggpacket.packet = pp_data[0];
287 if( daala_decode_header_in( &p_sys->di, &p_sys->dc, &ds, &oggpacket ) < 0 )
289 msg_Err( p_dec, "this bitstream does not contain Daala video data" );
294 /* Set output properties */
295 if( !p_sys->b_packetizer )
297 if( p_sys->di.plane_info[0].xdec == 0 && p_sys->di.plane_info[0].ydec == 0 &&
298 p_sys->di.plane_info[1].xdec == 1 && p_sys->di.plane_info[1].ydec == 1 &&
299 p_sys->di.plane_info[2].xdec == 1 && p_sys->di.plane_info[2].ydec == 1 )
301 p_dec->fmt_out.i_codec = VLC_CODEC_I420;
303 else if( p_sys->di.plane_info[0].xdec == 0 && p_sys->di.plane_info[0].ydec == 0 &&
304 p_sys->di.plane_info[1].xdec == 0 && p_sys->di.plane_info[1].ydec == 0 &&
305 p_sys->di.plane_info[2].xdec == 0 && p_sys->di.plane_info[2].ydec == 0 )
307 p_dec->fmt_out.i_codec = VLC_CODEC_I444;
311 msg_Err( p_dec, "unknown chroma in daala sample" );
315 p_dec->fmt_out.video.i_width = p_sys->di.pic_width;
316 p_dec->fmt_out.video.i_height = p_sys->di.pic_height;
317 if( p_sys->di.pic_width && p_sys->di.pic_height )
319 p_dec->fmt_out.video.i_visible_width = p_sys->di.pic_width;
320 p_dec->fmt_out.video.i_visible_height = p_sys->di.pic_height;
323 if( p_sys->di.pixel_aspect_denominator && p_sys->di.pixel_aspect_numerator )
325 p_dec->fmt_out.video.i_sar_num = p_sys->di.pixel_aspect_numerator;
326 p_dec->fmt_out.video.i_sar_den = p_sys->di.pixel_aspect_denominator;
330 p_dec->fmt_out.video.i_sar_num = 1;
331 p_dec->fmt_out.video.i_sar_den = 1;
334 if( p_sys->di.timebase_numerator > 0 && p_sys->di.timebase_denominator > 0 )
336 p_dec->fmt_out.video.i_frame_rate = p_sys->di.timebase_numerator;
337 p_dec->fmt_out.video.i_frame_rate_base = p_sys->di.timebase_denominator;
340 msg_Dbg( p_dec, "%dx%d %.02f fps video, frame content ",
341 p_sys->di.pic_width, p_sys->di.pic_height,
342 (double)p_sys->di.timebase_numerator/p_sys->di.timebase_denominator );
344 /* The next packet in order is the comments header */
346 oggpacket.bytes = pi_size[1];
347 oggpacket.packet = pp_data[1];
349 if( daala_decode_header_in( &p_sys->di, &p_sys->dc, &ds, &oggpacket ) < 0 )
351 msg_Err( p_dec, "Daala comment header is corrupted" );
356 ParseDaalaComments( p_dec );
358 /* The next packet in order is the setup header
359 * We need to watch out that this packet is not missing as a
360 * missing or corrupted header is fatal. */
362 oggpacket.bytes = pi_size[2];
363 oggpacket.packet = pp_data[2];
364 if( daala_decode_header_in( &p_sys->di, &p_sys->dc, &ds, &oggpacket ) < 0 )
366 msg_Err( p_dec, "Daala setup header is corrupted" );
371 if( !p_sys->b_packetizer )
373 /* We have all the headers, initialize decoder */
374 if ( ( p_sys->dcx = daala_decode_alloc( &p_sys->di, ds ) ) == NULL )
376 msg_Err( p_dec, "Could not allocate Daala decoder" );
383 p_dec->fmt_out.i_extra = p_dec->fmt_in.i_extra;
384 p_dec->fmt_out.p_extra = xrealloc( p_dec->fmt_out.p_extra,
385 p_dec->fmt_out.i_extra );
386 memcpy( p_dec->fmt_out.p_extra,
387 p_dec->fmt_in.p_extra, p_dec->fmt_out.i_extra );
391 /* Clean up the decoder setup info... we're done with it */
392 daala_setup_free( ds );
397 /*****************************************************************************
398 * ProcessPacket: processes a daala packet.
399 *****************************************************************************/
400 static void *ProcessPacket( decoder_t *p_dec, ogg_packet *p_oggpacket,
403 decoder_sys_t *p_sys = p_dec->p_sys;
404 block_t *p_block = *pp_block;
407 if( ( p_block->i_flags&(BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) != 0 )
409 /* Don't send the the first packet after a discontinuity to
410 * daala_decode, otherwise we get purple/green display artifacts
411 * appearing in the video output */
412 block_Release(p_block);
416 /* Date management */
417 if( p_block->i_pts > VLC_TS_INVALID && p_block->i_pts != p_sys->i_pts )
419 p_sys->i_pts = p_block->i_pts;
422 *pp_block = NULL; /* To avoid being fed the same packet again */
424 if( p_sys->b_packetizer )
426 /* Date management */
427 p_block->i_dts = p_block->i_pts = p_sys->i_pts;
429 p_block->i_length = p_sys->i_pts - p_block->i_pts;
435 p_buf = DecodePacket( p_dec, p_oggpacket );
436 block_Release( p_block );
439 /* Date management */
440 p_sys->i_pts += ( CLOCK_FREQ * p_sys->di.timebase_denominator /
441 p_sys->di.timebase_numerator ); /* 1 frame per packet */
446 /*****************************************************************************
447 * DecodePacket: decodes a Daala packet.
448 *****************************************************************************/
449 static picture_t *DecodePacket( decoder_t *p_dec, ogg_packet *p_oggpacket )
451 decoder_sys_t *p_sys = p_dec->p_sys;
455 if (daala_decode_packet_in( p_sys->dcx, &ycbcr, p_oggpacket ) < 0)
456 return NULL; /* bad packet */
458 /* Check for keyframe */
459 if( daala_packet_iskeyframe( p_oggpacket->packet, p_oggpacket->bytes ) )
460 p_sys->b_decoded_first_keyframe = true;
462 /* Get a new picture */
463 p_pic = decoder_NewPicture( p_dec );
464 if( !p_pic ) return NULL;
466 daala_CopyPicture( p_pic, &ycbcr );
468 p_pic->date = p_sys->i_pts;
473 /*****************************************************************************
474 * ParseDaalaComments:
475 *****************************************************************************/
476 static void ParseDaalaComments( decoder_t *p_dec )
478 char *psz_name, *psz_value, *psz_comment;
479 /* Regarding the daala_comment structure: */
481 /* The metadata is stored as a series of (tag, value) pairs, in
482 length-encoded string vectors. The first occurrence of the '='
483 character delimits the tag and value. A particular tag may
484 occur more than once, and order is significant. The character
485 set encoding for the strings is always UTF-8, but the tag names
486 are limited to ASCII, and treated as case-insensitive. See the
487 Theora specification, Section 6.3.3 for details. */
489 /* In filling in this structure, daala_decode_header_in() will
490 null-terminate the user_comment strings for safety. However,
491 the bitstream format itself treats them as 8-bit clean vectors,
492 possibly containing null characters, and so the length array
493 should be treated as their authoritative length. */
494 for ( int i = 0; i < p_dec->p_sys->dc.comments; i++ )
496 int clen = p_dec->p_sys->dc.comment_lengths[i];
497 if ( clen <= 0 || clen >= INT_MAX ) { continue; }
498 psz_comment = malloc( clen + 1 );
501 memcpy( (void*)psz_comment, (void*)p_dec->p_sys->dc.user_comments[i], clen + 1 );
502 psz_comment[clen] = '\0';
504 psz_name = psz_comment;
505 psz_value = strchr( psz_comment, '=' );
511 if( !p_dec->p_description )
512 p_dec->p_description = vlc_meta_New();
513 /* TODO: Since psz_value can contain NULLs see if there is an
514 * instance where we need to preserve the full length of this string */
515 if( p_dec->p_description )
516 vlc_meta_AddExtra( p_dec->p_description, psz_name, psz_value );
522 /*****************************************************************************
523 * CloseDecoder: daala decoder destruction
524 *****************************************************************************/
525 static void CloseDecoder( vlc_object_t *p_this )
527 decoder_t *p_dec = (decoder_t *)p_this;
528 decoder_sys_t *p_sys = p_dec->p_sys;
530 daala_info_clear(&p_sys->di);
531 daala_comment_clear(&p_sys->dc);
532 daala_decode_free(p_sys->dcx);
536 /*****************************************************************************
537 * daala_CopyPicture: copy a picture from daala internal buffers to a
538 * picture_t structure.
539 *****************************************************************************/
540 static void daala_CopyPicture( picture_t *p_pic,
543 const int i_planes = p_pic->i_planes < 3 ? p_pic->i_planes : 3;
544 for( int i_plane = 0; i_plane < i_planes; i_plane++ )
546 const int i_total_lines = __MIN(p_pic->p[i_plane].i_lines,
547 ycbcr->height >> ycbcr->planes[i_plane].ydec);
548 uint8_t *p_dst = p_pic->p[i_plane].p_pixels;
549 uint8_t *p_src = ycbcr->planes[i_plane].data;
550 const int i_dst_stride = p_pic->p[i_plane].i_pitch;
551 const int i_src_stride = ycbcr->planes[i_plane].ystride;
552 for( int i_line = 0; i_line < i_total_lines; i_line++ )
554 memcpy( p_dst, p_src, i_src_stride );
555 p_src += i_src_stride;
556 p_dst += i_dst_stride;
564 daala_info di; /* daala bitstream settings */
565 daala_comment dc; /* daala comment header */
566 daala_enc_ctx *dcx; /* daala context */
569 static int OpenEncoder( vlc_object_t *p_this )
571 encoder_t *p_enc = (encoder_t *)p_this;
572 encoder_sys_t *p_sys;
576 if( p_enc->fmt_out.i_codec != VLC_CODEC_DAALA &&
582 /* Allocate the memory needed to store the encoder's structure */
583 p_sys = malloc( sizeof( encoder_sys_t ) );
586 p_enc->p_sys = p_sys;
588 p_enc->pf_encode_video = Encode;
589 p_enc->fmt_in.i_codec = VLC_CODEC_I420;
590 p_enc->fmt_out.i_codec = VLC_CODEC_DAALA;
592 config_ChainParse( p_enc, ENC_CFG_PREFIX, ppsz_enc_options, p_enc->p_cfg );
594 char *psz_tmp = var_GetString( p_enc, ENC_CFG_PREFIX "chroma-fmt" );
600 if( !strcmp( psz_tmp, "420" ) ) {
601 i_codec = VLC_CODEC_I420;
603 else if( !strcmp( psz_tmp, "444" ) ) {
604 i_codec = VLC_CODEC_I444;
607 msg_Err( p_enc, "Invalid chroma format: %s", psz_tmp );
613 p_enc->fmt_in.i_codec = i_codec;
614 /* update bits_per_pixel */
615 video_format_Setup(&p_enc->fmt_in.video, i_codec,
616 p_enc->fmt_in.video.i_width,
617 p_enc->fmt_in.video.i_height,
618 p_enc->fmt_in.video.i_visible_width,
619 p_enc->fmt_in.video.i_visible_height,
620 p_enc->fmt_in.video.i_sar_num,
621 p_enc->fmt_in.video.i_sar_den);
624 daala_info_init( &p_sys->di );
626 p_sys->di.pic_width = p_enc->fmt_in.video.i_visible_width;
627 p_sys->di.pic_height = p_enc->fmt_in.video.i_visible_height;
629 p_sys->di.nplanes = 3;
630 for (int i = 0; i < p_sys->di.nplanes; i++)
632 p_sys->di.plane_info[i].xdec = i > 0 && i_codec != VLC_CODEC_I444;
633 p_sys->di.plane_info[i].ydec = i_codec == VLC_CODEC_I420 ?
634 p_sys->di.plane_info[i].xdec : 0;
636 p_sys->di.frame_duration = 1;
638 if( !p_enc->fmt_in.video.i_frame_rate ||
639 !p_enc->fmt_in.video.i_frame_rate_base )
641 p_sys->di.timebase_numerator = 25;
642 p_sys->di.timebase_denominator = 1;
646 p_sys->di.timebase_numerator = p_enc->fmt_in.video.i_frame_rate;
647 p_sys->di.timebase_denominator = p_enc->fmt_in.video.i_frame_rate_base;
650 if( p_enc->fmt_in.video.i_sar_num > 0 && p_enc->fmt_in.video.i_sar_den > 0 )
652 unsigned i_dst_num, i_dst_den;
653 vlc_ureduce( &i_dst_num, &i_dst_den,
654 p_enc->fmt_in.video.i_sar_num,
655 p_enc->fmt_in.video.i_sar_den, 0 );
656 p_sys->di.pixel_aspect_numerator = i_dst_num;
657 p_sys->di.pixel_aspect_denominator = i_dst_den;
661 p_sys->di.pixel_aspect_numerator = 4;
662 p_sys->di.pixel_aspect_denominator = 3;
665 p_sys->di.keyframe_rate = var_GetInteger( p_enc, ENC_CFG_PREFIX "keyint" );
668 p_sys->dcx = dcx = daala_encode_create( &p_sys->di );
675 daala_comment_init( &p_sys->dc );
677 int i_quality = var_GetInteger( p_enc, ENC_CFG_PREFIX "quality" );
678 daala_encode_ctl( dcx, OD_SET_QUANT, &i_quality, sizeof(i_quality) );
680 /* Create and store headers */
681 while( ( status = daala_encode_flush_header( dcx, &p_sys->dc, &header ) ) )
685 CloseEncoder( p_this );
688 if( xiph_AppendHeaders( &p_enc->fmt_out.i_extra,
689 &p_enc->fmt_out.p_extra, header.bytes,
692 p_enc->fmt_out.i_extra = 0;
693 p_enc->fmt_out.p_extra = NULL;
699 static block_t *Encode( encoder_t *p_enc, picture_t *p_pict )
701 encoder_sys_t *p_sys = p_enc->p_sys;
702 ogg_packet oggpacket;
706 if( !p_pict ) return NULL;
708 const int i_width = p_sys->di.pic_width;
709 const int i_height = p_sys->di.pic_height;
712 if( p_pict->p[0].i_pitch < i_width ||
713 p_pict->p[0].i_lines < i_height )
715 msg_Err( p_enc, "frame is smaller than encoding size"
716 "(%ix%i->%ix%i) -> dropping frame",
717 p_pict->p[0].i_pitch, p_pict->p[0].i_lines,
722 /* Daala is a one-frame-in, one-frame-out system. Submit a frame
723 * for compression and pull out the packet. */
725 img.nplanes = p_sys->di.nplanes;
727 img.height = i_height;
728 for( int i = 0; i < img.nplanes; i++ )
730 img.planes[i].data = p_pict->p[i].p_pixels;
731 img.planes[i].xdec = p_sys->di.plane_info[i].xdec;
732 img.planes[i].ydec = p_sys->di.plane_info[i].ydec;
733 img.planes[i].xstride = 1;
734 img.planes[i].ystride = p_pict->p[i].i_pitch;
737 if( daala_encode_img_in( p_sys->dcx, &img, 0 ) < 0 )
739 msg_Warn( p_enc, "failed encoding a frame" );
743 daala_encode_packet_out( p_sys->dcx, 0, &oggpacket );
745 /* Ogg packet to block */
746 p_block = block_Alloc( oggpacket.bytes );
747 memcpy( p_block->p_buffer, oggpacket.packet, oggpacket.bytes );
748 p_block->i_dts = p_block->i_pts = p_pict->date;
750 if( daala_packet_iskeyframe( oggpacket.packet, oggpacket.bytes ) )
751 p_block->i_flags |= BLOCK_FLAG_TYPE_I;
756 static void CloseEncoder( vlc_object_t *p_this )
758 encoder_t *p_enc = (encoder_t *)p_this;
759 encoder_sys_t *p_sys = p_enc->p_sys;
761 daala_info_clear(&p_sys->di);
762 daala_comment_clear(&p_sys->dc);
763 daala_encode_free(p_sys->dcx);