# How the Push-to-Talk (PTT) Voice Burst Protocol Works in BitChat

> Discover how BitChat's PTT voice burst protocol streams live audio over BLE or direct messages, ensuring walkie-talkie functionality and guaranteed delivery.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-07-31

---

**BitChat’s push-to-talk (PTT) voice burst protocol streams live AAC-LC audio frames over BLE mesh or Noise-encrypted direct messages while simultaneously recording a fallback voice-note file, ensuring real-time walkie-talkie functionality with guaranteed delivery.**

The **voice burst protocol** is the core mechanism that enables BitChat’s smart Push-to-Talk (PTT) feature. Unlike traditional voice notes that require full recording before transmission, this protocol—implemented in the `permissionlesstech/bitchat` repository—transmits encoded audio packets the moment the user presses the microphone button. The system gracefully degrades to a standard voice-note file if live streaming fails, providing both low-latency interaction and reliability.

## Capturing and Encoding Voice Bursts

Audio capture begins the instant the user taps the microphone button, creating a real-time pipeline from hardware to network.

### Audio Capture Pipeline

The `PTTCaptureEngine` class ([`bitchat/Features/voice/PTTCaptureEngine.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/PTTCaptureEngine.swift)) instantiates a fresh `AVAudioEngine` for each PTT session. It installs an input tap on the audio node, then pipes raw PCM buffers through `PTTInputResampler` to convert the stream to **16 kHz mono** format. This standardized format ensures consistent encoding regardless of device hardware capabilities.

### AAC-LC Frame Encoding

Resampled audio flows into `PTTFrameEncoder` ([`bitchat/Features/voice/PTTAudioCodec.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/PTTAudioCodec.swift)), which compresses the stream using **AAC-LC** at approximately **16 kbps**. Each encoded frame produces a payload of roughly **130 bytes**, extracted from `AVAudioCompressedBuffer` objects. These compact frames minimize wireless transmission latency while maintaining acceptable voice quality for walkie-talkie-style communication.

## The Voice Burst Wire Protocol

A *voice burst* consists of a sequence of packets sharing an 8-byte random `burstID`. The wire format uses a compact binary header:

```

[burstID:8][seq:UInt16 BE][flags:1][payload…]

```

- **`burstID`** – A random 8-byte identifier linking all frames in a single utterance.
- **`seq`** – Big-endian UInt16 sequence number for ordering and loss detection.
- **`flags`** – Control bits indicating frame type:
  - **START (0x01)** – Sent at the beginning and retransmitted every ~2 seconds for mid-burst synchronization, carrying codec description metadata.
  - **DATA (0x00)** – Standard audio payload containing length-prefixed AAC frames.
  - **END (0x02)** – Signals burst completion and carries total packet count and duration.
  - **CANCELED (0x04)** – Immediately aborts playback on receivers.

Transmission occurs via dedicated packet types: **0x29** for public BLE mesh broadcasts and **0x08** for Noise-encrypted direct messages ([`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) and [`bitchat/Nostr/BitchatProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/BitchatProtocol.swift)).

## Session Lifecycle and Transmission

The `PTTLiveVoiceSession` class ([`bitchat/Features/voice/VoiceCaptureSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/VoiceCaptureSession.swift)) orchestrates the end-to-end flow, subclassing `VoiceCaptureSession` to manage the dual-path streaming and recording logic.

### Session State Management

When initiated, the session generates a unique `burstID` and enters a streaming state. The `onFrames` closure in `PTTCaptureEngine` delivers encoded AAC data to `VoiceBurstPacketizer`, which wraps payloads in the wire format described above. The session simultaneously writes the same PCM data to an `.m4a` file on disk, creating the fallback voice note.

### BLE Mesh Transmission

Each packetized frame routes through `BLEService.sendVoiceFrame` for direct messages, or as a signed `MessageType.voiceFrame` for public mesh broadcasts. The protocol transmits frames without fragmentation, relying on the underlying BLE link layer for packet integrity.

## Receiving and Playing Voice Bursts

Inbound voice bursts reassemble on the receiver side through a coordinated assembly and playback pipeline.

### Frame Assembly and Buffering

`ChatLiveVoiceCoordinator` ([`bitchat/ViewModels/ChatLiveVoiceCoordinator.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatLiveVoiceCoordinator.swift)) monitors incoming packets and instantiates a `VoiceBurstAssembler` for each unique `(sender, burstID)` pair. The assembler maintains a jitter buffer, reordering frames by sequence number and detecting gaps. Missing frames are substituted with silence to prevent playback stutter.

