Skip to content

Add two-way audio (talk) support via Baichuan protocol - #165

Open
fredrsat wants to merge 4 commits into
starkillerOG:mainfrom
fredrsat:add-two-way-audio
Open

Add two-way audio (talk) support via Baichuan protocol#165
fredrsat wants to merge 4 commits into
starkillerOG:mainfrom
fredrsat:add-two-way-audio

Conversation

@fredrsat

@fredrsat fredrsat commented Apr 3, 2026

Copy link
Copy Markdown

Summary

Implements two-way audio (talk) for Reolink cameras via the Baichuan protocol, resolving #161.

  • Pure-Python IMA ADPCM encoder — no dependency on the removed audioop module (Python 3.13+)
  • BcMedia frame builder — wraps ADPCM blocks in the wire format expected by Reolink cameras
  • Mixed-encryption talk frames — AES-encrypted extension XML + raw binary audio payload (cmd_id=202)
  • Full talk session lifecycle — TalkConfig (201) → audio frames (202) → TalkReset (11), with 422 retry
  • TalkAbility parsing — extracts sample_rate, block_size, duplex, and stream_mode from capability discovery

New files

  • reolink_aio/baichuan/audio.py — IMA ADPCM encoder + BcMedia frame builder (188 lines)
  • tests/test_talk.py — 30 unit tests covering encoding, framing, header construction, and session lifecycle (392 lines)

Modified files

  • reolink_aio/baichuan/baichuan.pytalk(), _send_talk_frame(), TalkAbility config storage (+149 lines)
  • reolink_aio/baichuan/xmls.pyTALK_CONFIG_XML and BINARY_EXTENSION_XML templates (+24 lines)

Public API

await host.baichuan.talk(channel=0, audio_data=pcm_bytes)

Where audio_data is raw PCM (16-bit signed LE, mono) at the camera's sample rate. Optional sample_rate and block_size overrides.

Protocol reference

Based on reverse-engineering from the https://github.com/QuantumEntangledAndy/neolink project (Rust).

Test plan

  • Unit tests pass: python -m pytest tests/test_talk.py (30 tests)
  • ADPCM encoding verified against reference implementation
  • Tested against real hardware: Reolink Home Hub + Argus 4 Pro
  • Sine wave tone plays correctly through camera speaker
  • ElevenLabs TTS speech plays clearly through camera speaker
  • Talk session handles 422 (busy) with automatic reset and retry
  • TalkReset cleanup runs in finally block (no orphaned sessions)
  • Pre-existing tests unaffected

Closes #161

Implement sending audio to camera speakers using the Baichuan TCP
protocol (cmd_ids 10, 11, 201, 202). This addresses the long-standing
request for two-way audio support (starkillerOG#161).

New files:
- baichuan/audio.py: Pure-Python IMA ADPCM (DVI-4) encoder and BcMedia
  frame builder. No external dependencies (audioop was removed in 3.13).
- tests/test_talk.py: 30 unit tests covering ADPCM encoding, BcMedia
  framing, talk frame header construction, and talk() orchestration.

Modified files:
- baichuan/xmls.py: Add BINARY_EXTENSION_XML (with binaryData flag for
  binary payloads) and TALK_CONFIG_XML templates.
- baichuan/baichuan.py:
  - Parse and cache audio config from TalkAbility (cmd_id=10) response
  - Add _send_talk_frame(): fire-and-forget method that AES-encrypts
    the extension XML but sends the audio payload as raw binary
  - Add talk(): public method that starts a talk session (TalkConfig),
    encodes PCM to ADPCM, sends BcMedia frames paced to audio duration,
    and resets the session (TalkReset). Handles 422 (busy) gracefully.

Protocol details:
- Audio format: IMA ADPCM, 16-bit, mono, 8000/16000 Hz (from TalkAbility)
- cmd_id 202 uses mixed encryption: AES-encrypted extension + raw payload
- BcMedia frames use "01wb" magic with 8-byte aligned padding
- Up to 4 ADPCM blocks packed per Baichuan message for efficiency

Tested against Reolink Home Hub with Argus 4 Pro cameras.
lorek123 and others added 2 commits April 10, 2026 19:11
- Fix two_way_audio capability detection: cameras using "followVideoStream"
  (e.g. Reolink E1) were not being marked as capable; now any camera that
  returns an audioConfig in TalkAbility is correctly detected
- Fix cmd_id=10 not called during init on standalone cameras: add
  `or self.http_api.baichuan_only` guard so TalkAbility is always
  queried during channel discovery on baichuan-only devices
- Add split API: get_talk_ability() / start_talk() / stop_talk() /
  send_talk_data() on both Baichuan and Host, plus build_bcmedia_adpcm()
  static helper for BcMedia ADPCM framing
- Add TalkConfigSet XML template with all audio fields parameterized
- Rename _send_talk_frame() → send_binary_no_reply() with generalized
  signature (cmd_id, channel, binary_body); talk() updated to use it
- Update tests: rename TestSendTalkFrame → TestSendBinaryNoReply, add
  TestBuildBcmediaAdpcm, TestSplitTalkApi, TestTwoWayAudioCapability
  (52 tests total, all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix standalone camera support and add split talk API
@fredrsat

Copy link
Copy Markdown
Author

Updated with contributions from @lorek123: E1 standalone camera bugfix (baichuan_only guard for cmd_id=10), followVideoStream capability detection, and a split talk API (start_talk/send_talk_data/stop_talk). 52 tests passing.

@mrtncode

mrtncode commented Jul 5, 2026

Copy link
Copy Markdown

@fredrsat Thank you for the awesome work!! I have tested it and it works great on my camera.
I have forked your version to use it with my camera until it get merged. I hope thats okay :)

@matthewholm

Copy link
Copy Markdown

I built a Home Assistant custom integration (ha-reolink-talk) around this
exact protocol months before finding this PR, reverse-engineering the same
TalkAbility/TalkConfig/ADPCM/BcMedia framing independently. Wanted to add
some real-world validation:

  • Running in production against a real Reolink Home Hub + battery camera
    for several weeks (both one-shot TTS playback and live streaming
    push-to-talk from a browser microphone).
  • Same 400/421/422 retry-on-stuck-session behavior you've implemented here
    was necessary in my testing too -- cameras do leave sessions dangling.
  • The split start_talk/send_talk_data/stop_talk API is exactly the shape
    needed for streaming use cases (I built the equivalent by hand against
    private internals since no public API existed) -- this would let me
    drop a few hundred lines of my own protocol code and depend on this
    instead.

Happy to help test against my hardware if useful. This would be a great
addition -- hope it can land soon.

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.

AudioTalk / Two Way Audio Interest

4 participants