Mercurial > mplayer.hg
annotate libmpdemux/demux_lmlm4.c @ 29418:7acdcd4d512a
move truehd to correct section
author | compn |
---|---|
date | Thu, 30 Jul 2009 12:08:20 +0000 |
parents | 0f1b5b68af32 |
children | cd81fce1f010 |
rev | line source |
---|---|
11590 | 1 /* |
29238
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
2 * LMLM4 MPEG4 Compression Card stream & file parser |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
3 * Copyright (C) 2003 Maxim Yevtyushkin <max@linuxmedialabs.com> |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
4 * based on SMJPEG file parser by Alex Beregszaszi |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
5 * |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
6 * This file is part of MPlayer. |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
7 * |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
8 * MPlayer is free software; you can redistribute it and/or modify |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
9 * it under the terms of the GNU General Public License as published by |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
10 * the Free Software Foundation; either version 2 of the License, or |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
11 * (at your option) any later version. |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
12 * |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
13 * MPlayer is distributed in the hope that it will be useful, |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
16 * GNU General Public License for more details. |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
17 * |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
18 * You should have received a copy of the GNU General Public License along |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
19 * with MPlayer; if not, write to the Free Software Foundation, Inc., |
d643e4643313
Add standard license header to all files in libmpdemux.
diego
parents:
25707
diff
changeset
|
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
11590 | 21 */ |
22 | |
23 #include <stdio.h> | |
24 #include <stdlib.h> | |
25 #include <unistd.h> | |
26 #include <string.h> /* strtok */ | |
27 | |
28 #include "config.h" | |
29 #include "mp_msg.h" | |
30 #include "help_mp.h" | |
31 | |
22605
4d81dbdf46b9
Add explicit location for headers from the stream/ directory.
diego
parents:
21421
diff
changeset
|
32 #include "stream/stream.h" |
11590 | 33 #include "demuxer.h" |
34 #include "stheader.h" | |
35 | |
25607
9d0b189ce1b2
Fix illegal identifiers: Names starting with __ or _ and uppercase are reserved
diego
parents:
24357
diff
changeset
|
36 typedef struct FrameInfo |
11590 | 37 { |
38 ssize_t frameSize; | |
39 ssize_t paddingSize; | |
40 int frameType; | |
41 int channelNo; | |
42 } FrameInfo; | |
43 | |
44 #define FRAMETYPE_I 0 | |
45 #define FRAMETYPE_P 1 | |
46 #define FRAMETYPE_B 2 | |
47 #define FRAMETYPE_AUDIO_MPEG1L2 4 | |
48 #define FRAMETYPE_AUDIO_ULAW 5 | |
49 #define FRAMETYPE_AUDIO_ADPCM 6 | |
50 | |
51 #define PACKET_BLOCK_SIZE 0x00000200 | |
52 #define PACKET_BLOCK_LAST 0x000001FF | |
53 #define PACKET_BLOCK_MASK 0xFFFFFE00 | |
54 | |
55 #define MAX_PACKET_SIZE 1048576 // 1 Mb | |
56 | |
57 #define STREAM_START_CODE_SIZE 4 | |
58 | |
59 /* | |
60 // codes in MSB first | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
61 static unsigned int start_code [] = |
11590 | 62 { |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
63 0xB0010000, // VISUAL_OBJECT_SEQUENCE_START_CODE |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
64 0xB6010000, // VOP_START_CODE |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
65 0x04C4FDFF, // MPEG1LAYERII_START_CODE |
11590 | 66 0x00000000 // end of start codes list |
67 }; | |
68 */ | |
69 | |
70 static int imeHeaderValid(FrameInfo *frame) | |
71 { | |
72 if ( frame->channelNo > 7 || | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
73 frame->frameSize > MAX_PACKET_SIZE || frame->frameSize <= 0) |
11590 | 74 { |
75 mp_msg(MSGT_DEMUX, MSGL_V, "Invalid packet in LMLM4 stream: ch=%d size=%d\n", frame->channelNo, frame->frameSize); | |
76 return 0; | |
77 } | |
78 switch (frame->frameType) { | |
79 case FRAMETYPE_I: | |
80 case FRAMETYPE_P: | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
81 case FRAMETYPE_B: |
11590 | 82 case FRAMETYPE_AUDIO_MPEG1L2: |
83 case FRAMETYPE_AUDIO_ULAW: | |
84 case FRAMETYPE_AUDIO_ADPCM: | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
85 break; |
11590 | 86 default: |
87 mp_msg(MSGT_DEMUX, MSGL_V, "Invalid packet in LMLM4 stream (wrong packet type %d)\n", frame->frameType); | |
88 return 0; | |
89 } | |
90 return 1; | |
91 } | |
92 | |
93 /* | |
94 int searchMPEG4Stream(demuxer_t* demuxer, IME6400Header *imeHeader) | |
95 { | |
96 void *data; | |
97 ssize_t imeHeaderSize = sizeof(IME6400Header); | |
98 ssize_t dataSize = sizeof(IME6400Header) * 3; | |
99 ssize_t ptr = imeHeaderSize * 2; | |
100 int errNo, startCodeNo; | |
101 off_t pos; | |
102 | |
103 data = malloc(dataSize); | |
104 | |
105 imeHeaderSwap(imeHeader); | |
106 memcpy(data + imeHeaderSize, imeHeader, imeHeaderSize); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
107 |
11590 | 108 // printHex(data + imeHeaderSize, imeHeaderSize); |
109 | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
110 while ((errNo = stream_read(demuxer->stream, data + imeHeaderSize * 2 , imeHeaderSize)) == imeHeaderSize) |
11590 | 111 { |
112 // printHex(data + imeHeaderSize * 2, imeHeaderSize); | |
113 | |
114 pos = stream_tell(demuxer->stream); | |
115 while (dataSize - ptr >= STREAM_START_CODE_SIZE) { | |
116 startCodeNo = 0; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
117 while (start_code[startCodeNo]) |
11590 | 118 { |
119 if (memcmp(&start_code[startCodeNo], data + ptr, STREAM_START_CODE_SIZE) == 0) // start code match | |
120 { | |
121 memcpy(imeHeader, data + ptr - imeHeaderSize, imeHeaderSize); | |
122 imeHeaderSwap(imeHeader); | |
123 if (imeHeaderValid(imeHeader)) | |
124 { | |
125 stream_seek(demuxer->stream, pos - (dataSize - ptr)); | |
126 free(data); | |
127 return 0; | |
128 } | |
129 } | |
130 startCodeNo++; | |
131 } | |
132 ptr++; | |
133 } | |
134 memcpy(data,data + imeHeaderSize, imeHeaderSize * 2); | |
135 ptr -= imeHeaderSize; | |
136 } | |
137 | |
138 free(data); | |
139 return errNo; | |
140 } | |
141 */ | |
142 | |
143 static int getFrame(demuxer_t *demuxer, FrameInfo *frameInfo) | |
144 { | |
145 unsigned int packetSize; | |
146 | |
147 frameInfo->channelNo = stream_read_word(demuxer->stream); | |
148 frameInfo->frameType = stream_read_word(demuxer->stream); | |
149 packetSize=stream_read_dword(demuxer->stream); | |
150 | |
151 if(stream_eof(demuxer->stream)){ | |
152 frameInfo->frameSize = 0; | |
153 return 0; | |
154 } | |
155 | |
156 frameInfo->frameSize = packetSize - 8; //sizeof(IME6400Header); | |
157 frameInfo->paddingSize = (packetSize & PACKET_BLOCK_LAST) ? PACKET_BLOCK_SIZE - (packetSize & PACKET_BLOCK_LAST) : 0; | |
158 | |
159 mp_msg(MSGT_DEMUX, MSGL_DBG2, "typ: %d chan: %d size: %d pad: %d\n", | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
160 frameInfo->frameType, |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
161 frameInfo->channelNo, |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
162 frameInfo->frameSize, |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
163 frameInfo->paddingSize); |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
164 |
11590 | 165 if(!imeHeaderValid(frameInfo)){ |
166 // skip this packet | |
167 stream_skip(demuxer->stream,PACKET_BLOCK_SIZE-8); | |
168 frameInfo->frameSize = 0; | |
169 return -1; | |
170 } | |
171 | |
172 return 1; | |
173 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
174 |
16175 | 175 static int lmlm4_check_file(demuxer_t* demuxer) |
11590 | 176 { |
177 FrameInfo frameInfo; | |
178 unsigned int first; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
179 |
11590 | 180 mp_msg(MSGT_DEMUX, MSGL_V, "Checking for LMLM4 Stream Format\n"); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
181 |
11590 | 182 if(getFrame(demuxer, &frameInfo)!=1){ |
183 stream_skip(demuxer->stream,-8); | |
11790 | 184 mp_msg(MSGT_DEMUX, MSGL_V, "LMLM4 Stream Format not found\n"); |
11590 | 185 return 0; |
186 } | |
187 first=stream_read_dword(demuxer->stream); | |
188 stream_skip(demuxer->stream,-12); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
189 |
11590 | 190 mp_msg(MSGT_DEMUXER,MSGL_V,"LMLM4: first=0x%08X\n",first); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
191 |
11590 | 192 switch(frameInfo.frameType){ |
193 case FRAMETYPE_AUDIO_MPEG1L2: | |
194 if( (first & 0xffe00000) != 0xffe00000 ){ | |
195 mp_msg(MSGT_DEMUXER,MSGL_V,"LMLM4: not mpeg audio\n"); | |
196 return 0; | |
197 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
198 if((4-((first>>17)&3))!=2){ |
11590 | 199 mp_msg(MSGT_DEMUXER,MSGL_V,"LMLM4: not layer-2\n"); |
200 return 0; | |
201 } | |
202 if(((first>>10)&0x3)==3){ | |
203 mp_msg(MSGT_DEMUXER,MSGL_V,"LMLM4: invalid audio sampelrate\n"); | |
204 return 0; | |
205 } | |
206 mp_msg(MSGT_DEMUXER,MSGL_V,"LMLM4: first packet is audio, header checks OK!\n"); | |
207 break; | |
208 // TODO: add checks for video header too, for case of disabled audio | |
209 } | |
210 | |
211 | |
212 // stream_reset(demuxer->stream); | |
213 mp_msg(MSGT_DEMUX, MSGL_V, "LMLM4 Stream Format found\n"); | |
214 | |
16175 | 215 return DEMUXER_TYPE_LMLM4; |
11590 | 216 } |
217 | |
218 static int video = 0; | |
219 static int frames= 0; | |
220 | |
221 // return value: | |
222 // 0 = EOF or no stream found | |
223 // 1 = successfully read a packet | |
16175 | 224 static int demux_lmlm4_fill_buffer(demuxer_t *demux, demux_stream_t *ds) |
11590 | 225 { |
226 FrameInfo frameInfo; | |
227 double pts; | |
228 int id=1; | |
229 int ret; | |
230 | |
24357 | 231 //hdr: |
11590 | 232 demux->filepos = stream_tell(demux->stream); |
17366 | 233 mp_msg(MSGT_DEMUX, MSGL_DBG2, "fpos = %"PRId64"\n", (int64_t)demux->filepos); |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
234 |
11590 | 235 ret=getFrame(demux, &frameInfo); |
236 if(ret<=0) return ret; // EOF/error | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
237 |
11590 | 238 pts=demux->video->sh ? frames*((sh_video_t*)(demux->video->sh))->frametime : 0; |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
239 |
11590 | 240 switch(frameInfo.frameType){ |
241 case FRAMETYPE_AUDIO_MPEG1L2: | |
242 mp_dbg(MSGT_DEMUX, MSGL_DBG2, "Audio Packet\n"); | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
243 if (!video) |
11590 | 244 { |
245 stream_skip(demux->stream, frameInfo.frameSize + frameInfo.paddingSize); | |
246 mp_msg(MSGT_DEMUX, MSGL_V, "Skip Audio Packet\n"); | |
247 return -1; //goto hdr; | |
248 } | |
249 if(demux->audio->id==-1){ | |
250 if(!demux->a_streams[id]) new_sh_audio(demux,id); | |
251 demux->audio->id=id; | |
252 demux->audio->sh=demux->a_streams[id]; | |
253 ((sh_audio_t*)(demux->audio->sh))->format=0x50; // mpeg audio layer 1/2 | |
254 } | |
255 if(demux->audio->id==id) | |
256 ds_read_packet(demux->audio, demux->stream, frameInfo.frameSize, | |
257 pts, demux->filepos, 0); | |
258 else | |
259 stream_skip(demux->stream,frameInfo.frameSize); | |
260 break; | |
261 case FRAMETYPE_I: | |
262 if (!video) { | |
263 video = 1; | |
264 mp_dbg(MSGT_DEMUX, MSGL_DBG2, "First Video Packet\n"); | |
265 } | |
266 case FRAMETYPE_P: | |
267 frames=(frames+1)&(1024*1024-1); // wrap around at 4 hrs to avoid inaccurate float calculations | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
268 if (!video) |
11590 | 269 { |
270 stream_skip(demux->stream, frameInfo.frameSize + frameInfo.paddingSize); | |
271 mp_msg(MSGT_DEMUX, MSGL_V, "Skip Video P Packet\n"); | |
272 return -1; //goto hdr; | |
273 } | |
274 mp_dbg(MSGT_DEMUX, MSGL_DBG2, "Video Packet\n"); | |
275 if(demux->video->id==-1){ | |
276 if(!demux->v_streams[id]) new_sh_video(demux,id); | |
277 demux->video->id=id; | |
278 demux->video->sh=demux->v_streams[id]; | |
279 ((sh_video_t*)(demux->video->sh))->format=0x10000004; // mpeg4-ES | |
280 } | |
281 if(demux->video->id==id) | |
282 ds_read_packet(demux->video, demux->stream, frameInfo.frameSize, | |
283 pts, demux->filepos, 0); | |
284 break; | |
285 default: | |
286 stream_skip(demux->stream,frameInfo.frameSize); | |
287 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
288 |
11590 | 289 stream_skip(demux->stream, frameInfo.paddingSize); |
290 | |
291 return 1; | |
292 } | |
293 | |
16175 | 294 static demuxer_t* demux_open_lmlm4(demuxer_t* demuxer){ |
295 sh_audio_t *sh_audio=NULL; | |
296 sh_video_t *sh_video=NULL; | |
11590 | 297 |
298 #if 0 | |
299 sh_video_t* sh_video; | |
300 sh_audio_t* sh_audio; | |
301 unsigned int htype = 0, hleng; | |
302 int i = 0; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
303 |
11590 | 304 sh_video = new_sh_video(demuxer, 0); |
305 demuxer->video->sh = sh_video; | |
306 sh_video->ds = demuxer->video; | |
307 sh_video->disp_w = 640; | |
308 sh_video->disp_h = 480; | |
309 sh_video->format = mmioFOURCC('D','I','V','X'); | |
310 | |
311 sh_video->bih = malloc(sizeof(BITMAPINFOHEADER)); | |
312 memset(sh_video->bih, 0, sizeof(BITMAPINFOHEADER)); | |
313 | |
314 /* these are false values */ | |
315 sh_video->bih->biSize = 40; | |
316 sh_video->bih->biWidth = sh_video->disp_w; | |
317 sh_video->bih->biHeight = sh_video->disp_h; | |
318 sh_video->bih->biPlanes = 3; | |
319 sh_video->bih->biBitCount = 16; | |
320 sh_video->bih->biCompression = sh_video->format; | |
321 sh_video->bih->biSizeImage = sh_video->disp_w*sh_video->disp_h; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
322 |
11590 | 323 sh_audio = new_sh_audio(demuxer, 0); |
324 demuxer->audio->sh = sh_audio; | |
325 sh_audio->ds = demuxer->audio; | |
326 | |
327 sh_audio->wf = malloc(sizeof(WAVEFORMATEX)); | |
328 memset(sh_audio->wf, 0, sizeof(WAVEFORMATEX)); | |
329 | |
330 sh_audio->samplerate = 48000; | |
331 sh_audio->wf->wBitsPerSample = 16; | |
332 sh_audio->channels = 2; | |
333 sh_audio->format = 0x50; | |
334 sh_audio->wf->wFormatTag = sh_audio->format; | |
335 sh_audio->wf->nChannels = sh_audio->channels; | |
336 sh_audio->wf->nSamplesPerSec = sh_audio->samplerate; | |
337 sh_audio->wf->nAvgBytesPerSec = sh_audio->wf->nChannels* | |
338 sh_audio->wf->wBitsPerSample*sh_audio->wf->nSamplesPerSec/8; | |
339 sh_audio->wf->nBlockAlign = sh_audio->channels *2; | |
340 sh_audio->wf->cbSize = 0; | |
341 | |
342 #endif | |
343 | |
344 demuxer->seekable = 0; | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
345 |
16175 | 346 if(!ds_fill_buffer(demuxer->video)){ |
347 mp_msg(MSGT_DEMUXER,MSGL_INFO,"LMLM4: " MSGTR_MissingVideoStream); | |
348 demuxer->video->sh=NULL; | |
349 } else { | |
350 sh_video=demuxer->video->sh;sh_video->ds=demuxer->video; | |
351 } | |
352 if(demuxer->audio->id!=-2) { | |
353 if(!ds_fill_buffer(demuxer->audio)){ | |
354 mp_msg(MSGT_DEMUXER,MSGL_INFO,"LMLM4: " MSGTR_MissingAudioStream); | |
355 demuxer->audio->sh=NULL; | |
356 } else { | |
357 sh_audio=demuxer->audio->sh;sh_audio->ds=demuxer->audio; | |
358 } | |
29263
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
359 } |
0f1b5b68af32
whitespace cosmetics: Remove all trailing whitespace.
diego
parents:
29238
diff
changeset
|
360 |
16175 | 361 return demuxer; |
11590 | 362 } |
363 | |
16175 | 364 static void demux_close_lmlm4(demuxer_t *demuxer) |
11590 | 365 { |
366 // printf("Close LMLM4 Stream\n"); | |
367 return; | |
368 } | |
16175 | 369 |
370 | |
25707
d4fe6e23283e
Make all demuxer_desc_t const, thus moving them to .rodata
reimar
parents:
25607
diff
changeset
|
371 const demuxer_desc_t demuxer_desc_lmlm4 = { |
16175 | 372 "LMLM4 MPEG4 Compression Card stream demuxer", |
373 "lmlm4", | |
374 "RAW LMLM4", | |
375 "Maxim Yevtyushkin", | |
376 "", | |
377 DEMUXER_TYPE_LMLM4, | |
378 0, // unsafe autodetect | |
379 lmlm4_check_file, | |
380 demux_lmlm4_fill_buffer, | |
381 demux_open_lmlm4, | |
382 demux_close_lmlm4, | |
383 NULL, | |
384 NULL | |
385 }; |