]>
Commit | Line | Data |
---|---|---|
4abcd842 KS |
1 | use nihav_core::codecs::*; |
2 | use nihav_core::io::byteio::*; | |
3 | use std::str::FromStr; | |
4 | ||
5 | const ADAPT_TABLE: [i32; 16] = [ | |
c8db9313 | 6 | 230, 230, 230, 230, 307, 409, 512, 614, |
4abcd842 KS |
7 | 768, 614, 512, 409, 307, 230, 230, 230 |
8 | ]; | |
9 | const ADAPT_COEFFS: [[i32; 2]; 7] = [ | |
10 | [ 256, 0 ], [ 512, -256 ], [ 0, 0 ], [ 192, 64 ], | |
11 | [ 240, 0 ], [ 460, -208 ], [ 392, -232 ] | |
12 | ]; | |
13 | ||
14 | #[derive(Default)] | |
15 | struct Predictor { | |
16 | sample1: i32, | |
17 | sample2: i32, | |
18 | delta: i32, | |
19 | coef1: i32, | |
20 | coef2: i32, | |
21 | } | |
22 | ||
23 | impl Predictor { | |
24 | fn expand_nibble(&mut self, nibble: u8) -> i16 { | |
25 | let mul = if (nibble & 8) == 0 { i32::from(nibble) } else { i32::from(nibble) - 16 }; | |
dab59886 KS |
26 | let pred = self.calc_pred() + self.delta.wrapping_mul(mul); |
27 | self.update(pred.max(-0x8000).min(0x7FFF)); | |
4abcd842 KS |
28 | self.delta = (ADAPT_TABLE[nibble as usize].wrapping_mul(self.delta) >> 8).max(16); |
29 | self.sample1 as i16 | |
30 | } | |
dab59886 KS |
31 | fn calc_pred(&self) -> i32 { |
32 | self.sample1.wrapping_mul(self.coef1).wrapping_add(self.sample2.wrapping_mul(self.coef2)) >> 8 | |
33 | } | |
34 | fn update(&mut self, new_samp: i32) { | |
35 | self.sample2 = self.sample1; | |
36 | self.sample1 = new_samp; | |
37 | } | |
4abcd842 KS |
38 | } |
39 | ||
40 | struct MSADPCMDecoder { | |
41 | ainfo: NAAudioInfo, | |
42 | chmap: NAChannelMap, | |
43 | adapt_coeffs: Vec<[i32; 2]>, | |
44 | block_len: usize, | |
45 | block_samps: usize, | |
46 | } | |
47 | ||
48 | impl MSADPCMDecoder { | |
49 | fn new() -> Self { | |
50 | Self { | |
51 | ainfo: NAAudioInfo::new(0, 1, SND_S16P_FORMAT, 0), | |
52 | chmap: NAChannelMap::new(), | |
53 | adapt_coeffs: Vec::with_capacity(7), | |
54 | block_len: 0, | |
55 | block_samps: 0, | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | impl NADecoder for MSADPCMDecoder { | |
4efceb69 | 61 | #[allow(clippy::int_plus_one)] |
4abcd842 KS |
62 | fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { |
63 | if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { | |
64 | self.block_len = ainfo.get_block_len(); | |
65 | let channels = ainfo.get_channels() as usize; | |
66 | validate!(channels == 2 || channels == 1); | |
67 | validate!(self.block_len >= 7 * channels + 1); | |
68 | self.block_samps = (self.block_len / channels - 7) * 2 + 2; | |
69 | self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels as u8, SND_S16P_FORMAT, self.block_samps); | |
70 | self.chmap = NAChannelMap::from_str(if channels == 1 { "C" } else { "L,R" }).unwrap(); | |
71 | self.adapt_coeffs.truncate(0); | |
72 | if let Some(ref buf) = info.get_extradata() { | |
73 | validate!(buf.len() >= 6); | |
74 | validate!((buf.len() & 3) == 0); | |
75 | let mut mr = MemoryReader::new_read(buf.as_slice()); | |
76 | let mut br = ByteReader::new(&mut mr); | |
77 | let _smth = br.read_u16le()?; | |
78 | let ncoeffs = br.read_u16le()? as usize; | |
79 | validate!(buf.len() == ncoeffs * 4 + 4); | |
80 | ||
81 | for _ in 0..ncoeffs { | |
82 | let pair = [ | |
83 | i32::from(br.read_u16le()? as i16), | |
84 | i32::from(br.read_u16le()? as i16)]; | |
85 | self.adapt_coeffs.push(pair); | |
86 | } | |
87 | } else { | |
88 | self.adapt_coeffs.extend_from_slice(&ADAPT_COEFFS); | |
89 | } | |
90 | Ok(()) | |
91 | } else { | |
92 | Err(DecoderError::InvalidData) | |
93 | } | |
94 | } | |
95 | fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> { | |
96 | let info = pkt.get_stream().get_info(); | |
97 | if let NACodecTypeInfo::Audio(_) = info.get_properties() { | |
98 | let pktbuf = pkt.get_buffer(); | |
99 | let channels = self.chmap.num_channels(); | |
4efceb69 | 100 | validate!(!pktbuf.is_empty() && (pktbuf.len() % self.block_len) == 0); |
4abcd842 KS |
101 | let nblocks = pktbuf.len() / self.block_len; |
102 | let nsamples = nblocks * self.block_samps; | |
103 | let abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?; | |
104 | let mut adata = abuf.get_abuf_i16().unwrap(); | |
105 | let mut off = [adata.get_offset(0), adata.get_offset(1)]; | |
106 | let dst = adata.get_data_mut().unwrap(); | |
107 | ||
108 | let mut pred = [Predictor::default(), Predictor::default()]; | |
109 | ||
110 | for blk in pktbuf.chunks(self.block_len) { | |
111 | let mut mr = MemoryReader::new_read(blk); | |
112 | let mut br = ByteReader::new(&mut mr); | |
113 | for ch in 0..channels { | |
114 | let coef_idx = br.read_byte()? as usize; | |
115 | validate!(coef_idx < self.adapt_coeffs.len()); | |
116 | pred[ch].coef1 = self.adapt_coeffs[coef_idx][0]; | |
117 | pred[ch].coef2 = self.adapt_coeffs[coef_idx][1]; | |
118 | } | |
119 | for ch in 0..channels { | |
120 | pred[ch].delta = i32::from(br.read_u16le()?); | |
121 | } | |
122 | for ch in 0..channels { | |
123 | let samp = br.read_u16le()? as i16; | |
dab59886 | 124 | pred[ch].sample2 = i32::from(samp); |
4abcd842 KS |
125 | } |
126 | for ch in 0..channels { | |
127 | let samp = br.read_u16le()? as i16; | |
dab59886 KS |
128 | pred[ch].sample1 = i32::from(samp); |
129 | } | |
130 | for ch in 0..channels { | |
131 | dst[off[ch]] = pred[ch].sample2 as i16; | |
132 | dst[off[ch] + 1] = pred[ch].sample1 as i16; | |
133 | off[ch] += 2; | |
4abcd842 KS |
134 | } |
135 | if channels == 1 { | |
136 | while br.left() > 0 { | |
137 | let idx = br.read_byte()?; | |
138 | dst[off[0]] = pred[0].expand_nibble(idx >> 4); | |
139 | off[0] += 1; | |
140 | dst[off[0]] = pred[0].expand_nibble(idx & 0xF); | |
141 | off[0] += 1; | |
142 | } | |
143 | } else { | |
144 | while br.left() > 0 { | |
145 | let idx = br.read_byte()?; | |
146 | dst[off[0]] = pred[0].expand_nibble(idx >> 4); | |
147 | off[0] += 1; | |
148 | dst[off[1]] = pred[1].expand_nibble(idx & 0xF); | |
149 | off[1] += 1; | |
150 | } | |
151 | } | |
152 | } | |
153 | let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf); | |
154 | frm.set_duration(Some(nsamples as u64)); | |
155 | frm.set_keyframe(false); | |
156 | Ok(frm.into_ref()) | |
157 | } else { | |
158 | Err(DecoderError::InvalidData) | |
159 | } | |
160 | } | |
161 | fn flush(&mut self) { | |
162 | } | |
163 | } | |
164 | ||
7d57ae2f KS |
165 | impl NAOptionHandler for MSADPCMDecoder { |
166 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
167 | fn set_options(&mut self, _options: &[NAOption]) { } | |
168 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
169 | } | |
170 | ||
4abcd842 KS |
171 | pub fn get_decoder() -> Box<dyn NADecoder + Send> { |
172 | Box::new(MSADPCMDecoder::new()) | |
173 | } | |
174 | ||
dab59886 KS |
175 | #[derive(Default)] |
176 | struct MSADPCMEncoder { | |
177 | stream: Option<NAStreamRef>, | |
178 | samples: Vec<i16>, | |
179 | block_len: usize, | |
180 | channels: usize, | |
181 | flush: bool, | |
182 | srate: u32, | |
183 | } | |
184 | ||
185 | const DEFAULT_BLOCK_LEN: usize = 256; | |
186 | ||
187 | impl MSADPCMEncoder { | |
188 | fn new() -> Self { Self::default() } | |
189 | fn encode_packet(&mut self) -> EncoderResult<NAPacket> { | |
4efceb69 | 190 | if self.samples.is_empty() { |
dab59886 KS |
191 | return Err(EncoderError::TryAgain); |
192 | } | |
193 | let len = (self.samples.len() / self.channels).min(self.block_len); | |
194 | if len < self.block_len && !self.flush { | |
195 | return Err(EncoderError::TryAgain); | |
196 | } | |
197 | if len < 2 { | |
198 | self.flush = false; | |
199 | return Err(EncoderError::TryAgain); | |
200 | } | |
201 | ||
202 | let mut dbuf = vec![0u8; Self::calc_block_size(len, self.channels)]; | |
203 | let mut mw = MemoryWriter::new_write(dbuf.as_mut_slice()); | |
204 | let mut bw = ByteWriter::new(&mut mw); | |
205 | ||
206 | let mut best_idx = [0usize; 2]; | |
207 | for ch in 0..self.channels { | |
208 | let mut best_dist = std::i64::MAX; | |
209 | for i in 0..ADAPT_COEFFS.len() { | |
210 | let dist = self.calc_dist(ch, i, len); | |
211 | if dist < best_dist { | |
212 | best_dist = dist; | |
213 | best_idx[ch] = i; | |
214 | } | |
215 | } | |
216 | bw.write_byte(best_idx[ch] as u8)?; | |
217 | } | |
218 | let mut dec = [Predictor::default(), Predictor::default()]; | |
219 | for ch in 0..self.channels { | |
220 | dec[ch].sample1 = i32::from(self.samples[ch + self.channels]); | |
221 | dec[ch].sample2 = i32::from(self.samples[ch]); | |
222 | dec[ch].coef1 = ADAPT_COEFFS[best_idx[ch]][0]; | |
223 | dec[ch].coef2 = ADAPT_COEFFS[best_idx[ch]][1]; | |
224 | if len > 2 { | |
225 | let pred = dec[ch].calc_pred(); | |
226 | dec[ch].delta = ((i32::from(self.samples[ch + self.channels * 2]) - pred).abs() / 4).max(16); | |
227 | } else { | |
228 | dec[ch].delta = 16; | |
229 | } | |
230 | } | |
231 | for ch in 0..self.channels { | |
232 | bw.write_u16le(dec[ch].delta as u16)?; | |
233 | } | |
234 | for ch in 0..self.channels { | |
235 | bw.write_u16le(dec[ch].sample1 as u16)?; | |
236 | } | |
237 | for ch in 0..self.channels { | |
238 | bw.write_u16le(dec[ch].sample2 as u16)?; | |
239 | } | |
240 | if self.channels == 1 { | |
241 | for samps in self.samples.chunks(2).skip(1).take(len/2 - 1) { | |
242 | let diff = i32::from(samps[0]) - dec[0].calc_pred(); | |
243 | let nib0 = Self::calculate_mul(dec[0].delta, diff); | |
244 | dec[0].expand_nibble(nib0); | |
245 | let diff = i32::from(samps[1]) - dec[0].calc_pred(); | |
246 | let nib1 = Self::calculate_mul(dec[0].delta, diff); | |
247 | dec[0].expand_nibble(nib1); | |
248 | bw.write_byte(nib0 * 16 + nib1)?; | |
249 | } | |
250 | } else { | |
251 | for samps in self.samples.chunks(2).skip(2).take(len - 2) { | |
252 | let diff = i32::from(samps[0]) - dec[0].calc_pred(); | |
253 | let nib0 = Self::calculate_mul(dec[0].delta, diff); | |
254 | dec[0].expand_nibble(nib0); | |
255 | let diff = i32::from(samps[1]) - dec[1].calc_pred(); | |
256 | let nib1 = Self::calculate_mul(dec[1].delta, diff); | |
257 | dec[1].expand_nibble(nib1); | |
258 | bw.write_byte(nib0 * 16 + nib1)?; | |
259 | } | |
260 | } | |
261 | self.samples.drain(..len * self.channels); | |
262 | drop(bw); | |
263 | let ts = NATimeInfo::new(None, None, Some(1), 1, self.srate); | |
264 | Ok(NAPacket::new(self.stream.clone().unwrap(), ts, true, dbuf)) | |
265 | } | |
266 | fn calc_dist(&self, ch: usize, idx: usize, len: usize) -> i64 { | |
267 | let mut dist = 0; | |
268 | let mut dec = Predictor { | |
269 | sample2: i32::from(self.samples[ch]), | |
270 | sample1: i32::from(self.samples[ch + self.channels]), | |
271 | coef1: ADAPT_COEFFS[idx][0], | |
272 | coef2: ADAPT_COEFFS[idx][1], | |
273 | delta: 16, | |
274 | }; | |
275 | if self.channels == 1 { | |
276 | for samp in self.samples.iter().skip(2).take(len - 2) { | |
277 | let pred = dec.calc_pred(); | |
278 | dec.update(pred); | |
279 | let diff = i64::from(*samp) - i64::from(pred); | |
280 | dist += diff * diff; | |
281 | } | |
282 | } else { | |
283 | for samp in self.samples.chunks(2).skip(2).take(len - 2) { | |
284 | let pred = dec.calc_pred(); | |
285 | dec.update(pred); | |
286 | let diff = i64::from(samp[ch]) - i64::from(pred); | |
287 | dist += diff * diff; | |
288 | } | |
289 | } | |
290 | dist | |
291 | } | |
292 | fn calculate_mul(delta: i32, diff: i32) -> u8 { | |
293 | ((diff / delta).max(-8).min(7) & 0xF) as u8 | |
294 | } | |
295 | fn calc_block_size(nsamps: usize, channels: usize) -> usize { | |
296 | (nsamps - 2) * channels / 2 + 7 * channels | |
297 | } | |
298 | } | |
299 | ||
300 | impl NAEncoder for MSADPCMEncoder { | |
301 | fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> { | |
302 | match encinfo.format { | |
303 | NACodecTypeInfo::None => { | |
304 | let mut ofmt = EncodeParameters::default(); | |
305 | ofmt.format = NACodecTypeInfo::Audio(NAAudioInfo::new(0, 1, SND_S16_FORMAT, DEFAULT_BLOCK_LEN)); | |
4efceb69 | 306 | Ok(ofmt) |
dab59886 | 307 | }, |
4efceb69 | 308 | NACodecTypeInfo::Video(_) => Err(EncoderError::FormatError), |
dab59886 KS |
309 | NACodecTypeInfo::Audio(ainfo) => { |
310 | let mut outinfo = ainfo; | |
311 | outinfo.channels = outinfo.channels.min(2); | |
312 | if outinfo.format != SND_S16P_FORMAT && outinfo.format != SND_S16_FORMAT { | |
313 | outinfo.format = SND_S16_FORMAT; | |
314 | } | |
315 | if outinfo.block_len == 0 { | |
316 | outinfo.block_len = DEFAULT_BLOCK_LEN; | |
317 | } | |
318 | if outinfo.block_len < 2 { | |
319 | outinfo.block_len = 2; | |
320 | } | |
321 | if (outinfo.channels == 1) && ((outinfo.block_len & 1) == 1) { | |
322 | outinfo.block_len += 1; | |
323 | } | |
d722ffe9 | 324 | let mut ofmt = *encinfo; |
dab59886 | 325 | ofmt.format = NACodecTypeInfo::Audio(outinfo); |
4efceb69 | 326 | Ok(ofmt) |
dab59886 | 327 | } |
4efceb69 | 328 | } |
dab59886 KS |
329 | } |
330 | fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> { | |
331 | match encinfo.format { | |
332 | NACodecTypeInfo::None => Err(EncoderError::FormatError), | |
333 | NACodecTypeInfo::Video(_) => Err(EncoderError::FormatError), | |
334 | NACodecTypeInfo::Audio(ainfo) => { | |
335 | if ainfo.format != SND_S16P_FORMAT && ainfo.format != SND_S16_FORMAT { | |
336 | return Err(EncoderError::FormatError); | |
337 | } | |
338 | if ainfo.channels != 1 && ainfo.channels != 2 { | |
339 | return Err(EncoderError::FormatError); | |
340 | } | |
341 | if ainfo.block_len < 2 || ((ainfo.block_len * (ainfo.channels as usize)) & 1) != 0 { | |
342 | return Err(EncoderError::FormatError); | |
343 | } | |
344 | self.channels = ainfo.channels as usize; | |
345 | self.block_len = ainfo.block_len; | |
346 | ||
347 | let soniton = NASoniton::new(4, 0); | |
348 | let out_ainfo = NAAudioInfo::new(ainfo.sample_rate, ainfo.channels, soniton, Self::calc_block_size(self.block_len, self.channels)); | |
349 | let info = NACodecInfo::new("ms-adpcm", NACodecTypeInfo::Audio(out_ainfo), None); | |
2ff56201 KS |
350 | let mut stream = NAStream::new(StreamType::Audio, stream_id, info.clone(), self.block_len as u32, ainfo.sample_rate); |
351 | stream.set_num(stream_id as usize); | |
352 | let stream = stream.into_ref(); | |
dab59886 KS |
353 | |
354 | self.stream = Some(stream.clone()); | |
355 | self.samples = Vec::with_capacity(self.block_len * self.channels); | |
356 | self.srate = ainfo.sample_rate; | |
357 | self.flush = false; | |
c8db9313 | 358 | |
dab59886 KS |
359 | Ok(stream) |
360 | }, | |
361 | } | |
362 | } | |
363 | fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { | |
364 | let buf = frm.get_buffer(); | |
365 | if let Some(ref abuf) = buf.get_abuf_i16() { | |
366 | let src = abuf.get_data(); | |
367 | let len = abuf.get_length(); | |
368 | let ch = abuf.get_chmap().num_channels(); | |
369 | if abuf.get_step() > 1 || ch == 1 { | |
370 | self.samples.extend(src.iter().take(len * ch)); | |
371 | } else { | |
372 | let (src0, src1) = src.split_at(abuf.get_stride()); | |
373 | self.samples.reserve(len * 2); | |
374 | for (s0, s1) in src0.iter().take(len).zip(src1.iter()) { | |
375 | self.samples.push(*s0); | |
376 | self.samples.push(*s1); | |
377 | } | |
378 | } | |
379 | Ok(()) | |
380 | } else { | |
381 | Err(EncoderError::InvalidParameters) | |
382 | } | |
383 | } | |
384 | fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> { | |
385 | if let Ok(pkt) = self.encode_packet() { | |
386 | Ok(Some(pkt)) | |
387 | } else { | |
388 | Ok(None) | |
389 | } | |
390 | } | |
391 | fn flush(&mut self) -> EncoderResult<()> { | |
392 | self.flush = true; | |
393 | Ok(()) | |
394 | } | |
395 | } | |
396 | ||
20766f15 KS |
397 | impl NAOptionHandler for MSADPCMEncoder { |
398 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
399 | fn set_options(&mut self, _options: &[NAOption]) { } | |
400 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
401 | } | |
402 | ||
dab59886 KS |
403 | pub fn get_encoder() -> Box<dyn NAEncoder + Send> { |
404 | Box::new(MSADPCMEncoder::new()) | |
405 | } | |
406 | ||
4abcd842 KS |
407 | #[cfg(test)] |
408 | mod test { | |
dab59886 KS |
409 | use nihav_core::codecs::*; |
410 | use nihav_core::demuxers::*; | |
411 | use nihav_core::muxers::*; | |
4abcd842 | 412 | use nihav_codec_support::test::dec_video::*; |
dab59886 KS |
413 | use nihav_codec_support::test::enc_video::*; |
414 | use crate::*; | |
415 | use nihav_commonfmt::*; | |
416 | #[cfg(feature="decoder_ms_adpcm")] | |
4abcd842 | 417 | #[test] |
dab59886 | 418 | fn test_ms_adpcm_decoder() { |
4abcd842 KS |
419 | let mut dmx_reg = RegisteredDemuxers::new(); |
420 | generic_register_all_demuxers(&mut dmx_reg); | |
421 | let mut dec_reg = RegisteredDecoders::new(); | |
78fb6560 | 422 | ms_register_all_decoders(&mut dec_reg); |
4abcd842 KS |
423 | |
424 | test_decoding("avi", "ms-adpcm", "assets/MS/dance.avi", None, &dmx_reg, &dec_reg, | |
0be53e58 | 425 | ExpectedTestResult::MD5([0xf5e3fc84, 0xbcabc11c, 0x33c6874e, 0xe05ecd14])); |
4abcd842 | 426 | } |
dab59886 KS |
427 | #[cfg(feature="encoder_ms_adpcm")] |
428 | #[test] | |
429 | fn test_ms_adpcm_encoder() { | |
430 | let mut dmx_reg = RegisteredDemuxers::new(); | |
431 | generic_register_all_demuxers(&mut dmx_reg); | |
432 | let mut dec_reg = RegisteredDecoders::new(); | |
78fb6560 KS |
433 | generic_register_all_decoders(&mut dec_reg); |
434 | ms_register_all_decoders(&mut dec_reg); | |
dab59886 KS |
435 | let mut mux_reg = RegisteredMuxers::new(); |
436 | generic_register_all_muxers(&mut mux_reg); | |
437 | let mut enc_reg = RegisteredEncoders::new(); | |
438 | ms_register_all_encoders(&mut enc_reg); | |
439 | ||
440 | let dec_config = DecoderTestParams { | |
441 | demuxer: "avi", | |
442 | in_name: "assets/Indeo/laser05.avi", | |
443 | stream_type: StreamType::Audio, | |
444 | limit: None, | |
445 | dmx_reg, dec_reg, | |
446 | }; | |
447 | let enc_config = EncoderTestParams { | |
448 | muxer: "wav", | |
449 | enc_name: "ms-adpcm", | |
450 | out_name: "msadpcm.wav", | |
451 | mux_reg, enc_reg, | |
452 | }; | |
453 | let dst_ainfo = NAAudioInfo { | |
454 | sample_rate: 0, | |
455 | channels: 0, | |
456 | format: SND_S16_FORMAT, | |
457 | block_len: 128, | |
458 | }; | |
459 | let enc_params = EncodeParameters { | |
460 | format: NACodecTypeInfo::Audio(dst_ainfo), | |
461 | quality: 0, | |
462 | bitrate: 0, | |
463 | tb_num: 0, | |
464 | tb_den: 0, | |
465 | flags: 0, | |
466 | }; | |
6dc8bcd9 KS |
467 | test_encoding_md5(&dec_config, &enc_config, enc_params, |
468 | &[0xe1591a1e, 0x816d0239, 0x4cc42291, 0x4e6b69cb]); | |
dab59886 | 469 | } |
4abcd842 | 470 | } |