cros_codecs/codec/h264/
synthesizer.rs

1// Copyright 2024 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use std::fmt;
5use std::io::Write;
6
7use crate::codec::h264::nalu_writer::NaluWriter;
8use crate::codec::h264::nalu_writer::NaluWriterError;
9use crate::codec::h264::parser::HrdParams;
10use crate::codec::h264::parser::NaluType;
11use crate::codec::h264::parser::Pps;
12use crate::codec::h264::parser::Sps;
13use crate::codec::h264::parser::DEFAULT_4X4_INTER;
14use crate::codec::h264::parser::DEFAULT_4X4_INTRA;
15use crate::codec::h264::parser::DEFAULT_8X8_INTER;
16use crate::codec::h264::parser::DEFAULT_8X8_INTRA;
17
18mod private {
19    pub trait NaluStruct {}
20}
21
22impl private::NaluStruct for Sps {}
23
24impl private::NaluStruct for Pps {}
25
26#[derive(Debug)]
27pub enum SynthesizerError {
28    Unsupported,
29    NaluWriter(NaluWriterError),
30}
31
32impl fmt::Display for SynthesizerError {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        match self {
35            SynthesizerError::Unsupported => write!(f, "tried to synthesize unsupported settings"),
36            SynthesizerError::NaluWriter(x) => write!(f, "{}", x.to_string()),
37        }
38    }
39}
40
41impl From<NaluWriterError> for SynthesizerError {
42    fn from(err: NaluWriterError) -> Self {
43        SynthesizerError::NaluWriter(err)
44    }
45}
46
47pub type SynthesizerResult<T> = Result<T, SynthesizerError>;
48
49/// A helper to output typed NALUs to [`std::io::Write`] using [`NaluWriter`].
50pub struct Synthesizer<'n, N: private::NaluStruct, W: Write> {
51    writer: NaluWriter<W>,
52    nalu: &'n N,
53}
54
55/// Extended Sample Aspect Ratio - H.264 Table E-1
56const EXTENDED_SAR: u8 = 255;
57
58impl<N: private::NaluStruct, W: Write> Synthesizer<'_, N, W> {
59    fn u<T: Into<u32>>(&mut self, bits: usize, value: T) -> SynthesizerResult<()> {
60        self.writer.write_u(bits, value)?;
61        Ok(())
62    }
63
64    fn f<T: Into<u32>>(&mut self, bits: usize, value: T) -> SynthesizerResult<()> {
65        self.writer.write_f(bits, value)?;
66        Ok(())
67    }
68
69    fn ue<T: Into<u32>>(&mut self, value: T) -> SynthesizerResult<()> {
70        self.writer.write_ue(value)?;
71        Ok(())
72    }
73
74    fn se<T: Into<i32>>(&mut self, value: T) -> SynthesizerResult<()> {
75        self.writer.write_se(value)?;
76        Ok(())
77    }
78
79    fn scaling_list(&mut self, list: &[u8], default: &[u8]) -> SynthesizerResult<()> {
80        // H.264 7.3.2.1.1.1
81        if list == default {
82            self.se(-8)?;
83            return Ok(());
84        }
85
86        // The number of list values we want to encode.
87        let mut run = list.len();
88
89        // Check how many values at the end of the matrix are the same,
90        // so we can save on encoding those.
91        for j in (1..list.len()).rev() {
92            if list[j - 1] != list[j] {
93                break;
94            }
95            run -= 1;
96        }
97
98        // Encode deltas.
99        let mut last_scale = 8;
100        for scale in &list[0..run] {
101            let delta_scale = *scale as i32 - last_scale;
102            self.se(delta_scale)?;
103            last_scale = *scale as i32;
104        }
105
106        // Didn't encode all values, encode -|last_scale| to set decoder's
107        // |next_scale| (H.264 7.3.2.1.1.1) to zero, i.e. decoder should repeat
108        // last values in matrix.
109        if run < list.len() {
110            self.se(-last_scale)?;
111        }
112
113        Ok(())
114    }
115
116    fn default_scaling_list(i: usize) -> &'static [u8] {
117        // H.264 Table 7-2
118        match i {
119            0 => &DEFAULT_4X4_INTRA[..],
120            1 => &DEFAULT_4X4_INTRA[..],
121            2 => &DEFAULT_4X4_INTRA[..],
122            3 => &DEFAULT_4X4_INTER[..],
123            4 => &DEFAULT_4X4_INTER[..],
124            5 => &DEFAULT_4X4_INTER[..],
125            6 => &DEFAULT_8X8_INTRA[..],
126            7 => &DEFAULT_8X8_INTER[..],
127            8 => &DEFAULT_8X8_INTRA[..],
128            9 => &DEFAULT_8X8_INTER[..],
129            10 => &DEFAULT_8X8_INTRA[..],
130            11 => &DEFAULT_8X8_INTER[..],
131            _ => unreachable!(),
132        }
133    }
134
135    fn rbsp_trailing_bits(&mut self) -> SynthesizerResult<()> {
136        self.f(1, 1u32)?;
137
138        while !self.writer.aligned() {
139            self.f(1, 0u32)?;
140        }
141
142        Ok(())
143    }
144}
145
146impl<'n, W: Write> Synthesizer<'n, Sps, W> {
147    pub fn synthesize(
148        ref_idc: u8,
149        sps: &'n Sps,
150        writer: W,
151        ep_enabled: bool,
152    ) -> SynthesizerResult<()> {
153        let mut s = Self { writer: NaluWriter::<W>::new(writer, ep_enabled), nalu: sps };
154
155        s.writer.write_header(ref_idc, NaluType::Sps as u8)?;
156        s.seq_parameter_set_data()?;
157        s.rbsp_trailing_bits()
158    }
159
160    fn hrd_parameters(&mut self, hrd_params: &HrdParams) -> SynthesizerResult<()> {
161        self.ue(hrd_params.cpb_cnt_minus1)?;
162        self.u(4, hrd_params.bit_rate_scale)?;
163        self.u(4, hrd_params.cpb_size_scale)?;
164
165        for i in 0..=(hrd_params.cpb_cnt_minus1 as usize) {
166            self.ue(hrd_params.bit_rate_value_minus1[i])?;
167            self.ue(hrd_params.cpb_size_value_minus1[i])?;
168            self.u(1, hrd_params.cbr_flag[i])?;
169        }
170
171        self.u(5, hrd_params.initial_cpb_removal_delay_length_minus1)?;
172        self.u(5, hrd_params.cpb_removal_delay_length_minus1)?;
173        self.u(5, hrd_params.dpb_output_delay_length_minus1)?;
174        self.u(5, hrd_params.time_offset_length)?;
175
176        Ok(())
177    }
178
179    fn vui_parameters(&mut self) -> SynthesizerResult<()> {
180        // H.264 E.1.1
181        let vui_params = &self.nalu.vui_parameters;
182
183        self.u(1, vui_params.aspect_ratio_info_present_flag)?;
184        if vui_params.aspect_ratio_info_present_flag {
185            self.u(8, vui_params.aspect_ratio_idc)?;
186            if vui_params.aspect_ratio_idc == EXTENDED_SAR {
187                self.u(16, vui_params.sar_width)?;
188                self.u(16, vui_params.sar_height)?;
189            }
190        }
191
192        self.u(1, vui_params.overscan_info_present_flag)?;
193        if vui_params.overscan_info_present_flag {
194            self.u(1, vui_params.overscan_appropriate_flag)?;
195        }
196
197        self.u(1, vui_params.video_signal_type_present_flag)?;
198        if vui_params.video_signal_type_present_flag {
199            self.u(3, vui_params.video_format)?;
200            self.u(1, vui_params.video_full_range_flag)?;
201
202            self.u(1, vui_params.colour_description_present_flag)?;
203            if vui_params.colour_description_present_flag {
204                self.u(8, vui_params.colour_primaries)?;
205                self.u(8, vui_params.transfer_characteristics)?;
206                self.u(8, vui_params.matrix_coefficients)?;
207            }
208        }
209
210        self.u(1, vui_params.chroma_loc_info_present_flag)?;
211        if vui_params.chroma_loc_info_present_flag {
212            self.ue(vui_params.chroma_sample_loc_type_top_field)?;
213            self.ue(self.nalu.vui_parameters.chroma_sample_loc_type_bottom_field)?;
214        }
215
216        self.u(1, vui_params.timing_info_present_flag)?;
217        if vui_params.timing_info_present_flag {
218            self.u(32, vui_params.num_units_in_tick)?;
219            self.u(32, vui_params.time_scale)?;
220            self.u(1, vui_params.fixed_frame_rate_flag)?;
221        }
222
223        self.u(1, vui_params.nal_hrd_parameters_present_flag)?;
224        if vui_params.nal_hrd_parameters_present_flag {
225            self.hrd_parameters(&vui_params.nal_hrd_parameters)?;
226        }
227        self.u(1, vui_params.vcl_hrd_parameters_present_flag)?;
228        if vui_params.vcl_hrd_parameters_present_flag {
229            self.hrd_parameters(&vui_params.vcl_hrd_parameters)?;
230        }
231
232        if vui_params.nal_hrd_parameters_present_flag || vui_params.vcl_hrd_parameters_present_flag
233        {
234            self.u(1, vui_params.low_delay_hrd_flag)?;
235        }
236
237        self.u(1, vui_params.pic_struct_present_flag)?;
238
239        self.u(1, vui_params.bitstream_restriction_flag)?;
240        if vui_params.bitstream_restriction_flag {
241            self.u(1, vui_params.motion_vectors_over_pic_boundaries_flag)?;
242            self.ue(vui_params.max_bytes_per_pic_denom)?;
243            self.ue(vui_params.max_bits_per_mb_denom)?;
244            self.ue(vui_params.log2_max_mv_length_horizontal)?;
245            self.ue(vui_params.log2_max_mv_length_vertical)?;
246            self.ue(vui_params.max_num_reorder_frames)?;
247            self.ue(vui_params.max_dec_frame_buffering)?;
248        }
249
250        Ok(())
251    }
252
253    fn seq_parameter_set_data(&mut self) -> SynthesizerResult<()> {
254        // H.264 7.3.2.1.1
255        self.u(8, self.nalu.profile_idc)?;
256        self.u(1, self.nalu.constraint_set0_flag)?;
257        self.u(1, self.nalu.constraint_set1_flag)?;
258        self.u(1, self.nalu.constraint_set2_flag)?;
259        self.u(1, self.nalu.constraint_set3_flag)?;
260        self.u(1, self.nalu.constraint_set4_flag)?;
261        self.u(1, self.nalu.constraint_set5_flag)?;
262        self.u(2, /* reserved_zero_2bits */ 0u32)?;
263        self.u(8, self.nalu.level_idc as u32)?;
264        self.ue(self.nalu.seq_parameter_set_id)?;
265
266        if self.nalu.profile_idc == 100
267            || self.nalu.profile_idc == 110
268            || self.nalu.profile_idc == 122
269            || self.nalu.profile_idc == 244
270            || self.nalu.profile_idc == 44
271            || self.nalu.profile_idc == 83
272            || self.nalu.profile_idc == 86
273            || self.nalu.profile_idc == 118
274            || self.nalu.profile_idc == 128
275            || self.nalu.profile_idc == 138
276            || self.nalu.profile_idc == 139
277            || self.nalu.profile_idc == 134
278            || self.nalu.profile_idc == 135
279        {
280            self.ue(self.nalu.chroma_format_idc)?;
281
282            if self.nalu.chroma_format_idc == 3 {
283                self.u(1, self.nalu.separate_colour_plane_flag)?;
284            }
285
286            self.ue(self.nalu.bit_depth_luma_minus8)?;
287            self.ue(self.nalu.bit_depth_chroma_minus8)?;
288            self.u(1, self.nalu.qpprime_y_zero_transform_bypass_flag)?;
289            self.u(1, self.nalu.seq_scaling_matrix_present_flag)?;
290
291            if self.nalu.seq_scaling_matrix_present_flag {
292                let scaling_list_count = if self.nalu.chroma_format_idc != 3 { 8 } else { 12 };
293
294                for i in 0..scaling_list_count {
295                    // Assume if scaling lists are zeroed that they are not present.
296                    if i < 6 {
297                        if self.nalu.scaling_lists_4x4[i] == [0; 16] {
298                            self.u(1, /* seq_scaling_list_present_flag */ false)?;
299                        } else {
300                            self.u(1, /* seq_scaling_list_present_flag */ true)?;
301                            self.scaling_list(
302                                &self.nalu.scaling_lists_4x4[i],
303                                Self::default_scaling_list(i),
304                            )?;
305                        }
306                    } else if self.nalu.scaling_lists_8x8[i - 6] == [0; 64] {
307                        self.u(1, /* seq_scaling_list_present_flag */ false)?;
308                    } else {
309                        self.u(1, /* seq_scaling_list_present_flag */ true)?;
310                        self.scaling_list(
311                            &self.nalu.scaling_lists_8x8[i - 6],
312                            Self::default_scaling_list(i),
313                        )?;
314                    }
315                }
316            }
317        }
318
319        self.ue(self.nalu.log2_max_frame_num_minus4)?;
320        self.ue(self.nalu.pic_order_cnt_type)?;
321
322        if self.nalu.pic_order_cnt_type == 0 {
323            self.ue(self.nalu.log2_max_pic_order_cnt_lsb_minus4)?;
324        } else if self.nalu.pic_order_cnt_type == 1 {
325            self.u(1, self.nalu.delta_pic_order_always_zero_flag)?;
326            self.se(self.nalu.offset_for_non_ref_pic)?;
327            self.se(self.nalu.offset_for_top_to_bottom_field)?;
328            self.ue(self.nalu.num_ref_frames_in_pic_order_cnt_cycle)?;
329
330            for offset_for_ref_frame in &self.nalu.offset_for_ref_frame {
331                self.se(*offset_for_ref_frame)?;
332            }
333        }
334
335        self.ue(self.nalu.max_num_ref_frames)?;
336        self.u(1, self.nalu.gaps_in_frame_num_value_allowed_flag)?;
337        self.ue(self.nalu.pic_width_in_mbs_minus1)?;
338        self.ue(self.nalu.pic_height_in_map_units_minus1)?;
339        self.u(1, self.nalu.frame_mbs_only_flag)?;
340        if !self.nalu.frame_mbs_only_flag {
341            self.u(1, self.nalu.mb_adaptive_frame_field_flag)?;
342        }
343        self.u(1, self.nalu.direct_8x8_inference_flag)?;
344
345        self.u(1, self.nalu.frame_cropping_flag)?;
346        if self.nalu.frame_cropping_flag {
347            self.ue(self.nalu.frame_crop_left_offset)?;
348            self.ue(self.nalu.frame_crop_right_offset)?;
349            self.ue(self.nalu.frame_crop_top_offset)?;
350            self.ue(self.nalu.frame_crop_bottom_offset)?;
351        }
352
353        self.u(1, self.nalu.vui_parameters_present_flag)?;
354        if self.nalu.vui_parameters_present_flag {
355            self.vui_parameters()?;
356        }
357
358        Ok(())
359    }
360}
361
362impl<'n, W: Write> Synthesizer<'n, Pps, W> {
363    pub fn synthesize(
364        ref_idc: u8,
365        pps: &'n Pps,
366        writer: W,
367        ep_enabled: bool,
368    ) -> SynthesizerResult<()> {
369        let mut s = Self { writer: NaluWriter::<W>::new(writer, ep_enabled), nalu: pps };
370
371        s.writer.write_header(ref_idc, NaluType::Pps as u8)?;
372        s.pic_parameter_set_rbsp()?;
373        s.rbsp_trailing_bits()
374    }
375
376    fn pic_parameter_set_rbsp(&mut self) -> SynthesizerResult<()> {
377        self.ue(self.nalu.pic_parameter_set_id)?;
378        self.ue(self.nalu.seq_parameter_set_id)?;
379        self.u(1, self.nalu.entropy_coding_mode_flag)?;
380        self.u(1, self.nalu.bottom_field_pic_order_in_frame_present_flag)?;
381
382        self.ue(self.nalu.num_slice_groups_minus1)?;
383        if self.nalu.num_slice_groups_minus1 > 0 {
384            return Err(SynthesizerError::Unsupported);
385        }
386
387        self.ue(self.nalu.num_ref_idx_l0_default_active_minus1)?;
388        self.ue(self.nalu.num_ref_idx_l1_default_active_minus1)?;
389        self.u(1, self.nalu.weighted_pred_flag)?;
390        self.u(2, self.nalu.weighted_bipred_idc)?;
391        self.se(self.nalu.pic_init_qp_minus26)?;
392        self.se(self.nalu.pic_init_qs_minus26)?;
393        self.se(self.nalu.chroma_qp_index_offset)?;
394        self.u(1, self.nalu.deblocking_filter_control_present_flag)?;
395        self.u(1, self.nalu.constrained_intra_pred_flag)?;
396        self.u(1, self.nalu.redundant_pic_cnt_present_flag)?;
397
398        if !(self.nalu.transform_8x8_mode_flag
399            || self.nalu.pic_scaling_matrix_present_flag
400            || self.nalu.second_chroma_qp_index_offset != 0)
401        {
402            return Ok(());
403        }
404
405        self.u(1, self.nalu.transform_8x8_mode_flag)?;
406        self.u(1, self.nalu.pic_scaling_matrix_present_flag)?;
407
408        if self.nalu.pic_scaling_matrix_present_flag {
409            let mut scaling_list_count = 6;
410            if self.nalu.transform_8x8_mode_flag {
411                if self.nalu.sps.chroma_format_idc != 3 {
412                    scaling_list_count += 2;
413                } else {
414                    scaling_list_count += 6;
415                }
416            }
417
418            for i in 0..scaling_list_count {
419                // Assume if scaling lists are zeroed that they are not present.
420                if i < 6 {
421                    if self.nalu.scaling_lists_4x4[i] == [0; 16] {
422                        self.u(1, /* seq_scaling_list_present_flag */ false)?;
423                    } else {
424                        self.u(1, /* seq_scaling_list_present_flag */ true)?;
425                        self.scaling_list(
426                            &self.nalu.scaling_lists_4x4[i],
427                            Self::default_scaling_list(i),
428                        )?;
429                    }
430                } else if self.nalu.scaling_lists_8x8[i - 6] == [0; 64] {
431                    self.u(1, /* seq_scaling_list_present_flag */ false)?;
432                } else {
433                    self.u(1, /* seq_scaling_list_present_flag */ true)?;
434                    self.scaling_list(
435                        &self.nalu.scaling_lists_8x8[i - 6],
436                        Self::default_scaling_list(i),
437                    )?;
438                }
439            }
440        }
441
442        self.se(self.nalu.second_chroma_qp_index_offset)?;
443
444        Ok(())
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use std::io::Cursor;
451
452    use super::*;
453    use crate::codec::h264::parser::Nalu;
454    use crate::codec::h264::parser::NaluType;
455    use crate::codec::h264::parser::Parser;
456    use crate::codec::h264::parser::Profile;
457
458    #[test]
459    fn synthesize_sps() {
460        let raw_sps_buf = [0x00, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x0a, 0xfb, 0x88];
461        let mut raw_sps = Cursor::new(&raw_sps_buf[..]);
462
463        let nalu = Nalu::next(&mut raw_sps).unwrap();
464        assert_eq!(nalu.header.type_, NaluType::Sps);
465
466        let mut parser = Parser::default();
467        let sps = parser.parse_sps(&nalu).unwrap();
468
469        let mut buf = Vec::<u8>::new();
470        Synthesizer::<'_, Sps, _>::synthesize(0, sps, &mut buf, false).unwrap();
471
472        assert_eq!(buf, raw_sps_buf);
473
474        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
475        if write_to_file {
476            let mut out = std::fs::File::create("sps.h264").unwrap();
477            out.write_all(&buf).unwrap();
478            out.flush().unwrap();
479        }
480
481        let mut cursor = Cursor::new(&buf[..]);
482        let nalu = Nalu::next(&mut cursor).unwrap();
483
484        let mut parser = Parser::default();
485
486        let sps2 = parser.parse_sps(&nalu).unwrap();
487
488        assert_eq!(sps, sps2);
489    }
490
491    #[test]
492    fn synthesize_sps_scaling_lists() {
493        let sps = Sps {
494            profile_idc: Profile::High as u8,
495            seq_scaling_matrix_present_flag: true,
496            scaling_lists_4x4: [[11, 20, 10, 20, 10, 22, 10, 20, 10, 20, 13, 20, 10, 20, 10, 24];
497                6],
498            scaling_lists_8x8: [
499                [
500                    33, 20, 10, 21, 33, 20, 12, 20, 33, 23, 10, 20, 33, 20, 10, 20, 33, 24, 10, 20,
501                    33, 20, 15, 20, 33, 20, 10, 26, 33, 20, 17, 20, 33, 28, 10, 20, 33, 20, 10, 20,
502                    33, 29, 10, 20, 33, 20, 11, 20, 33, 20, 10, 20, 33, 20, 10, 20, 33, 20, 10, 20,
503                    33, 20, 10, 20,
504                ],
505                [
506                    10, 77, 11, 20, 10, 77, 12, 20, 10, 77, 13, 20, 10, 77, 14, 20, 10, 77, 15, 20,
507                    10, 77, 16, 20, 10, 77, 17, 20, 10, 77, 18, 20, 10, 77, 19, 20, 10, 77, 10, 20,
508                    10, 77, 10, 21, 10, 77, 10, 22, 10, 77, 10, 23, 10, 77, 10, 24, 10, 77, 10, 26,
509                    10, 77, 10, 28,
510                ],
511                [0; 64],
512                [0; 64],
513                [0; 64],
514                [0; 64],
515            ],
516            frame_mbs_only_flag: true,
517            ..Default::default()
518        };
519
520        let mut buf = Vec::<u8>::new();
521        Synthesizer::<'_, Sps, _>::synthesize(0, &sps, &mut buf, false).unwrap();
522
523        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
524        if write_to_file {
525            let mut out = std::fs::File::create("sps.h264").unwrap();
526            out.write_all(&buf).unwrap();
527            out.flush().unwrap();
528        }
529
530        let mut cursor = Cursor::new(&buf[..]);
531        let nalu = Nalu::next(&mut cursor).unwrap();
532
533        let mut parser = Parser::default();
534
535        let sps2 = parser.parse_sps(&nalu).unwrap();
536
537        assert_eq!(sps.scaling_lists_4x4, sps2.scaling_lists_4x4);
538        assert_eq!(sps.scaling_lists_8x8, sps2.scaling_lists_8x8);
539    }
540
541    #[test]
542    fn synthesize_pps() {
543        let raw_sps_pps = [
544            0x00, 0x00, 0x00, 0x01, 0x07, 0x4d, 0x40, 0x0d, 0xa9, 0x18, 0x28, 0x3e, 0x60, 0x0d,
545            0x41, 0x80, 0x41, 0xad, 0xb0, 0xad, 0x7b, 0xdf, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08,
546            0xde, 0x09, 0x88,
547        ];
548
549        let mut buf = Vec::<u8>::new();
550        let mut out = Cursor::new(&mut buf);
551
552        let mut cursor = Cursor::new(&raw_sps_pps[..]);
553        let mut parser: Parser = Default::default();
554
555        while let Ok(nalu) = Nalu::next(&mut cursor) {
556            match nalu.header.type_ {
557                NaluType::Sps => {
558                    let sps = parser.parse_sps(&nalu).unwrap();
559                    Synthesizer::<'_, Sps, _>::synthesize(0, sps, &mut out, false).unwrap();
560                }
561                NaluType::Pps => {
562                    let pps = parser.parse_pps(&nalu).unwrap();
563                    Synthesizer::<'_, Pps, _>::synthesize(0, pps, &mut out, false).unwrap();
564                }
565                _ => panic!(),
566            }
567        }
568
569        let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
570        if write_to_file {
571            let mut out = std::fs::File::create("sps_pps.h264").unwrap();
572            out.write_all(&buf).unwrap();
573            out.flush().unwrap();
574
575            let mut out = std::fs::File::create("sps_pps_ref.h264").unwrap();
576            out.write_all(&raw_sps_pps).unwrap();
577            out.flush().unwrap();
578        }
579
580        assert_eq!(buf, raw_sps_pps);
581    }
582}