Skip to content

Deduplicate codec base classes#40

Merged
codingjoe merged 9 commits intomainfrom
codev
Mar 16, 2026
Merged

Deduplicate codec base classes#40
codingjoe merged 9 commits intomainfrom
codev

Conversation

@codingjoe
Copy link
Copy Markdown
Owner

No description provided.

@codingjoe codingjoe self-assigned this Mar 15, 2026
Copilot AI review requested due to automatic review settings March 15, 2026 20:29
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.13%. Comparing base (a02edc0) to head (b4a7fa0).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #40      +/-   ##
==========================================
+ Coverage   94.12%   94.13%   +0.01%     
==========================================
  Files          23       24       +1     
  Lines        1720     1723       +3     
==========================================
+ Hits         1619     1622       +3     
  Misses        101      101              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Refactors the codec layer by replacing the previous structural Codec Protocol with a shared RTPCodec base class, and updates call handlers and documentation to use the new base type.

Changes:

  • Replace Codec Protocol with RTPCodec base class and update codec exports/registry.
  • Update AudioCall/AgentCall type hints to reference RTPCodec.
  • Add codec API docs page and refresh a few RFC links/docstrings.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
voip/codecs/opus.py Docstring link formatting updates for referenced RFCs.
voip/codecs/g722.py Adds/expands docstring for packetize behavior.
voip/codecs/base.py Updates module/class docs to position RTPCodec as the shared base.
voip/codecs/init.py Removes Codec Protocol export, exports RTPCodec, updates registry/get typing.
voip/audio.py Switches codec typing from Codec to RTPCodec.
voip/ai.py Switches codec typing from Codec to RTPCodec.
mkdocs.yml Adds Codecs page to API reference nav.
docs/codecs.md Introduces mkdocstrings page for voip.codecs.

You can also share your feedback on Copilot code review. Take the survey.

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 RTP codec layer to separate a lightweight RTPCodec base from a new PyAVCodec base (for codecs that require PyAV), and updates packaging/docs so G.711 codecs can run without PyAV while Opus/G.722 remain PyAV-backed.

Changes:

  • Introduces voip.codecs.av.PyAVCodec and moves PyAV encode/decode helpers out of voip.codecs.base.
  • Reworks PCMA/PCMU to be pure-NumPy for both encode and decode; updates registry to conditionally register PyAV codecs.
  • Updates extras/docs/navigation to describe “audio” vs “hd-audio” codec tiers.

Reviewed changes

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

