Audio - icom-lan
Description: Python library for controlling Icom transceivers over LAN

Skip to content

# Audio Streaming

Audio RX/TX via the Icom audio UDP port (default 50003).

## 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.

#### `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]` | Called with each decoded :class:`AudioPacket`. When jitter buffering is enabled, `None` may be passed for gap placeholders (missing packets). | 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.

## 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).

#### `pending` `property`

Number of packets currently held in the buffer.

#### `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.

## 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(on_audio)
        await asyncio.sleep(10)
        await radio.stop_audio_rx()

### TX Audio (push-based)

    async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
        await radio.start_audio_tx()
        await radio.push_audio_tx(opus_frame)
        await radio.stop_audio_tx()

### Full-Duplex

    async with IcomRadio("192.168.1.100", username="u", password="p") as radio:
        await radio.start_audio(rx_callback=on_audio, tx_enabled=True)
        # ... push TX frames, receive RX via callback ...
        await radio.stop_audio()

### 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,
    )

Opus codecs

`OPUS_1CH` (0x40) and `OPUS_2CH` (0x41) are only supported when the radio reports `connection_type == "WFVIEW"`. Standard connections use LPCM16 (0x04).