### Playback and Fallback Handling

Once approximately **350 ms** of audio buffers, the `PTTBurstPlayer` begins decoding via `PTTFrameDecoder` and pipes PCM data to an `AVAudioPlayerNode`. When the burst terminates—signaled by the `END` flag or a timeout—the player writes the assembled AAC frames to a temporary file, later promoted to `voice_<burstID>.m4a`. If the receiver already rendered the live burst, this final file replaces the provisional bubble, ensuring no data is lost if packets dropped during transmission.

## Policy Controls and UX Behavior

BitChat applies strict policy gates to prevent accidental live audio transmission. The PTT option appears only when `noiseService.hasEstablishedSession` returns true or when the peer is reachable on the mesh; otherwise, the UI defaults to classic voice-note recording ([`docs/PUSH-TO-TALK-DESIGN.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/PUSH-TO-TALK-DESIGN.md)).

Users control live voice functionality globally via `PTTSettings.liveVoiceEnabled` ([`bitchat/Features/voice/PTTSettings.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/PTTSettings.swift)). When disabled, incoming bursts store silently without auto-playback, displaying a "live bubble" requiring manual tap to hear. Auto-playback activates only when the app is foregrounded, the conversation is active, and the toggle is enabled.

## Summary

- **Real-time streaming** – The voice burst protocol transmits AAC-LC frames immediately upon capture using an 8-byte `burstID` and 16-bit sequence numbers for ordering.
- **Dual-path reliability** – While streaming live, the system records an `.m4a` fallback file named with the `burstID`, ensuring delivery even if the live stream fails.
- **Wire efficiency** – Compact 130-byte frames at ~16 kbps travel over BLE packet types `0x29` (public) and `0x08` (DM) without fragmentation.
- **Smart assembly** – Receivers buffer ~350 ms before playback, filling missing frames with silence and finalizing bursts into standard voice-note files.
- **User control** – Global settings and session prerequisites prevent unintended transmission, requiring explicit Noise sessions or mesh visibility.

## Frequently Asked Questions

### What audio codec does BitChat use for PTT voice bursts?

BitChat uses **AAC-LC** (Advanced Audio Coding Low Complexity) running at **16 kbps**. The `PTTFrameEncoder` class converts 16 kHz mono PCM buffers into ~130-byte compressed frames, balancing bandwidth efficiency with intelligible voice quality suitable for push-to-talk communication.

### How does BitChat handle packet loss during a live voice burst?

The protocol employs a jitter buffer in `VoiceBurstAssembler` that reorders frames by sequence number. Missing sequence numbers are detected after a timeout, and the `PTTBurstPlayer` substitutes silence for lost frames rather than pausing playback. Additionally, the fallback `.m4a` file sent after the burst completes allows receivers to replace any incomplete live playback with the full recording.

### What is the latency budget for live PTT playback?

The receiver intentionally delays playback for approximately **350 milliseconds** to accumulate a buffer of incoming frames. This jitter buffer absorbs network variance and prevents underruns, resulting in a total end-to-end latency typically under one second—sufficient for natural walkie-talkie conversation without noticeable echo or collision.

### How does the system distinguish between live PTT and regular voice notes?

The UI and backend check for established Noise sessions or mesh reachability before offering the live PTT mode. When active, `PTTLiveVoiceSession` manages the dual stream-and-record pipeline, whereas standard voice notes use the `VoiceCaptureSession` base class without the packetizer or `VoiceBurstPacketizer` components. Receivers identify live bursts by the `MessageType.voiceFrame` packet type and the presence of burst headers, distinct from `fileTransfer` voice-note deliveries.