Skip to content

Fix G.722 ADPCM state reset causing robotic/truncated echo audio#43

Merged
codingjoe merged 6 commits intomainfrom
copilot/fix-g722-sound-issue
Mar 16, 2026
Merged

Fix G.722 ADPCM state reset causing robotic/truncated echo audio#43
codingjoe merged 6 commits intomainfrom
copilot/fix-g722-sound-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 16, 2026

G722.decode created a new av.open() context per RTP packet, resetting the ADPCM predictor to zero every 20 ms. Since the remote encoder accumulates predictor state across packets, only packet 0 decoded correctly — packets 1+ decoded to near-silence. The VAD discarded these silent frames, so the speech buffer collected ≈20 ms of audio regardless of utterance length, producing a truncated, robotic echo.

Changes

  • voip/codecs/g722.pyG722Decoder: stateful decoder dataclass that holds a single persistent av.CodecContext(sample_rate=16000) for the call lifetime; each packet is fed into the same context, preserving predictor state across boundaries.

  • voip/codecs/g722.pyG722.create_decoder: factory classmethod returning a G722Decoder for per-call use.

  • voip/codecs/base.py — decoder infrastructure:

    • PayloadDecoder Protocol — common interface (decode(bytes) → np.ndarray)
    • PerPacketDecoder — stateless wrapper for PCMA/PCMU/Opus; delegates to RTPCodec.decode per packet (preserves existing behavior)
    • RTPCodec.create_decoder() — default factory, returns PerPacketDecoder; overridden by G722
  • voip/audio.pyAudioCall: __post_init__ now calls codec.create_decoder() and stores the result as payload_decoder; decode_payload delegates to self.payload_decoder.decode(payload).

# G.722 call → stateful G722Decoder (preserves ADPCM state)
call = AudioCall(rtp=..., sip=..., media=g722_media, caller=...)
assert isinstance(call.payload_decoder, G722Decoder)

# PCMA/PCMU/Opus → PerPacketDecoder (stateless, unchanged behaviour)
call = AudioCall(rtp=..., sip=..., media=pcma_media, caller=...)
assert isinstance(call.payload_decoder, PerPacketDecoder)

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • releases.astral.sh
    • Triggering command: /home/REDACTED/.local/bin/uv /home/REDACTED/.local/bin/uv run --extra=audio --extra=hd-audio pytest tests/codecs/test_g722.py tests/test_audio.py -x -q (dns block)
  • stun.cloudflare.com
    • Triggering command: /home/REDACTED/work/VoIP/VoIP/.venv/bin/pytest pytest -q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>🐛 G.722 is sounding off</issue_title>
<issue_description>### Bug Description

When I use the echo call, the response is audible but broken. It seems too short and sounds "like a robot". The pitch and gain seem correct.

Expected Behavior

I should hear my voice in the echo call clear as day, since it's a high-res call and I have a wired broadband connection to the UAS. My ping stats are round-trip min/avg/max/stddev = 26.084/29.938/32.507/2.109 ms.

Version

0.2.1

Stack trace

No response

Steps to Reproduce

No response</issue_description>

Comments on the Issue (you are @copilot in this section)

Custom agent used: SuperJoe
CodingJoe's digital clone following his coding guidelines and best practices.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 16, 2026

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #43      +/-   ##
==========================================
+ Coverage   94.13%   94.25%   +0.11%     
==========================================
  Files          24       24              
  Lines        1723     1759      +36     
==========================================
+ Hits         1622     1658      +36     
  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.

Copilot AI changed the title [WIP] [G-722] Fix broken audio in echo call Fix G.722 ADPCM state reset causing robotic/truncated echo audio Mar 16, 2026
Copilot AI requested a review from codingjoe March 16, 2026 11:04
Copy link
Copy Markdown
Owner

@codingjoe codingjoe left a comment

Choose a reason for hiding this comment

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

GTM

@codingjoe codingjoe marked this pull request as ready for review March 16, 2026 11:45
Copilot AI review requested due to automatic review settings March 16, 2026 11:45
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

Adds per-call, stateful G.722 decoding to preserve ADPCM predictor state across RTP packets, fixing truncated/robotic audio in echo/VAD flows.

Changes:

  • Introduce PayloadDecoder + RTPCodec.create_decoder() and a PerPacketDecoder default to support per-call decoder instances.
  • Add G722Decoder dataclass holding a persistent PyAV codec context, and override G722.create_decoder() to return it.
  • Update AudioCall to instantiate and use a per-call payload_decoder; extend tests and docs accordingly.

Reviewed changes

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

Show a summary per file
File Description
voip/codecs/g722.py Adds G722Decoder and G722.create_decoder() for stateful per-call decode.
voip/codecs/base.py Adds decoder protocol + default per-packet decoder factory infrastructure.
voip/audio.py Switches payload decoding to a per-call decoder instance (payload_decoder).
tests/test_audio.py Updates/extends AudioCall.decode_payload tests for decoder routing.
tests/codecs/test_g722.py Adds tests validating stateful decoding and documenting the original bug.
tests/codecs/test_base.py Adds tests for RTPCodec.create_decoder() / PerPacketDecoder.
docs/codecs.md Documents new decoder types in API docs.

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

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
@codingjoe codingjoe merged commit 4dc14e2 into main Mar 16, 2026
24 checks passed
@codingjoe codingjoe deleted the copilot/fix-g722-sound-issue branch March 16, 2026 12:37
@codingjoe
Copy link
Copy Markdown
Owner

@copilot this worked for the echo call, but the agent suffers from the same strange sound. Open a new PR against main and check if the TTS output is correctly send via G.722

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.

🐛 G.722 is sounding off

3 participants