1use alloc::vec::Vec;
2
3use crate::error::{bail, DecodingError, Result};
4use crate::j2c::idwt;
5use crate::math::{floor_f32, round_f32};
6use crate::{
7 decode_ht_code_block_scalar, decode_j2k_code_block_scalar, HtCodeBlockDecodeJob,
8 HtOwnedSubBandPlan, J2kCodeBlockDecodeJob, J2kDirectBandId, J2kDirectColorPlan,
9 J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kDirectIdwtStep, J2kDirectStoreStep,
10 J2kIdwtBand, J2kOwnedSubBandPlan, J2kRect, J2kSingleDecompositionIdwtJob, J2kWaveletTransform,
11};
12
13#[derive(Debug, Default)]
15pub struct J2kDirectCpuScratch {
16 component_band_sets: Vec<DirectComponentBandScratch>,
17 component_planes: Vec<DirectComponentPlane>,
18}
19
20impl J2kDirectCpuScratch {
21 #[must_use]
23 pub const fn new() -> Self {
24 Self {
25 component_band_sets: Vec::new(),
26 component_planes: Vec::new(),
27 }
28 }
29
30 pub fn clear(&mut self) {
32 self.component_band_sets.clear();
33 self.component_planes.clear();
34 }
35
36 fn prepare_component_scratch(&mut self, component_count: usize) {
37 while self.component_band_sets.len() < component_count {
38 self.component_band_sets
39 .push(DirectComponentBandScratch::default());
40 }
41 while self.component_planes.len() < component_count {
42 self.component_planes.push(DirectComponentPlane::default());
43 }
44 }
45
46 #[cfg(test)]
47 fn allocation_profile_for_tests(&self) -> DirectScratchAllocationProfile {
48 let band_buffers = self
49 .component_band_sets
50 .iter()
51 .map(|component| component.bands.len())
52 .sum();
53 let band_sample_len = self
54 .component_band_sets
55 .iter()
56 .flat_map(|component| component.bands.iter())
57 .map(|band| band.coefficients.len())
58 .sum();
59 let band_sample_capacity = self
60 .component_band_sets
61 .iter()
62 .flat_map(|component| component.bands.iter())
63 .map(|band| band.coefficients.capacity())
64 .sum();
65 let component_sample_len = self
66 .component_planes
67 .iter()
68 .map(|plane| plane.samples.len())
69 .sum();
70 let component_sample_capacity = self
71 .component_planes
72 .iter()
73 .map(|plane| plane.samples.capacity())
74 .sum();
75 DirectScratchAllocationProfile {
76 component_band_sets: self.component_band_sets.len(),
77 component_planes: self.component_planes.len(),
78 band_buffers,
79 band_sample_len,
80 band_sample_capacity,
81 component_sample_len,
82 component_sample_capacity,
83 }
84 }
85}
86
87#[cfg(test)]
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89struct DirectScratchAllocationProfile {
90 component_band_sets: usize,
91 component_planes: usize,
92 band_buffers: usize,
93 band_sample_len: usize,
94 band_sample_capacity: usize,
95 component_sample_len: usize,
96 component_sample_capacity: usize,
97}
98
99#[derive(Debug, Default)]
100struct DirectComponentBandScratch {
101 bands: Vec<DirectCpuBand>,
102 active_len: usize,
103}
104
105impl DirectComponentBandScratch {
106 fn reset(&mut self) {
107 self.active_len = 0;
108 }
109
110 fn active(&self) -> &[DirectCpuBand] {
111 &self.bands[..self.active_len]
112 }
113
114 fn prepare_band(&mut self, band_id: J2kDirectBandId, rect: J2kRect, len: usize) -> usize {
115 let index = self.active_len;
116 if index == self.bands.len() {
117 self.bands.push(DirectCpuBand::empty());
118 }
119 let band = &mut self.bands[index];
120 band.band_id = band_id;
121 band.rect = rect;
122 resize_and_zero(&mut band.coefficients, len);
123 self.active_len += 1;
124 index
125 }
126}
127
128#[derive(Debug)]
129struct DirectCpuBand {
130 band_id: J2kDirectBandId,
131 rect: J2kRect,
132 coefficients: Vec<f32>,
133}
134
135impl DirectCpuBand {
136 const fn empty() -> Self {
137 Self {
138 band_id: 0,
139 rect: J2kRect {
140 x0: 0,
141 y0: 0,
142 x1: 0,
143 y1: 0,
144 },
145 coefficients: Vec::new(),
146 }
147 }
148}
149
150#[derive(Debug, Default)]
151struct DirectComponentPlane {
152 width: u32,
153 height: u32,
154 samples: Vec<f32>,
155}
156
157pub fn execute_direct_color_plan_rgb8_into(
159 plan: &J2kDirectColorPlan,
160 output_region: J2kRect,
161 scratch: &mut J2kDirectCpuScratch,
162 out: &mut [u8],
163 stride: usize,
164) -> Result<()> {
165 execute_direct_color_plan_u8_into(
166 plan,
167 output_region,
168 scratch,
169 out,
170 stride,
171 DirectColorU8Output::Rgb8,
172 )
173}
174
175pub fn execute_direct_color_plan_rgba8_into(
177 plan: &J2kDirectColorPlan,
178 output_region: J2kRect,
179 scratch: &mut J2kDirectCpuScratch,
180 out: &mut [u8],
181 stride: usize,
182) -> Result<()> {
183 execute_direct_color_plan_u8_into(
184 plan,
185 output_region,
186 scratch,
187 out,
188 stride,
189 DirectColorU8Output::Rgba8,
190 )
191}
192
193#[derive(Clone, Copy)]
194enum DirectColorU8Output {
195 Rgb8,
196 Rgba8,
197}
198
199impl DirectColorU8Output {
200 const fn bytes_per_pixel(self) -> usize {
201 match self {
202 Self::Rgb8 => 3,
203 Self::Rgba8 => 4,
204 }
205 }
206}
207
208fn execute_direct_color_plan_u8_into(
209 plan: &J2kDirectColorPlan,
210 output_region: J2kRect,
211 scratch: &mut J2kDirectCpuScratch,
212 out: &mut [u8],
213 stride: usize,
214 output: DirectColorU8Output,
215) -> Result<()> {
216 if plan.component_plans.len() != 3 {
217 bail!(DecodingError::UnsupportedFeature(
218 "direct CPU color plan requires three components"
219 ));
220 }
221 validate_output_region(plan, output_region, out.len(), stride, output)?;
222
223 scratch.prepare_component_scratch(plan.component_plans.len());
224 for (component_index, component_plan) in plan.component_plans.iter().enumerate() {
225 let band_scratch = &mut scratch.component_band_sets[component_index];
226 let plane = &mut scratch.component_planes[component_index];
227 execute_component_plan(component_plan, band_scratch, plane)?;
228 }
229
230 let [plane0, plane1, plane2, ..] = scratch.component_planes.as_mut_slice() else {
231 bail!(DecodingError::CodeBlockDecodeFailure);
232 };
233 if plan.mct {
234 apply_inverse_mct(plan.transform, plan.bit_depths, plane0, plane1, plane2)?;
235 }
236 write_rgb8_region(
237 [plane0, plane1, plane2],
238 plan.bit_depths,
239 output_region,
240 out,
241 stride,
242 output,
243 )
244}
245
246fn execute_component_plan(
247 plan: &J2kDirectGrayscalePlan,
248 bands: &mut DirectComponentBandScratch,
249 output: &mut DirectComponentPlane,
250) -> Result<()> {
251 bands.reset();
252 let mut output_written = false;
253
254 for step in &plan.steps {
255 match step {
256 J2kDirectGrayscaleStep::ClassicSubBand(sub_band) => {
257 execute_classic_sub_band(sub_band, bands)?;
258 }
259 J2kDirectGrayscaleStep::HtSubBand(sub_band) => {
260 execute_ht_sub_band(sub_band, bands)?;
261 }
262 J2kDirectGrayscaleStep::Idwt(step) => {
263 execute_idwt_step(step, bands)?;
264 }
265 J2kDirectGrayscaleStep::Store(store) => {
266 store_component(store, bands.active(), output, &mut output_written)?;
267 }
268 }
269 }
270
271 if output_written {
272 Ok(())
273 } else {
274 Err(DecodingError::CodeBlockDecodeFailure.into())
275 }
276}
277
278fn execute_classic_sub_band(
279 plan: &J2kOwnedSubBandPlan,
280 bands: &mut DirectComponentBandScratch,
281) -> Result<()> {
282 let required_len = checked_area(plan.width, plan.height)?;
283 let band_index = bands.prepare_band(plan.band_id, plan.rect, required_len);
284 let output = &mut bands.bands[band_index].coefficients;
285 let sub_band_width =
286 usize::try_from(plan.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
287
288 for job in &plan.jobs {
289 let base_idx = checked_block_base(job.output_x, job.output_y, sub_band_width)?;
290 let block_len = checked_block_output_len(job.output_stride, job.width, job.height)?;
291 let end_idx = base_idx
292 .checked_add(block_len)
293 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
294 if end_idx > output.len()
295 || job
296 .output_x
297 .checked_add(job.width)
298 .is_none_or(|x| x > plan.width)
299 || job
300 .output_y
301 .checked_add(job.height)
302 .is_none_or(|y| y > plan.height)
303 {
304 bail!(DecodingError::CodeBlockDecodeFailure);
305 }
306
307 let code_block = J2kCodeBlockDecodeJob {
308 data: &job.data,
309 segments: &job.segments,
310 width: job.width,
311 height: job.height,
312 output_stride: job.output_stride,
313 missing_bit_planes: job.missing_bit_planes,
314 number_of_coding_passes: job.number_of_coding_passes,
315 total_bitplanes: job.total_bitplanes,
316 roi_shift: job.roi_shift,
317 sub_band_type: job.sub_band_type,
318 style: job.style,
319 strict: job.strict,
320 dequantization_step: job.dequantization_step,
321 };
322 decode_j2k_code_block_scalar(code_block, &mut output[base_idx..end_idx])?;
323 }
324 Ok(())
325}
326
327fn execute_ht_sub_band(
328 plan: &HtOwnedSubBandPlan,
329 bands: &mut DirectComponentBandScratch,
330) -> Result<()> {
331 let required_len = checked_area(plan.width, plan.height)?;
332 let band_index = bands.prepare_band(plan.band_id, plan.rect, required_len);
333 let output = &mut bands.bands[band_index].coefficients;
334 let sub_band_width =
335 usize::try_from(plan.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
336
337 for job in &plan.jobs {
338 let base_idx = checked_block_base(job.output_x, job.output_y, sub_band_width)?;
339 let block_len = checked_block_output_len(job.output_stride, job.width, job.height)?;
340 let end_idx = base_idx
341 .checked_add(block_len)
342 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
343 if end_idx > output.len()
344 || job
345 .output_x
346 .checked_add(job.width)
347 .is_none_or(|x| x > plan.width)
348 || job
349 .output_y
350 .checked_add(job.height)
351 .is_none_or(|y| y > plan.height)
352 {
353 bail!(DecodingError::CodeBlockDecodeFailure);
354 }
355
356 let code_block = HtCodeBlockDecodeJob {
357 data: &job.data,
358 cleanup_length: job.cleanup_length,
359 refinement_length: job.refinement_length,
360 width: job.width,
361 height: job.height,
362 output_stride: job.output_stride,
363 missing_bit_planes: job.missing_bit_planes,
364 number_of_coding_passes: job.number_of_coding_passes,
365 num_bitplanes: job.num_bitplanes,
366 roi_shift: job.roi_shift,
367 stripe_causal: job.stripe_causal,
368 strict: job.strict,
369 dequantization_step: job.dequantization_step,
370 };
371 decode_ht_code_block_scalar(code_block, &mut output[base_idx..end_idx])?;
372 }
373 Ok(())
374}
375
376fn execute_idwt_step(
377 step: &J2kDirectIdwtStep,
378 bands: &mut DirectComponentBandScratch,
379) -> Result<()> {
380 let output_index = bands.prepare_band(step.output_band_id, step.rect, 0);
381 let (input_bands, output_bands) = bands.bands.split_at_mut(output_index);
382 let output = &mut output_bands[0].coefficients;
383 let ll = find_idwt_band(input_bands, step.ll_band_id)?;
384 let hl = find_idwt_band(input_bands, step.hl_band_id)?;
385 let lh = find_idwt_band(input_bands, step.lh_band_id)?;
386 let hh = find_idwt_band(input_bands, step.hh_band_id)?;
387 let job = J2kSingleDecompositionIdwtJob {
388 rect: step.rect,
389 transform: step.transform,
390 ll,
391 hl,
392 lh,
393 hh,
394 };
395 idwt::apply_single_decomposition_idwt_job(job, output)
396}
397
398fn find_idwt_band(bands: &[DirectCpuBand], band_id: J2kDirectBandId) -> Result<J2kIdwtBand<'_>> {
399 let band = find_band(bands, band_id)?;
400 Ok(J2kIdwtBand {
401 rect: band.rect,
402 coefficients: &band.coefficients,
403 })
404}
405
406fn store_component(
407 store: &J2kDirectStoreStep,
408 bands: &[DirectCpuBand],
409 plane: &mut DirectComponentPlane,
410 output_written: &mut bool,
411) -> Result<()> {
412 let input = find_band(bands, store.input_band_id)?;
413 if !*output_written {
414 plane.width = store.output_width;
415 plane.height = store.output_height;
416 let required_len = checked_area(store.output_width, store.output_height)?;
417 resize_and_zero(&mut plane.samples, required_len);
418 *output_written = true;
419 }
420 if plane.width != store.output_width
421 || plane.height != store.output_height
422 || plane.samples.len() != checked_area(store.output_width, store.output_height)?
423 {
424 bail!(DecodingError::CodeBlockDecodeFailure);
425 }
426
427 validate_store_bounds(store, input, plane)?;
428 let input_width = input.rect.width() as usize;
429 let output_width = plane.width as usize;
430 let copy_width = store.copy_width as usize;
431 for row in 0..store.copy_height as usize {
432 let src_start = (store.source_y as usize + row)
433 .checked_mul(input_width)
434 .and_then(|base| base.checked_add(store.source_x as usize))
435 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
436 let dst_start = (store.output_y as usize + row)
437 .checked_mul(output_width)
438 .and_then(|base| base.checked_add(store.output_x as usize))
439 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
440 let src = &input.coefficients[src_start..src_start + copy_width];
441 let dst = &mut plane.samples[dst_start..dst_start + copy_width];
442 for (src, dst) in src.iter().zip(dst.iter_mut()) {
443 *dst = *src + store.addend;
444 }
445 }
446 Ok(())
447}
448
449fn find_band(bands: &[DirectCpuBand], band_id: J2kDirectBandId) -> Result<&DirectCpuBand> {
450 bands
451 .iter()
452 .find(|band| band.band_id == band_id)
453 .ok_or_else(|| DecodingError::CodeBlockDecodeFailure.into())
454}
455
456fn validate_store_bounds(
457 store: &J2kDirectStoreStep,
458 input: &DirectCpuBand,
459 output: &DirectComponentPlane,
460) -> Result<()> {
461 if store
462 .source_x
463 .checked_add(store.copy_width)
464 .is_none_or(|x| x > input.rect.width())
465 || store
466 .source_y
467 .checked_add(store.copy_height)
468 .is_none_or(|y| y > input.rect.height())
469 || store
470 .output_x
471 .checked_add(store.copy_width)
472 .is_none_or(|x| x > output.width)
473 || store
474 .output_y
475 .checked_add(store.copy_height)
476 .is_none_or(|y| y > output.height)
477 {
478 bail!(DecodingError::CodeBlockDecodeFailure);
479 }
480 Ok(())
481}
482
483fn apply_inverse_mct(
484 transform: J2kWaveletTransform,
485 bit_depths: [u8; 3],
486 plane0: &mut DirectComponentPlane,
487 plane1: &mut DirectComponentPlane,
488 plane2: &mut DirectComponentPlane,
489) -> Result<()> {
490 if plane0.width != plane1.width
491 || plane1.width != plane2.width
492 || plane0.height != plane1.height
493 || plane1.height != plane2.height
494 || plane0.samples.len() != plane1.samples.len()
495 || plane1.samples.len() != plane2.samples.len()
496 {
497 bail!(DecodingError::CodeBlockDecodeFailure);
498 }
499
500 let addend0 = sign_addend(bit_depths[0]);
501 let addend1 = sign_addend(bit_depths[1]);
502 let addend2 = sign_addend(bit_depths[2]);
503 for ((y0, y1), y2) in plane0
504 .samples
505 .iter_mut()
506 .zip(plane1.samples.iter_mut())
507 .zip(plane2.samples.iter_mut())
508 {
509 let src0 = *y0;
510 let src1 = *y1;
511 let src2 = *y2;
512 let (out0, out1, out2) = match transform {
513 J2kWaveletTransform::Irreversible97 => (
514 src0 + 1.402 * src2,
515 src0 - 0.34413 * src1 - 0.71414 * src2,
516 src0 + 1.772 * src1,
517 ),
518 J2kWaveletTransform::Reversible53 => {
519 let i1 = src0 - floor_f32((src2 + src1) * 0.25);
520 (src2 + i1, i1, src1 + i1)
521 }
522 };
523 *y0 = out0 + addend0;
524 *y1 = out1 + addend1;
525 *y2 = out2 + addend2;
526 }
527 Ok(())
528}
529
530fn write_rgb8_region(
531 planes: [&DirectComponentPlane; 3],
532 bit_depths: [u8; 3],
533 output_region: J2kRect,
534 out: &mut [u8],
535 stride: usize,
536 output: DirectColorU8Output,
537) -> Result<()> {
538 let width = output_region.width() as usize;
539 let height = output_region.height() as usize;
540 let bytes_per_pixel = output.bytes_per_pixel();
541 let row_bytes = width
542 .checked_mul(bytes_per_pixel)
543 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
544 for plane in planes {
545 if output_region.x1 > plane.width || output_region.y1 > plane.height {
546 bail!(DecodingError::CodeBlockDecodeFailure);
547 }
548 }
549
550 for y in 0..height {
551 let src_y = output_region.y0 as usize + y;
552 let dst = &mut out[y * stride..y * stride + row_bytes];
553 for x in 0..width {
554 let src_x = output_region.x0 as usize + x;
555 let dst = &mut dst[x * bytes_per_pixel..x * bytes_per_pixel + bytes_per_pixel];
556 for channel in 0..3 {
557 let plane = planes[channel];
558 let sample = plane.samples[src_y * plane.width as usize + src_x];
559 dst[channel] = sample_as_u8(sample, bit_depths[channel]);
560 }
561 if matches!(output, DirectColorU8Output::Rgba8) {
562 dst[3] = u8::MAX;
563 }
564 }
565 }
566 Ok(())
567}
568
569fn validate_output_region(
570 plan: &J2kDirectColorPlan,
571 output_region: J2kRect,
572 out_len: usize,
573 stride: usize,
574 output: DirectColorU8Output,
575) -> Result<()> {
576 if output_region.x1 > plan.dimensions.0
577 || output_region.y1 > plan.dimensions.1
578 || output_region.x0 > output_region.x1
579 || output_region.y0 > output_region.y1
580 {
581 bail!(DecodingError::CodeBlockDecodeFailure);
582 }
583 let row_bytes = output_region
584 .width()
585 .checked_mul(output.bytes_per_pixel() as u32)
586 .and_then(|len| usize::try_from(len).ok())
587 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
588 if stride < row_bytes {
589 bail!(DecodingError::CodeBlockDecodeFailure);
590 }
591 let height = usize::try_from(output_region.height())
592 .map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
593 let required = if height == 0 {
594 0
595 } else {
596 stride
597 .checked_mul(height - 1)
598 .and_then(|prefix| prefix.checked_add(row_bytes))
599 .ok_or(DecodingError::CodeBlockDecodeFailure)?
600 };
601 if out_len < required {
602 bail!(DecodingError::CodeBlockDecodeFailure);
603 }
604 Ok(())
605}
606
607fn checked_area(width: u32, height: u32) -> Result<usize> {
608 usize::try_from(width)
609 .ok()
610 .and_then(|width| width.checked_mul(height as usize))
611 .ok_or_else(|| DecodingError::CodeBlockDecodeFailure.into())
612}
613
614fn checked_block_base(output_x: u32, output_y: u32, stride: usize) -> Result<usize> {
615 usize::try_from(output_y)
616 .ok()
617 .and_then(|y| y.checked_mul(stride))
618 .and_then(|base| base.checked_add(output_x as usize))
619 .ok_or_else(|| DecodingError::CodeBlockDecodeFailure.into())
620}
621
622fn checked_block_output_len(stride: usize, width: u32, height: u32) -> Result<usize> {
623 if height == 0 {
624 return Ok(0);
625 }
626 stride
627 .checked_mul(height as usize - 1)
628 .and_then(|prefix| prefix.checked_add(width as usize))
629 .ok_or_else(|| DecodingError::CodeBlockDecodeFailure.into())
630}
631
632fn resize_and_zero(buffer: &mut Vec<f32>, len: usize) {
633 buffer.resize(len, 0.0);
634 buffer.fill(0.0);
635}
636
637fn sign_addend(bit_depth: u8) -> f32 {
638 (1_u32 << (bit_depth - 1)) as f32
639}
640
641fn sample_as_u8(sample: f32, bit_depth: u8) -> u8 {
642 let rounded = round_f32(sample);
643 if bit_depth == 8 {
644 return rounded.clamp(0.0, f32::from(u8::MAX)) as u8;
645 }
646 let max_value = if bit_depth >= 16 {
647 f32::from(u16::MAX)
648 } else {
649 f32::from(((1_u16 << bit_depth) - 1).max(1))
650 };
651 round_f32((rounded.clamp(0.0, max_value) / max_value) * f32::from(u8::MAX)) as u8
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use crate::{encode_htj2k, DecodeSettings, DecoderContext, EncodeOptions, Image};
658 use alloc::vec;
659
660 fn direct_htj2k_rgb_plan() -> (J2kDirectColorPlan, J2kRect) {
661 let pixels = (0..16 * 16 * 3)
662 .map(|idx| ((idx * 13 + idx / 3) & 0xff) as u8)
663 .collect::<Vec<_>>();
664 let options = EncodeOptions {
665 reversible: true,
666 num_decomposition_levels: 2,
667 ..EncodeOptions::default()
668 };
669 let bytes = encode_htj2k(&pixels, 16, 16, 3, 8, false, &options).expect("encode HTJ2K RGB");
670 let image = Image::new(
671 &bytes,
672 &DecodeSettings {
673 target_resolution: Some((4, 4)),
674 ..DecodeSettings::default()
675 },
676 )
677 .expect("scaled image");
678 let output_region = J2kRect {
679 x0: 1,
680 y0: 1,
681 x1: 3,
682 y1: 3,
683 };
684 let mut context = DecoderContext::default();
685 let plan = image
686 .build_direct_color_plan_region_with_context(&mut context, (1, 1, 2, 2))
687 .expect("direct color plan");
688 (plan, output_region)
689 }
690
691 #[test]
692 fn direct_cpu_scratch_retains_component_buffers_between_executions() {
693 let (plan, output_region) = direct_htj2k_rgb_plan();
694 let stride = output_region.width() as usize * 3;
695 let mut out = vec![0_u8; stride * output_region.height() as usize];
696 let mut scratch = J2kDirectCpuScratch::new();
697
698 execute_direct_color_plan_rgb8_into(&plan, output_region, &mut scratch, &mut out, stride)
699 .expect("first direct execute");
700 let first = scratch.allocation_profile_for_tests();
701
702 execute_direct_color_plan_rgb8_into(&plan, output_region, &mut scratch, &mut out, stride)
703 .expect("second direct execute");
704 let second = scratch.allocation_profile_for_tests();
705
706 assert_eq!(first.component_band_sets, 3);
707 assert_eq!(first.component_planes, 3);
708 assert_eq!(second.component_band_sets, first.component_band_sets);
709 assert_eq!(second.component_planes, first.component_planes);
710 assert_eq!(second.band_buffers, first.band_buffers);
711 assert_eq!(
712 second.component_sample_capacity,
713 first.component_sample_capacity
714 );
715 assert_eq!(second.band_sample_capacity, first.band_sample_capacity);
716 assert!(second.band_sample_capacity >= second.band_sample_len);
717 assert!(second.component_sample_capacity >= second.component_sample_len);
718 }
719}