Skip to content

(enhancement): SEA codec optimization#6

Open
chanderlud wants to merge 11 commits intomasterfrom
sea-optimization
Open

(enhancement): SEA codec optimization#6
chanderlud wants to merge 11 commits intomasterfrom
sea-optimization

Conversation

@chanderlud
Copy link
Copy Markdown
Owner

  • refactors the sea codec to avoid per-frame allocations
  • implements codec tests and benchmarks

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the SEA audio codec implementation to reduce per-frame allocations by reusing scratch buffers across encode/decode operations, and adds integration tests + Criterion benchmarks to validate correctness and measure performance.

Changes:

  • Refactor SEA encoder/decoder + chunk serialization/deserialization to encode/decode into caller-provided buffers and reuse internal scratch vectors.
  • Update bitpacking APIs to support reuse (reset_*) and return borrowed slices from finish() to avoid allocations.
  • Add SEA codec integration tests and a sea_codec Criterion benchmark.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
rust/telepathy-audio/src/sea/encoder.rs Reuses an internal chunk_data buffer to avoid per-frame chunk allocation.
rust/telepathy-audio/src/sea/decoder.rs Uses SeaFile::new_for_decoding constructor.
rust/telepathy-audio/src/sea/codec/mod.rs Exposes bits module publicly for external access (tests/benches).
rust/telepathy-audio/src/sea/codec/file.rs Introduces reusable scratch buffers and new encode/decode-into paths.
rust/telepathy-audio/src/sea/codec/chunk.rs Refactors chunk model into ChunkInfo + serialize_into/parse_into scratch-buffer APIs.
rust/telepathy-audio/src/sea/codec/encoder_cbr.rs Moves encoding to encode_into and reuses scratch vectors.
rust/telepathy-audio/src/sea/codec/encoder_vbr.rs Moves encoding to encode_into and reuses scratch vectors for analysis/selection.
rust/telepathy-audio/src/sea/codec/decoder.rs Refactors decoding into decode_*_into and reuses internal scratch LMS.
rust/telepathy-audio/src/sea/codec/bits.rs Adds reset methods + makes finish() return borrowed slices to reduce allocations.
rust/telepathy-audio/src/sea/codec/common.rs Replaces EncodedSamples with SeaEncoderTrait::encode_into API.
rust/telepathy-audio/tests/sea_codec_test.rs Adds round-trip tests + bitpacker/unpacker test cases.
rust/telepathy-audio/benches/sea_codec.rs Adds Criterion benchmarks for encode/decode and bitpacking.
rust/telepathy-audio/Cargo.toml Registers the new sea_codec benchmark target.
rust/telepathy_audio/tests/sea_codec_test.rs Duplicate test file in a non-workspace directory (likely unused).
rust/telepathy_audio/benches/sea_codec.rs Duplicate bench file in a non-workspace directory (likely unused).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
return Err(SeaError::InvalidParameters);
}

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EncoderSettings::validate doesn't validate residual_bits even though downstream code calls SeaResidualSize::from(floor(residual_bits) as u8), which will panic for values outside 1..=8. Consider validating that residual_bits is finite and that floor(residual_bits) is within 1..=8 (and returning SeaError::InvalidParameters), so invalid settings fail gracefully instead of crashing.

