VoiceBurstPacket Format in BitChat: Binary Protocol for Live Voice Notes

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, 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 file provides Swift structs and methods to construct and serialize packets. Below are practical examples demonstrating the construction of each packet type:

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:

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →