Add two-way audio (talk) support via Baichuan protocol - #165
Conversation
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.
- 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
|
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. |
|
@fredrsat Thank you for the awesome work!! I have tested it and it works great on my camera. |
…ebhook templates)
|
I built a Home Assistant custom integration (ha-reolink-talk) around this
Happy to help test against my hardware if useful. This would be a great |
Summary
Implements two-way audio (talk) for Reolink cameras via the Baichuan protocol, resolving #161.
audioopmodule (Python 3.13+)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.py—talk(),_send_talk_frame(), TalkAbility config storage (+149 lines)reolink_aio/baichuan/xmls.py—TALK_CONFIG_XMLandBINARY_EXTENSION_XMLtemplates (+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
Closes #161