Show a summary per file
File Description
voip/codecs/base.py Removes PyAV helpers and adds a shared NumPy resample() utility on RTPCodec.
voip/codecs/av.py Adds PyAVCodec with PyAV-backed decode_pcm/encode_pcm.
voip/codecs/pcma.py Switches PCMA decoding to pure NumPy (no PyAV).
voip/codecs/pcmu.py Switches PCMU decoding to pure NumPy (no PyAV).
voip/codecs/opus.py Moves Opus to inherit from PyAVCodec and renames Ogg helpers.
voip/codecs/g722.py Moves G722 to inherit from PyAVCodec; doc updates.
voip/codecs/__init__.py Registers PCMA/PCMU always; conditionally adds Opus/G722 when PyAV is importable.
voip/audio.py Uses RTPCodec typing and derives preferred codecs from the registry.
voip/ai.py Updates codec typing for preferred codec list.
tests/codecs/test_base.py Refocuses base tests onto RTPCodec.resample.
tests/codecs/test_av.py Adds tests for the new PyAVCodec helper methods.
tests/codecs/test_pcm.py Updates tests to no longer require PyAV for G.711 codecs.
tests/codecs/test_codecs.py Adds coverage for registry behavior with/without PyAV importability.
tests/test_audio.py Updates expectations wording around preferred codecs under PyAV.
pyproject.toml Splits extras into audio and hd-audio; adjusts ai/cli extras.
docs/codecs.md Adds codec documentation page and installation guidance for tiers.
mkdocs.yml Adds “Codecs” to the API Reference navigation.
docs/rtp.md Adds STUN docs section under RTP docs.
.github/workflows/ci.yml Adds hd-audio to the extras test matrix.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +53 to +55
# Reconstruct the biased linear magnitude from segment and mantissa.
biased = ((mantissa | 0x10) << (exp + 3)).astype(np.int32)
linear = ((biased - _MU_LAW_BIAS) / 32768.0).astype(np.float32)
Comment on lines +56 to +57
return cls.resample(
(sign * linear).astype(np.float32), cls.sample_rate_hz, output_rate_hz
from voip.codecs.opus import Opus # noqa: E402
from voip.codecs.pcma import PCMA # noqa: E402
from voip.codecs.pcmu import PCMU # noqa: E402
__all__ = ["G722", "Opus", "PCMA", "PCMU", "PyAVCodec", "RTPCodec", "get"]
Comment on lines +54 to +58
linear = np.where(
quantized < threshold,
quantized * _COMPRESS_SCALE / _A_LAW,
np.exp(quantized * _COMPRESS_SCALE - 1.0) / _A_LAW,
).astype(np.float32)
Comment on lines +59 to +60
return cls.resample(
(sign * linear).astype(np.float32), cls.sample_rate_hz, output_rate_hz
Comment on lines +98 to +105
if source_rate_hz == destination_rate_hz:
return audio
n_out = round(len(audio) * destination_rate_hz / source_rate_hz)
return np.interp(
np.linspace(0, len(audio) - 1, n_out),
np.arange(len(audio)),
audio,
).astype(np.float32)
@codingjoe
Copy link
Copy Markdown
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 15, 2026

@codingjoe I've opened a new pull request, #41, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits March 16, 2026 00:36
…ll__` (#41)

Addresses several correctness issues in the G.711 codec layer introduced
during the codec base-class refactor: wrong µ-law/A-law decode formulas,
`input_rate_hz` ignored as source rate, `__all__` exporting undefined
names without PyAV, and `resample()` crashing on edge inputs.

## PCMU (µ-law)

**Decode formula**: Replaced `(mantissa | 0x10) << (exp + 3)` with the
canonical ITU-T G.711 expansion:

```python
magnitude = (((mantissa << 3) + _MU_LAW_BIAS) << exp) - _MU_LAW_BIAS
```

Silence codeword `0x7F` now decodes to exactly `0.0` instead of a small
non-zero value.

## PCMA (A-law)

**Segmented algorithm**: Replaced the continuous companding
approximation (`log`/`exp`) with the standard ITU-T G.711 segmented
codec (Sun g711.c, 16-bit scale). Codeword `0xAA` now decodes to exactly
`0.984375` per spec, enabling bit-exact interop with real RTP streams.

**Vectorized segment lookup**: Segment index determination uses
`np.searchsorted` instead of an 8-step Python loop.

## Both PCMA and PCMU

**`input_rate_hz` as source rate**: `decode()` now uses `input_rate_hz`
(when provided) as the resampling source rate, correctly handling
non-standard G.711 clock rates advertised in SDP (e.g. `PCMA/16000`).

## `voip/codecs/__init__.py`

**Conditional `__all__`**: `G722`, `Opus`, `PyAVCodec` are now appended
to `__all__` only when the `try` import succeeds, preventing `from
voip.codecs import *` from raising `AttributeError` in environments
without PyAV.

## `RTPCodec.resample()`

**Edge cases**: Returns an empty `float32` array for empty input; clamps
`n_out` to a minimum of `1` for non-empty input, avoiding `numpy.interp`
failures during aggressive downsampling.

## Tests

- Golden codeword assertions: `PCMU 0x7F → 0.0`, `PCMA 0xAA → 0.984375`,
`PCMA 1.0 → 0xAA`, `PCMA 0.0 → 0xD5`
- `input_rate_hz`-as-source-rate tests for both codecs
- `resample()` empty-input and single-sample heavy-downsample edge cases
- `__all__` conditional test (no-PyAV scenario)

<!-- START COPILOT CODING AGENT TIPS -->
---

📱 Kick off Copilot coding agent tasks wherever you are with [GitHub
Mobile](https://gh.io/cca-mobile-docs), available on iOS and Android.

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: codingjoe <[email protected]>
Co-authored-by: Johannes Maron <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@codingjoe codingjoe merged commit aaed58f into main Mar 16, 2026
24 checks passed
@codingjoe codingjoe deleted the codev branch March 16, 2026 09:15
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.

3 participants