# VoiceBurstPacket Format in BitChat: Binary Protocol for Live Voice Notes

> Discover the VoiceBurstPacket format used by BitChat for efficient live voice notes. Learn how this compact binary protocol groups AAC audio frames into sequenced packets for push-to-talk communication.

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

---

**BitChat transmits push‑to‑talk voice notes using VoiceBurstPacket, a compact binary format that groups AAC audio frames into sequenced packets identified by an 8‑byte burst ID.**

BitChat implements live voice messaging through a lightweight wire protocol defined in the `permissionlesstech/bitchat` repository. The VoiceBurstPacket format structures each transmission into sequenced binary packets that carry start signals, encoded audio data, and termination markers. This design enables efficient streaming of AAC‑LC encoded voice bursts across mesh networks with minimal overhead.

## Binary Wire Structure

In [`bitchat/Protocols/VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/VoiceBurstPacket.swift), the packet layout is defined as a contiguous byte sequence with four distinct fields:

| Field | Size | Description |
|-------|------|-------------|
| **burstID** | 8 bytes | Cryptographically random identifier that groups all packets belonging to the same voice burst session. |
| **seq** | 2 bytes (big‑endian) | Sequence number where `0` is reserved for the START packet; data packets begin at `1`. |
| **flags** | 1 byte | Determines packet kind: START, END, CANCELED, or DATA. |
| **payload** | Variable | Content interpreted according to the flags byte. |

The implementation enforces constants such as `burstIDSize = 8` and `maxFramesPerPacket = 8` to constrain memory usage and network bandwidth.

## Packet Types and Payload Formats

The **flags** byte determines how receivers parse the variable‑length payload section.

### START Packet (0x01)

When `flags = 0x01`, the payload contains a single unsigned byte indicating the audio codec:

```

[codec: UInt8]  // 0x01 = AAC‑LC 16kHz mono

```

Currently, BitChat only supports AAC‑LC at 16kHz mono (`0x01`), though the format reserves space for future codec additions.

### DATA Packet (0x00)

Data packets carry one or more encoded AAC frames using a length‑prefixed framing scheme:

```

[length: UInt16 BE][frame bytes] [length: UInt16 BE][frame bytes] ...

```

Each frame is preceded by a 2‑byte big‑endian length field. A single VoiceBurstPacket can contain up to **8 frames** (the `maxFramesPerPacket` limit), allowing implementations to amortize packet overhead across multiple audio frames.

### END Packet (0x02)

Termination packets signal the completion of a burst and include metadata about the transmission:

```

[totalDataPackets: UInt16 BE][durationMs: UInt32 BE]

```

The **totalDataPackets** field counts all DATA packets sent in the burst, while **durationMs** records the total voice duration in milliseconds.

### CANCELED Packet (0x04)

If the sender aborts transmission, a packet with `flags = 0x04` and an empty payload notifies receivers to discard any buffered data for that burst ID.

## Encoding and Decoding in Swift

The [`VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceBurstPacket.swift) file provides Swift structs and methods to construct and serialize packets. Below are practical examples demonstrating the construction of each packet type:

```swift
import Foundation

// 1️⃣ Create a START packet for a new voice burst
let burstID = VoiceBurstPacket.makeBurstID()          // 8‑byte random ID
let start = VoiceBurstPacket(
    burstID: burstID,
    seq: 0,
    kind: .start(codec: .aacLC16kMono)
)!                                           // nil if validation fails
let startData = start.encode()                     // Serialized Data to send

// 2️⃣ Create a DATA packet containing one AAC frame
let aacFrame = Data([/* ... encoded AAC bytes ... */])
let dataPacket = VoiceBurstPacket(
    burstID: burstID,
    seq: 1,
    kind: .frames([aacFrame])
)!                                           // validation ensures non‑empty frame
let dataBytes = dataPacket.encode()

// 3️⃣ Create an END packet after all frames are sent
let end = VoiceBurstPacket(
    burstID: burstID,
    seq: 2,
    kind: .end(totalDataPackets: 2, durationMs: 1280)
)!                                           // total packets and duration
let endData = end.encode()

// 4️⃣ Decode a received packet
if let received = VoiceBurstPacket.decode(receivedData) {
    switch received.kind {
    case .start(let codec):
        print("Burst started, codec = \(codec)")
    case .frames(let frames):
        print("Received \(frames.count) AAC frame(s)")
    case .end(let total, let duration):
        print("Burst ended – packets: \(total), duration: \(duration) ms")
    case .canceled:
        print("Burst was canceled")
    }
}

```

## Key Implementation Files

The VoiceBurstPacket format is implemented across three primary locations in the repository:

- **[`bitchat/Protocols/VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/VoiceBurstPacket.swift)** — Defines the packet structure, encoding/decoding logic, and constants including `burstIDSize` and `maxFramesPerPacket`.
- **[`bitchatTests/VoiceBurstPacketTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/VoiceBurstPacketTests.swift)** — Unit tests verifying each packet kind (start, frames, end, canceled) plus edge‑case handling for malformed inputs.
- **[`bitchat/Services/MeshTransportCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MeshTransportCapabilities.swift)** — Documents the transport capability that relies on VoiceBurstPacket for live voice transmission capabilities.

## Summary

- **VoiceBurstPacket** uses an 8‑byte burst ID to correlate packets in a single voice session.
- The binary header includes a 2‑byte big‑endian sequence number and 1‑byte flags field.
- Four packet types exist: **START** (codec negotiation), **DATA** (AAC frames), **END** (metadata), and **CANCELED** (abort signal).
- DATA packets support up to 8 AAC frames using length‑prefixed encoding with UInt16 big‑endian headers.
- The implementation in [`VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceBurstPacket.swift) provides Swift‑native encoding and decoding with validation.

## Frequently Asked Questions

### What audio codec does VoiceBurstPacket use?

BitChat currently uses **AAC‑LC 16kHz mono** (value `0x01`) as defined in the START packet payload. The format reserves the UInt8 codec field for future expansion, though the reference implementation only supports this specific configuration.

### How many audio frames can one DATA packet contain?

Each DATA packet can carry up to **8 frames**, enforced by the `maxFramesPerPacket = 8` constant in [`VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceBurstPacket.swift). This limit balances network efficiency against packet size constraints in mesh networking environments.

### What happens when a voice burst is canceled mid‑transmission?

When a sender transmits a CANCELED packet (flags `0x04`), receivers immediately discard any buffered DATA packets for that burst ID. The CANCELED packet carries no payload, making it a lightweight one‑byte signaling mechanism when the user releases the push‑to‑talk button early.

### How is packet sequencing handled in VoiceBurstPacket?

The **seq** field uses a 2‑byte big‑endian unsigned integer where sequence `0` is reserved for START packets, data packets increment from `1`, and END or CANCELED packets use the next available sequence number. This ordering allows receivers to detect gaps and reconstruct the audio stream chronologically.