Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
CodecProtocol withRTPCodecbase class and update codec exports/registry. - Update
AudioCall/AgentCalltype hints to referenceRTPCodec. - 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.
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
There was a problem hiding this comment.
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.PyAVCodecand moves PyAV encode/decode helpers out ofvoip.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.
voip/codecs/pcmu.py
Outdated
| # 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) |
voip/codecs/pcmu.py
Outdated
| return cls.resample( | ||
| (sign * linear).astype(np.float32), cls.sample_rate_hz, output_rate_hz |
voip/codecs/__init__.py
Outdated
| 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"] |
voip/codecs/pcma.py
Outdated
| linear = np.where( | ||
| quantized < threshold, | ||
| quantized * _COMPRESS_SCALE / _A_LAW, | ||
| np.exp(quantized * _COMPRESS_SCALE - 1.0) / _A_LAW, | ||
| ).astype(np.float32) |
voip/codecs/pcma.py
Outdated
| return cls.resample( | ||
| (sign * linear).astype(np.float32), cls.sample_rate_hz, output_rate_hz |
| 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) |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@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. |
…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>
No description provided.