diff options
author | Darren Salt <linux@youmustbejoking.demon.co.uk> | 2009-02-10 18:25:09 +0000 |
---|---|---|
committer | Darren Salt <linux@youmustbejoking.demon.co.uk> | 2009-02-10 18:25:09 +0000 |
commit | 1c19b8e8e3cfb32e341332b751686e86d7389569 (patch) | |
tree | 64d9edcf312915b3d9ae524d55b6508b61bf361f | |
parent | c62b455944c8c91bd4d9ae5e8000ec33190174c5 (diff) | |
parent | 6002a9a87b3f591832c2b91ca1b2b1b67be008f5 (diff) | |
download | xine-lib-1c19b8e8e3cfb32e341332b751686e86d7389569.tar.gz xine-lib-1c19b8e8e3cfb32e341332b751686e86d7389569.tar.bz2 |
Merge from 1.1.
--HG--
rename : doc/faq/faq.sgml => doc/faq/faq.docbook
rename : src/xine-engine/buffer.h => include/xine/buffer.h
rename : src/xine-engine/xine_internal.h => include/xine/xine_internal.h
-rw-r--r-- | .hgtags | 1 | ||||
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | doc/faq/faq.docbook | 13 | ||||
-rw-r--r-- | include/xine/buffer.h | 3 | ||||
-rw-r--r-- | include/xine/xine_internal.h | 1 | ||||
-rw-r--r-- | src/combined/ffmpeg/ff_audio_decoder.c | 4 | ||||
-rw-r--r-- | src/demuxers/demux_4xm.c | 4 | ||||
-rw-r--r-- | src/input/input_dvb.c | 4 | ||||
-rw-r--r-- | src/input/input_file.c | 4 | ||||
-rw-r--r-- | src/input/input_gnome_vfs.c | 4 | ||||
-rw-r--r-- | src/input/input_http.c | 4 | ||||
-rw-r--r-- | src/input/input_mms.c | 4 | ||||
-rw-r--r-- | src/input/input_net.c | 4 | ||||
-rw-r--r-- | src/input/input_pnm.c | 4 | ||||
-rw-r--r-- | src/input/input_pvr.c | 4 | ||||
-rw-r--r-- | src/input/input_rtp.c | 4 | ||||
-rw-r--r-- | src/input/input_rtsp.c | 4 | ||||
-rw-r--r-- | src/input/input_smb.c | 4 | ||||
-rw-r--r-- | src/input/input_stdin_fifo.c | 4 | ||||
-rw-r--r-- | src/xine-engine/audio_decoder.c | 6 | ||||
-rw-r--r-- | src/xine-engine/audio_out.c | 2 | ||||
-rw-r--r-- | src/xine-engine/demux.c | 53 | ||||
-rw-r--r-- | src/xine-engine/video_decoder.c | 2 | ||||
-rw-r--r-- | src/xine-engine/xine.c | 6 | ||||
-rw-r--r-- | src/xine-engine/xine_interface.c | 3 |
25 files changed, 122 insertions, 36 deletions
@@ -79,3 +79,4 @@ ffe7962edb79c2ed967b82a82ccfb2ac7eb148a2 vdr-xine-version-802 17f8ed16524ba779af42913e51667e89b83a1887 xine-lib-1_1_15-release e33280bcaa3b1f3f5b93e633e2225e2440ecfd7c xine-lib-1_1_16-release 01fac0a015581bbdf7e38561ad2a95405e2ca785 xine-lib-1_1_16_1-release +ff19463729d8f9bbea35171d641c5f28cdacc7c8 xine-lib-1_1_16_2-release @@ -69,12 +69,18 @@ xine-lib (1.1.90) (Unreleased) colour controls, zooming, colour keying. xine-lib (1.1.17) 2009-??-?? - * Build fixes related to ImageMagick 6.4 & later. - * Fix an error in Matroska PTS calculation. * Enable libmpeg2new. This is not yet production code; the old mpeg2 decoder remains the default. - * Fix a broken size check in the pvr input plugin (ref. CVE-2008-5239). + +xine-lib (1.1.16.2) 2009-02-10 + * Build fixes related to ImageMagick 6.4 & later. + * Fix an error in Matroska PTS calculation. + * Some front ends hang due to the hang fixes in 1.1.16. Fix this by + removing a break statement. + * Fix broken size checks in various input plugins (ref. CVE-2008-5239). * More malloc checking (ref. CVE-2008-5240). + * Fix race conditions in gapless_switch (ref. kde bug #180339) + * Fix a possible integer overflow in the 4XM demuxer. (TKADV2009-004.txt) xine-lib (1.1.16.1) 2009-01-11 * Fix build with older ffmpeg, both internal and in Debian 5.0. diff --git a/doc/faq/faq.docbook b/doc/faq/faq.docbook index 7b8acd7a9..4eb947e06 100644 --- a/doc/faq/faq.docbook +++ b/doc/faq/faq.docbook @@ -876,6 +876,19 @@ <filename>/opt/real/RealPlayer/codecs/</filename>. Restart xine then and you should be able to watch Real files/streams. </para> + <para> + Another way to get the Real codecs is to download them from the MPlayer website + <ulink url="http://www.mplayerhq.hu/design7/dload.html"> + http://www.mplayerhq.hu/design7/dload.html + </ulink>. + The package is called "essential". Unpack it and move everything you + find inside to <filename>/usr/lib/codecs</filename> and set the + <parameter>decoder.external.real_codecs_path</parameter> in your xine config file + to <filename>/usr/lib/codecs</filename> (actually you can place them + anywhere you want, e.g. someplace in your home directory, but then you'll + have to set <parameter>decoder.external.real_codecs_path</parameter> accordingly). + Restart xine then and you should be able to watch Real files/streams. + </para> </sect3> <sect3 id="realnetworkstreams"> <title>What about (live) network streams (pnm://, rtsp:// style urls)?</title> diff --git a/include/xine/buffer.h b/include/xine/buffer.h index 76302dd93..6959b1de0 100644 --- a/include/xine/buffer.h +++ b/include/xine/buffer.h @@ -391,6 +391,9 @@ struct buf_element_s { * decoder_info[2] carries denominator for display aspect ratio */ #define BUF_FLAG_ASPECT 0x0800 +/* represent the state of gapless_switch at the time buf was enqueued */ +#define BUF_FLAG_GAPLESS_SW 0x1000 + /** * \defgroup buffer_special Special buffer types: diff --git a/include/xine/xine_internal.h b/include/xine/xine_internal.h index b876030ee..c6e2b50b2 100644 --- a/include/xine/xine_internal.h +++ b/include/xine/xine_internal.h @@ -260,6 +260,7 @@ struct xine_stream_s { * layers as they cannot call xine_stop. */ uint32_t early_finish_event:1; /*< do not wait fifos get empty before sending event */ uint32_t gapless_switch:1; /*< next stream switch will be gapless */ + uint32_t keep_ao_driver_open:1; input_class_t *eject_class; demux_plugin_t *demux_plugin; diff --git a/src/combined/ffmpeg/ff_audio_decoder.c b/src/combined/ffmpeg/ff_audio_decoder.c index 893e405b1..bae0f92a4 100644 --- a/src/combined/ffmpeg/ff_audio_decoder.c +++ b/src/combined/ffmpeg/ff_audio_decoder.c @@ -332,8 +332,10 @@ static void ff_audio_decode_data (audio_decoder_t *this_gen, buf_element_t *buf) while (out < decode_buffer_size) { int stream_status = xine_get_status(this->stream); - if (stream_status == XINE_STATUS_QUIT || stream_status == XINE_STATUS_STOP) + if (stream_status == XINE_STATUS_QUIT || stream_status == XINE_STATUS_STOP) { + this->size = 0; return; + } audio_buffer = this->stream->audio_out->get_buffer (this->stream->audio_out); diff --git a/src/demuxers/demux_4xm.c b/src/demuxers/demux_4xm.c index 62146cffc..0870179fe 100644 --- a/src/demuxers/demux_4xm.c +++ b/src/demuxers/demux_4xm.c @@ -192,6 +192,10 @@ static int open_fourxm_file(demux_fourxm_t *fourxm) { const uint32_t current_track = _X_LE_32(&header[i + 8]); if (current_track + 1 > fourxm->track_count) { fourxm->track_count = current_track + 1; + if (fourxm->track_count >= UINT_MAX / sizeof(audio_track_t)) { + free(header); + return 0; + } fourxm->tracks = realloc(fourxm->tracks, fourxm->track_count * sizeof(audio_track_t)); if (!fourxm->tracks) { diff --git a/src/input/input_dvb.c b/src/input/input_dvb.c index 2c25b9863..065a57b47 100644 --- a/src/input/input_dvb.c +++ b/src/input/input_dvb.c @@ -2590,7 +2590,9 @@ static buf_element_t *dvb_plugin_read_block (input_plugin_t *this_gen, buf_element_t *buf = fifo->buffer_pool_alloc (fifo); int total_bytes; - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_file.c b/src/input/input_file.c index 387b8c4ea..26874db7c 100644 --- a/src/input/input_file.c +++ b/src/input/input_file.c @@ -169,7 +169,9 @@ static buf_element_t *file_plugin_read_block (input_plugin_t *this_gen, fifo_buf file_input_plugin_t *this = (file_input_plugin_t *) this_gen; buf_element_t *buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_gnome_vfs.c b/src/input/input_gnome_vfs.c index cb0c88877..acb63dcf6 100644 --- a/src/input/input_gnome_vfs.c +++ b/src/input/input_gnome_vfs.c @@ -122,7 +122,9 @@ gnomevfs_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t total_bytes; buf_element_t *buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_http.c b/src/input/input_http.c index 3b87e003d..a4a37c501 100644 --- a/src/input/input_http.c +++ b/src/input/input_http.c @@ -474,7 +474,9 @@ static buf_element_t *http_plugin_read_block (input_plugin_t *this_gen, fifo_buf off_t total_bytes; buf_element_t *buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_mms.c b/src/input/input_mms.c index 3d06653ea..191d1aed0 100644 --- a/src/input/input_mms.c +++ b/src/input/input_mms.c @@ -123,7 +123,9 @@ static buf_element_t *mms_plugin_read_block (input_plugin_t *this_gen, lprintf ("mms_plugin_read_block: %"PRId64" bytes...\n", todo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_net.c b/src/input/input_net.c index 964995bf6..a052d5650 100644 --- a/src/input/input_net.c +++ b/src/input/input_net.c @@ -292,7 +292,9 @@ static buf_element_t *net_plugin_read_block (input_plugin_t *this_gen, buf_element_t *buf = fifo->buffer_pool_alloc (fifo); off_t total_bytes; - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_pnm.c b/src/input/input_pnm.c index afb64e3f5..718473819 100644 --- a/src/input/input_pnm.c +++ b/src/input/input_pnm.c @@ -98,7 +98,9 @@ static buf_element_t *pnm_plugin_read_block (input_plugin_t *this_gen, lprintf ("pnm_plugin_read_block: %"PRId64" bytes...\n", todo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_pvr.c b/src/input/input_pvr.c index feacc0edf..07f5d955d 100644 --- a/src/input/input_pvr.c +++ b/src/input/input_pvr.c @@ -1209,7 +1209,9 @@ static buf_element_t *pvr_plugin_read_block (input_plugin_t *this_gen, fifo_buff } buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer(buf); return NULL; } diff --git a/src/input/input_rtp.c b/src/input/input_rtp.c index 4d1fb43a9..d8822a3e1 100644 --- a/src/input/input_rtp.c +++ b/src/input/input_rtp.c @@ -528,7 +528,9 @@ static buf_element_t *rtp_plugin_read_block (input_plugin_t *this_gen, buf_element_t *buf = fifo->buffer_pool_alloc (fifo); int total_bytes; - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_rtsp.c b/src/input/input_rtsp.c index 0522f555b..0e5af6043 100644 --- a/src/input/input_rtsp.c +++ b/src/input/input_rtsp.c @@ -98,7 +98,9 @@ static buf_element_t *rtsp_plugin_read_block (input_plugin_t *this_gen, lprintf ("rtsp_plugin_read_block: %"PRId64" bytes...\n", todo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_smb.c b/src/input/input_smb.c index b7c864f50..3c2086cc3 100644 --- a/src/input/input_smb.c +++ b/src/input/input_smb.c @@ -92,7 +92,9 @@ smb_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t total_bytes; buf_element_t *buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/input/input_stdin_fifo.c b/src/input/input_stdin_fifo.c index 2797568b6..55ac18a42 100644 --- a/src/input/input_stdin_fifo.c +++ b/src/input/input_stdin_fifo.c @@ -124,7 +124,9 @@ static buf_element_t *stdin_plugin_read_block (input_plugin_t *this_gen, fifo_bu /* stdin_input_plugin_t *this = (stdin_input_plugin_t *) this_gen; */ buf_element_t *buf = fifo->buffer_pool_alloc (fifo); - if (todo < 0 || todo > buf->size) { + if (todo > buf->max_size) + todo = buf->max_size; + if (todo < 0) { buf->free_buffer (buf); return NULL; } diff --git a/src/xine-engine/audio_decoder.c b/src/xine-engine/audio_decoder.c index 6a0688f0d..483567994 100644 --- a/src/xine-engine/audio_decoder.c +++ b/src/xine-engine/audio_decoder.c @@ -89,16 +89,18 @@ static void *audio_decoder_loop (void *stream_gen) { if (stream->audio_decoder_plugin) { lprintf ("close old decoder\n"); - + + stream->keep_ao_driver_open = !!(buf->decoder_flags & BUF_FLAG_GAPLESS_SW); _x_free_audio_decoder (stream, stream->audio_decoder_plugin); stream->audio_decoder_plugin = NULL; stream->audio_track_map_entries = 0; stream->audio_type = 0; + stream->keep_ao_driver_open = 0; } running_ticket->release(running_ticket, 0); - if( !stream->gapless_switch ) + if( !(buf->decoder_flags & BUF_FLAG_GAPLESS_SW) ) stream->metronom->handle_audio_discontinuity (stream->metronom, DISC_STREAMSTART, 0); buftype_unknown = 0; diff --git a/src/xine-engine/audio_out.c b/src/xine-engine/audio_out.c index f16f482aa..cf202cf8f 100644 --- a/src/xine-engine/audio_out.c +++ b/src/xine-engine/audio_out.c @@ -1611,7 +1611,7 @@ static void ao_close(xine_audio_port_t *this_gen, xine_stream_t *stream) { pthread_mutex_unlock(&this->streams_lock); /* close driver if no streams left */ - if (!ite && !this->grab_only && !stream->gapless_switch) { + if (!ite && !this->grab_only && !stream->keep_ao_driver_open) { xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_out: no streams left, closing driver\n"); if (this->audio_loop_running) { diff --git a/src/xine-engine/demux.c b/src/xine-engine/demux.c index ed5abb247..449b9f991 100644 --- a/src/xine-engine/demux.c +++ b/src/xine-engine/demux.c @@ -120,6 +120,16 @@ void _x_demux_flush_engine (xine_stream_t *stream) { } +struct timespec _x_compute_interval(unsigned int millisecs) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t ttimer = (uint64_t)ts.tv_sec*1000 + ts.tv_nsec/1000000 + millisecs; + ts.tv_sec = ttimer/1000; + ts.tv_nsec = (ttimer%1000)*1000000; + return ts; +} + + void _x_demux_control_newpts( xine_stream_t *stream, int64_t pts, uint32_t flags ) { buf_element_t *buf; @@ -147,19 +157,20 @@ void _x_demux_control_newpts( xine_stream_t *stream, int64_t pts, uint32_t flags */ static int demux_unstick_ao_loop (xine_stream_t *stream) { - if (!stream->audio_thread_created) +/* if (!stream->audio_thread_created) return 0; - +*/ int status = xine_get_status (stream); - if (status != XINE_STATUS_QUIT && status != XINE_STATUS_STOP) + if (status != XINE_STATUS_QUIT && status != XINE_STATUS_STOP && stream->demux_plugin->get_status(stream->demux_plugin) != DEMUX_FINISHED) return 0; - +#if 0 /* right, stream is stopped... */ audio_buffer_t *buf = stream->audio_out->get_buffer (stream->audio_out); buf->num_frames = 0; buf->stream = NULL; stream->audio_out->put_buffer (stream->audio_out, buf, stream); - +#endif + lprintf("stuck\n"); return 1; } @@ -200,24 +211,27 @@ void _x_demux_control_headers_done (xine_stream_t *stream) { stream->audio_fifo->put (stream->audio_fifo, buf_audio); pthread_mutex_unlock(&stream->demux_mutex); + unsigned int max_iterations = 0; while ((stream->header_count_audio < header_count_audio) || (stream->header_count_video < header_count_video)) { - struct timeval tv; - struct timespec ts; lprintf ("waiting for headers. v:%d %d a:%d %d\n", stream->header_count_video, header_count_video, stream->header_count_audio, header_count_audio); + + struct timespec ts = _x_compute_interval(1000); + int ret_wait; - gettimeofday(&tv, NULL); - ts.tv_sec = tv.tv_sec + 1; - ts.tv_nsec = tv.tv_usec * 1000; /* use timedwait to workaround buggy pthread broadcast implementations */ - pthread_cond_timedwait (&stream->counter_changed, &stream->counter_lock, &ts); + ret_wait = pthread_cond_timedwait (&stream->counter_changed, &stream->counter_lock, &ts); - if (demux_unstick_ao_loop (stream)) + if (ret_wait == ETIMEDOUT && demux_unstick_ao_loop (stream) && ++max_iterations > 4) { + xine_log(stream->xine, + XINE_LOG_MSG,_("Stuck in _x_demux_control_headers_done(). Taking the emergency exit\n")); + stream->emergency_brake = 1; break; + } } stream->demux_action_pending = 0; @@ -231,15 +245,18 @@ void _x_demux_control_headers_done (xine_stream_t *stream) { void _x_demux_control_start( xine_stream_t *stream ) { buf_element_t *buf; + uint32_t flags = (stream->gapless_switch) ? BUF_FLAG_GAPLESS_SW : 0; pthread_mutex_lock(&stream->demux_mutex); buf = stream->video_fifo->buffer_pool_alloc (stream->video_fifo); buf->type = BUF_CONTROL_START; + buf->decoder_flags = flags; stream->video_fifo->put (stream->video_fifo, buf); buf = stream->audio_fifo->buffer_pool_alloc (stream->audio_fifo); buf->type = BUF_CONTROL_START; + buf->decoder_flags = flags; stream->audio_fifo->put (stream->audio_fifo, buf); pthread_mutex_unlock(&stream->demux_mutex); @@ -371,13 +388,21 @@ static void *demux_loop (void *stream_gen) { pthread_mutex_unlock( &stream->demux_lock ); pthread_mutex_lock (&stream->counter_lock); + struct timespec ts; + unsigned int max_iterations = 0; + int ret_wait; while ((stream->finished_count_audio < finished_count_audio) || (stream->finished_count_video < finished_count_video)) { lprintf ("waiting for finisheds.\n"); - pthread_cond_wait (&stream->counter_changed, &stream->counter_lock); + ts = _x_compute_interval(1000); + ret_wait = pthread_cond_timedwait (&stream->counter_changed, &stream->counter_lock, &ts); - if (demux_unstick_ao_loop (stream)) + if (ret_wait == ETIMEDOUT && demux_unstick_ao_loop (stream) && ++max_iterations > 4) { + xine_log(stream->xine, + XINE_LOG_MSG,_("Stuck in demux_loop(). Taking the emergency exit\n")); + stream->emergency_brake = 1; break; + } } pthread_mutex_unlock (&stream->counter_lock); diff --git a/src/xine-engine/video_decoder.c b/src/xine-engine/video_decoder.c index 3cc4e37f7..af22ecdd0 100644 --- a/src/xine-engine/video_decoder.c +++ b/src/xine-engine/video_decoder.c @@ -161,7 +161,7 @@ static void *video_decoder_loop (void *stream_gen) { running_ticket->release(running_ticket, 0); - if( !stream->gapless_switch ) + if( !(buf->decoder_flags & BUF_FLAG_GAPLESS_SW) ) stream->metronom->handle_video_discontinuity (stream->metronom, DISC_STREAMSTART, 0); diff --git a/src/xine-engine/xine.c b/src/xine-engine/xine.c index 722e04679..86b099922 100644 --- a/src/xine-engine/xine.c +++ b/src/xine-engine/xine.c @@ -456,6 +456,7 @@ void xine_stop (xine_stream_t *stream) { static void close_internal (xine_stream_t *stream) { int i ; + int gapless_switch = stream->gapless_switch; if( stream->slave ) { xine_close( stream->slave ); @@ -466,7 +467,7 @@ static void close_internal (xine_stream_t *stream) { } } - if( !stream->gapless_switch ) { + if( !gapless_switch ) { /* make sure that other threads cannot change the speed, especially pauseing the stream */ pthread_mutex_lock(&stream->speed_change_lock); stream->ignore_speed_change = 1; @@ -482,7 +483,7 @@ static void close_internal (xine_stream_t *stream) { stop_internal( stream ); - if( !stream->gapless_switch ) { + if( !gapless_switch ) { if (stream->video_out) stream->video_out->set_property(stream->video_out, VO_PROP_DISCARD_FRAMES, 0); if (stream->audio_out) @@ -637,6 +638,7 @@ xine_stream_t *xine_stream_new (xine_t *this, stream->early_finish_event = 0; stream->delay_finish_event = 0; stream->gapless_switch = 0; + stream->keep_ao_driver_open = 0; stream->video_out = vo; if (vo) diff --git a/src/xine-engine/xine_interface.c b/src/xine-engine/xine_interface.c index 43a40f51c..a2015bb06 100644 --- a/src/xine-engine/xine_interface.c +++ b/src/xine-engine/xine_interface.c @@ -518,6 +518,9 @@ void xine_set_param (xine_stream_t *stream, int param, int value) { case XINE_PARAM_GAPLESS_SWITCH: stream->gapless_switch = !!value; + if( stream->gapless_switch && !stream->early_finish_event ) { + xprintf (stream->xine, XINE_VERBOSITY_DEBUG, "frontend possibly buggy: gapless_switch without early_finish_event\n"); + } break; default: |