Suggested change
if !self.residual_bits.is_finite()
|| !(1..=8).contains(&((self.residual_bits.floor()) as u8))
{
return Err(SeaError::InvalidParameters);
}

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +83
for vbr_residual_size in vbr_residual_sizes.iter() {
let relative_size = *vbr_residual_size as i32 - residual_size as i32 + 1;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When serializing VBR residual sizes, relative_size is computed as an i32 and then cast to u32 before packing into 2 bits. If a value is outside the representable range (expected 0..=3), this will wrap/truncate in release builds (debug_asserts won’t fire) and produce an invalid stream. Please add explicit range checks for relative_size (and for vbr_residual_sizes being in 1..=8) and return SeaError::InvalidParameters on violation.

Suggested change
for vbr_residual_size in vbr_residual_sizes.iter() {
let relative_size = *vbr_residual_size as i32 - residual_size as i32 + 1;
for vbr_residual_size in vbr_residual_sizes.iter() {
if !matches!(*vbr_residual_size, 1..=8) {
return Err(SeaError::InvalidParameters);
}
let relative_size = *vbr_residual_size as i32 - residual_size as i32 + 1;
if !(0..=3).contains(&relative_size) {
return Err(SeaError::InvalidParameters);
}

Copilot uses AI. Check for mistakes.
vbr_residual_sizes.extend_from_slice(unpacker.finish());
vbr_residual_sizes.resize(scale_factor_items, 0);
for item in vbr_residual_sizes.iter_mut() {
*item += residual_size as u8 - 1;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After decoding VBR residual sizes, the code adjusts them via *item += residual_size as u8 - 1 but never validates the resulting bit-lengths. Corrupt frames can yield sizes <1 or >8 (e.g., residual_size=8 and encoded value=3 => 10), which can later panic when indexing BitUnpacker::MASKS/DQT tables. Add a validation pass here to reject any VBR residual size outside 1..=8 with SeaError::InvalidFrame.

Suggested change
*item += residual_size as u8 - 1;
let adjusted = *item + residual_size as u8 - 1;
if !(1..=8).contains(&adjusted) {
return Err(SeaError::InvalidFrame);
}
*item = adjusted;

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +263
let invalid_scale_factor_bits = [0x01_u8, 0x01, 20, 0x5A];

let res_short = decoder.decode_frame(&too_short, &mut output);
let res_type = decoder.decode_frame(&invalid_chunk_type, &mut output);
let res_sf = decoder.decode_frame(&invalid_scale_factor_bits, &mut output);

assert!(matches!(res_short, Err(SeaError::InvalidFrame)));
assert!(matches!(res_type, Err(SeaError::InvalidFrame)));
assert!(matches!(res_sf, Err(SeaError::InvalidFrame)));
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid_frame_paths currently doesn't cover malformed VBR residual-size metadata (e.g., VBR chunk with residual_size=8 and a 2-bit residual-size code of 3 leading to an out-of-range bitlength). Adding a case here would help prevent regressions in the new scratch-buffer parsing path.

Suggested change
let invalid_scale_factor_bits = [0x01_u8, 0x01, 20, 0x5A];
let res_short = decoder.decode_frame(&too_short, &mut output);
let res_type = decoder.decode_frame(&invalid_chunk_type, &mut output);
let res_sf = decoder.decode_frame(&invalid_scale_factor_bits, &mut output);
assert!(matches!(res_short, Err(SeaError::InvalidFrame)));
assert!(matches!(res_type, Err(SeaError::InvalidFrame)));
assert!(matches!(res_sf, Err(SeaError::InvalidFrame)));
let invalid_scale_factor_bits = [0x01_u8, 0x01, 20, 0x5A];
// Malformed VBR residual-size metadata: residual_size=8 with a 2-bit
// residual-size code of 3 should be rejected instead of producing an
// out-of-range residual bitlength during parsing.
let invalid_vbr_residual_size = [0x41_u8, 0x08, 0xC0, 0x00];
let res_short = decoder.decode_frame(&too_short, &mut output);
let res_type = decoder.decode_frame(&invalid_chunk_type, &mut output);
let res_sf = decoder.decode_frame(&invalid_scale_factor_bits, &mut output);
let res_vbr_residual_size = decoder.decode_frame(&invalid_vbr_residual_size, &mut output);
assert!(matches!(res_short, Err(SeaError::InvalidFrame)));
assert!(matches!(res_type, Err(SeaError::InvalidFrame)));
assert!(matches!(res_sf, Err(SeaError::InvalidFrame)));
assert!(matches!(res_vbr_residual_size, Err(SeaError::InvalidFrame)));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants