Audio Streaming¶
Audio RX/TX via the Icom audio UDP port (default 50003).
Naming Map¶
Low-level Opus methods are now explicitly suffixed with _opus.
High-level PCM APIs are available for both RX and TX.
| Scope | Preferred method names |
|---|---|
| Low-level Opus (current) | start_audio_rx_opus, stop_audio_rx_opus, start_audio_tx_opus, push_audio_tx_opus, stop_audio_tx_opus, start_audio_opus, stop_audio_opus |
| High-level PCM | start_audio_rx_pcm, stop_audio_rx_pcm, start_audio_tx_pcm, push_audio_tx_pcm, stop_audio_tx_pcm |
Deprecated aliases still work during the deprecation window (two minor releases):
start_audio_rx, stop_audio_rx, start_audio_tx, push_audio_tx, stop_audio_tx, start_audio, stop_audio.
AudioStream¶
icom_lan.audio.AudioStream
¶
Manages audio RX/TX on the Icom audio UDP port.
Uses an :class:IcomTransport for the underlying UDP communication
(discovery, pings, retransmit). Audio-specific packet framing is
handled here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transport
|
IcomTransport
|
Connected IcomTransport for the audio port. |
required |
Example::
stream = AudioStream(audio_transport)
await stream.start_rx(my_callback)
# ... later
await stream.stop_rx()
state
property
¶
Current audio stream state.
transport
property
¶
Underlying transport.
get_audio_stats()
¶
Return runtime audio stats for the current stream.
Metrics and units:
rx_packets_received/rx_packets_delivered/tx_packets_sent: packet counters (>= 0).packets_lost: inferred missing RX packets (>= 0).packet_loss_percent: percentage in [0.0, 100.0].jitter_ms/jitter_max_ms: sequence-jitter estimates in ms (>= 0.0).underrun_count/overrun_count: jitter-buffer event counters (>= 0).estimated_latency_ms: current buffering latency estimate in ms (>= 0.0).jitter_buffer_depth_packets/jitter_buffer_pending_packets: packet counts (>= 0).
push_tx(opus_data)
async
¶
Send an Opus-encoded audio frame to the radio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
opus_data
|
bytes
|
Opus-encoded audio data. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not in transmitting state. |
start_rx(callback, *, jitter_depth=None)
async
¶
Start receiving audio from the radio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
Callable[[AudioPacket | None], None]
|
Called with each decoded :class: |
required |
jitter_depth
|
int | None
|
Override jitter buffer depth (0 to disable). Defaults to the value set at construction time. |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If already receiving or transmitting. |
start_tx()
async
¶
Start transmitting audio to the radio.
Can be called while already receiving (full-duplex).
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If already transmitting. |
stop_rx()
async
¶
Stop receiving audio and flush remaining buffered packets.
stop_tx()
async
¶
Stop transmitting audio.
If RX is still active, state reverts to RECEIVING.
Runtime Audio Stats (get_audio_stats)¶
Use get_audio_stats() on AudioStream or IcomRadio to retrieve a JSON-friendly
snapshot of live stream quality metrics.
Metrics, Units, Bounds¶
| Field | Unit | Bounds | Notes |
|---|---|---|---|
active |
boolean | true/false |
Whether stream state is not idle |
state |
string | idle / receiving / transmitting |
Current stream state |
rx_packets_received |
packets | >= 0 |
Parsed RX audio packets |
rx_packets_delivered |
packets | >= 0 |
RX packets delivered to callback |
tx_packets_sent |
packets | >= 0 |
TX packets sent |
packets_lost |
packets | >= 0 |
Inferred missing RX packets |
packet_loss_percent |
percent | 0.0..100.0 |
packets_lost / (delivered + lost) |
jitter_ms |
milliseconds | >= 0.0 |
Smoothed sequence-jitter estimate |
jitter_max_ms |
milliseconds | >= 0.0 |
Peak observed jitter estimate |
underrun_count |
events | >= 0 |
Jitter-buffer underrun events |
overrun_count |
events | >= 0 |
Jitter-buffer overrun events |
estimated_latency_ms |
milliseconds | >= 0.0 |
Estimated buffering delay |
jitter_buffer_depth_packets |
packets | >= 0 |
Configured jitter depth (0 when disabled) |
jitter_buffer_pending_packets |
packets | >= 0 |
Currently buffered packets |
duplicates_dropped |
packets | >= 0 |
Duplicate RX packets dropped |
stale_packets_dropped |
packets | >= 0 |
Stale/old RX packets dropped |
out_of_order_packets |
packets | >= 0 |
RX packets observed out of sequence |
AudioPacket¶
icom_lan.audio.AudioPacket
dataclass
¶
Parsed audio packet.
Attributes:
| Name | Type | Description |
|---|---|---|
ident |
int
|
Audio stream identifier (0x0080 for TX, varies for RX). |
send_seq |
int
|
Audio-level sequence number. |
data |
bytes
|
Opus-encoded audio data (raw bytes after header). |
AudioState¶
icom_lan.audio.AudioState
¶
Bases: StrEnum
Audio stream state.
JitterBuffer¶
icom_lan.audio.JitterBuffer
¶
Reorder-and-delay buffer for incoming audio packets.
Collects packets and delivers them in sequence-number order after
a configurable depth of buffering. Handles out-of-order packets,
duplicates, and gaps (delivering None for missing packets).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
Number of packets to buffer before delivery (default 5, which is ~100 ms at 20 ms/packet). |
5
|
Example::
jb = JitterBuffer(depth=5)
for pkt in jb.push(audio_packet):
if pkt is None:
# gap — insert silence
...
else:
play(pkt.data)
depth
property
¶
Configured buffer depth (number of packets).
duplicate_count
property
¶
Count of duplicate packets dropped.
gap_count
property
¶
Count of inferred missing packets (gap placeholders).
overrun_count
property
¶
Count of jitter-buffer overrun events.
pending
property
¶
Number of packets currently held in the buffer.
stale_count
property
¶
Count of stale/old packets dropped.
underrun_count
property
¶
Count of jitter-buffer underrun events.
flush()
¶
Flush all buffered packets in order (for stream end).
Returns:
| Type | Description |
|---|---|
list[AudioPacket | None]
|
Remaining packets in order (None for gaps). |
push(packet)
¶
Insert a packet and return any packets ready for delivery.
Packets are delivered in order. If a gap is detected (missing
sequence number), None is yielded in its place.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
packet
|
AudioPacket
|
Incoming audio packet. |
required |
Returns:
| Type | Description |
|---|---|
list[AudioPacket | None]
|
List of packets (or None for gaps) ready for playback. |
list[AudioPacket | None]
|
May be empty if more buffering is needed. |
Packet Functions¶
icom_lan.audio.parse_audio_packet(data)
¶
Parse a raw UDP audio packet into an :class:AudioPacket.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes
|
Raw UDP packet bytes (must be > 0x18 bytes). |
required |
Returns:
| Type | Description |
|---|---|
AudioPacket | None
|
Parsed AudioPacket, or None if the packet is too short or |
AudioPacket | None
|
is a control/retransmit packet (type != DATA). |
icom_lan.audio.build_audio_packet(opus_data, *, sender_id, receiver_id, send_seq, ident=TX_IDENT)
¶
Build a raw UDP audio packet from Opus data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
opus_data
|
bytes
|
Opus-encoded audio frame. |
required |
sender_id
|
int
|
Our connection ID. |
required |
receiver_id
|
int
|
Radio's connection ID. |
required |
send_seq
|
int
|
Audio-level sequence number. |
required |
ident
|
int
|
Audio ident field (default TX_IDENT=0x0080). |
TX_IDENT
|
Returns:
| Type | Description |
|---|---|
bytes
|
Complete UDP packet bytes ready to send. |
Internal Transcoder Layer¶
icom_lan now includes an internal PCM<->Opus transcoder foundation used for
future high-level PCM APIs.
- Module:
icom_lan._audio_transcoder(internal, no stability guarantee yet) - Backend: optional
opuslib(pip install icom-lan[audio]) - Typed failures:
AudioCodecBackendErrorfor missing backendAudioFormatErrorfor invalid PCM/Opus frame formatsAudioTranscodeErrorfor codec encode/decode failures
Usage¶
RX Audio (callback-based)¶
async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
received = []
def on_audio(pkt):
if pkt is not None: # None = gap (missing packet)
received.append(pkt.data)
await radio.start_audio_rx_opus(on_audio)
await asyncio.sleep(10)
await radio.stop_audio_rx_opus()
RX Audio (high-level PCM)¶
async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
def on_pcm(frame: bytes | None) -> None:
if frame is None:
return # gap placeholder from jitter buffer
# frame is 16-bit little-endian PCM for configured format
process_pcm(frame)
await radio.start_audio_rx_pcm(
on_pcm,
sample_rate=48000,
channels=1,
frame_ms=20,
jitter_depth=5,
)
await asyncio.sleep(10)
await radio.stop_audio_rx_pcm()
TX Audio (push-based)¶
async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
await radio.start_audio_tx_opus()
await radio.push_audio_tx_opus(opus_frame)
await radio.stop_audio_tx_opus()
TX Audio (high-level PCM)¶
async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
await radio.start_audio_tx_pcm(sample_rate=48000, channels=1, frame_ms=20)
await radio.push_audio_tx_pcm(pcm_frame) # one 20ms PCM frame (1920 bytes)
await radio.stop_audio_tx_pcm()
Full-Duplex¶
async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
await radio.start_audio_opus(rx_callback=on_audio, tx_enabled=True)
# ... push TX frames, receive RX via callback ...
await radio.stop_audio_opus()
Codec Selection¶
from icom_lan import IcomRadio, AudioCodec
radio = IcomRadio(
"192.168.1.100",
audio_codec=AudioCodec.PCM_1CH_16BIT, # default
audio_sample_rate=48000,
)
Capability Introspection¶
Use the capability API to inspect negotiated client-side audio options and defaults:
from icom_lan import IcomRadio
caps = IcomRadio.audio_capabilities()
print(caps.supported_codecs)
print(caps.supported_sample_rates_hz)
print(caps.supported_channels)
print(caps.default_codec, caps.default_sample_rate_hz, caps.default_channels)
Deterministic default selection rules:
- Codec: first supported codec in icom-lan preference order.
- Sample rate: highest supported sample rate.
- Channels: the channel count implied by default codec (fallback: minimum supported channels).
Opus codecs
OPUS_1CH (0x40) and OPUS_2CH (0x41) are only supported when
the radio reports connection_type == "WFVIEW". Standard connections
use LPCM16 (0x04).
Migration¶
Use the explicit _opus methods now:
| Deprecated alias | Replacement |
|---|---|
start_audio_rx |
start_audio_rx_opus |
stop_audio_rx |
stop_audio_rx_opus |
start_audio_tx |
start_audio_tx_opus |
push_audio_tx |
push_audio_tx_opus |
stop_audio_tx |
stop_audio_tx_opus |
start_audio |
start_audio_opus |
stop_audio |
stop_audio_opus |
For RX PCM, migrate callback-side decoding to the built-in API:
- Before:
start_audio_rx_opus()+ manual Opus decode in callback. - Now:
start_audio_rx_pcm()and receivebytes | Nonedirectly.
For TX PCM, migrate manual Opus encoding to the built-in API:
- Before: encode PCM to Opus yourself, then
push_audio_tx_opus(). - Now:
start_audio_tx_pcm()andpush_audio_tx_pcm()with fixed-size PCM frames.