# Can BitChat Transmit Voice Bursts Over BLE Mesh? Technical Implementation Guide

> Learn how BitChat transmits voice bursts over BLE mesh by encapsulating AAC-LC audio frames into VoiceBurstPacket structures routed as opaque encrypted payloads. Technical implementation guide.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-02

---

**Yes, BitChat can transmit voice bursts over BLE mesh by encapsulating AAC-LC audio frames into `VoiceBurstPacket` structures that respect BLE frame budgets, then routing them through the mesh stack as opaque encrypted binary payloads.**

The permissionlesstech/bitchat repository implements an offline-first messaging protocol that supports push-to-talk (PTT) voice communication. According to the source code in [`bitchat/Protocols/VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/VoiceBurstPacket.swift) and the architecture documentation in [`docs/BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/BLE-ARCHITECTURE-V3.md), BitChat transmits voice bursts over BLE mesh without requiring internet connectivity, using packetization strategies that ensure real-time audio fits within the constraints of BLE link layers.

## How Voice Bursts Travel Over BLE Mesh

### The VoiceBurstPacket Wire Format

BitChat defines a concrete packet structure in [`bitchat/Protocols/VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/VoiceBurstPacket.swift) that represents a single transmissible unit of voice data. Each **VoiceBurstPacket** contains a unique burst identifier, a sequence number for ordering, flags indicating the packet type (start, data, end, or cancel), and the actual audio payload. The structure is designed to remain small enough that after **Noise encryption** and protocol padding, the final binary representation fits comfortably within a single BLE LE frame.

The packet supports multiple codec signatures, with `.aacLC16kMono` being the standard for PTT sessions. This allows the receiver to initialize the correct decoder upon receiving the `START` marker without external negotiation.

### Packetization and BLE Frame Budget

Before transmission, raw microphone captures flow through [`VoiceRecorder.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceRecorder.swift) and [`VoiceCaptureSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceCaptureSession.swift), which encode audio into AAC-LC frames. The **VoiceBurstPacketizer** class (defined alongside `VoiceBurstPacket`) aggregates these frames into chunks that respect `TransportConfig.pttMaxBurstContentBytes`. This configuration constant represents the maximum usable bytes per BLE mesh payload after accounting for encryption overhead and protocol headers.

The packetizer maintains internal state to track sequence numbers and burst boundaries. When the user initiates a push-to-talk session, the system generates a unique burst ID via `VoiceBurstPacket.makeBurstID()`, ensuring that packets belonging to different voice sessions can be demultiplexed even when interleaved with other mesh traffic.

## Code Implementation: Sending and Receiving Voice Bursts

### Transmitting Voice Over the Mesh

The following Swift implementation demonstrates how BitChat constructs and transmits voice bursts using the BLE transport layer:

```swift
import bitchat.Features.voice

// Generate a unique burst ID for this voice session
let burstID = VoiceBurstPacket.makeBurstID()

// Initialize the packetizer with the BLE mesh byte budget
var packetizer = VoiceBurstPacketizer(burstID: burstID)

// Send the START packet to advertise codec information
let startPacket = VoiceBurstPacket(
    burstID: burstID,
    seq: 0,
    kind: .start(codec: .aacLC16kMono)
)!.encode()
BLEService.shared.send(data: startPacket)

// Process encoded AAC frames and transmit full packets
for aacFrame in encodedFrames {
    let fullPackets = packetizer.add(aacFrame)
    for packet in fullPackets {
        BLEService.shared.send(data: packet)
    }
}

// Transmit the END marker with metadata
let endPacket = VoiceBurstPacket(
    burstID: burstID,
    seq: packetizer.nextSeq,
    kind: .end(totalDataPackets: packetizer.dataPacketCount,
               durationMs: UInt32(totalDurationMs))
)!.encode()
BLEService.shared.send(data: endPacket)

```

### Receiving and Decoding Voice Bursts

On the receiving side, incoming BLE data passes through the same `BLEService` into the voice subsystem:

```swift
func handleIncomingBLEData(_ data: Data) {
    guard let burst = VoiceBurstPacket.decode(data) else { return }
    
    switch burst.kind {
    case .start(let codec):
        // Initialize decoder based on advertised codec
        voiceDecoder = VoiceDecoder(codec: codec)
    case .frames(let frames):
        // Accumulate frames for playback
        frames.forEach { voiceDecoder?.append(frame: $0) }
    case .end(let totalPackets, let durationMs):
        // Finalize audio playback session
        voiceDecoder?.finalize(totalPackets: totalPackets, 
                               durationMs: durationMs)
    case .canceled:
        // Abort current playback and reset state
        voiceDecoder?.reset()
    }
}

```

These implementations show how `VoiceBurstPacket` acts as the bridge between audio codecs and the BLE transport, enabling true mesh-based voice communication.

## Key Components in the Source Code

The voice-over-mesh capability spans several specific files within the repository:

- **[`bitchat/Protocols/VoiceBurstPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/VoiceBurstPacket.swift)** — Defines the `VoiceBurstPacket` struct, the `VoiceBurstPacketizer` class, and encoding/decoding logic that conforms to the BLE frame budget.
- **[`bitchat/Features/voice/VoiceRecorder.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/VoiceRecorder.swift)** — Captures microphone input and encodes streams into AAC-LC frames suitable for packetization.
- **[`bitchat/Features/voice/VoiceCaptureSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Features/voice/VoiceCaptureSession.swift)** — Manages the lifecycle of PTT sessions, coordinating between UI state and the packetizer.
- **[`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift)** — The core BLE mesh service that treats voice packets as opaque `Data` objects, transmitting them via standard mesh routing.
- **[`docs/BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/BLE-ARCHITECTURE-V3.md)** — Documents the mesh stack design, concurrency model, and how binary payloads (including voice) traverse the network.
- **[`bitchatTests/VoiceBurstPacketTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/VoiceBurstPacketTests.swift)** — Validates packet encoding, budget enforcement, and codec negotiation logic.

## Summary

- BitChat transmits voice bursts over BLE mesh using specialized **VoiceBurstPacket** structures that include burst IDs, sequence numbers, and codec metadata.
- The **VoiceBurstPacketizer** batches AAC-LC frames to fit within `TransportConfig.pttMaxBurstContentBytes`, ensuring compatibility with BLE frame constraints after Noise encryption.
- **BLEService.swift** routes voice packets through the mesh network identically to text messages, enabling offline push-to-talk functionality.
- The implementation supports robust session management via `START`, `END`, and `CANCEL` packet types defined in the protocol layer.

## Frequently Asked Questions

### Does BitChat require internet connectivity to send voice bursts?

No. According to the [`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md) design documents and the [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) implementation, voice packets transmit as encrypted binary payloads directly over the BLE mesh network. The system operates entirely offline, routing voice bursts through peer-to-peer mesh hops without cellular or Wi-Fi connectivity.

### What audio codec does BitChat use for BLE mesh voice transmission?

BitChat uses **AAC-LC (Advanced Audio Coding Low Complexity)** at 16 kHz mono for voice bursts. This codec provides efficient compression suitable for low-bandwidth BLE links while maintaining intelligibility. The specific codec signature is embedded in the `START` packet's `.aacLC16kMono` variant, allowing receivers to initialize compatible decoders dynamically.

### How does BitChat handle packet loss during voice transmission?

The `VoiceBurstPacket` structure includes monotonically increasing **sequence numbers** that allow the receiver to detect missing packets. While the current implementation in [`VoiceCaptureSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/VoiceCaptureSession.swift) focuses on real-time streaming without retransmission for latency-sensitive PTT, the packet format supports NACK (negative acknowledgment) flags that future iterations could use to request specific missing frames from mesh neighbors.

### Is voice data encrypted when transmitted over the BLE mesh?

Yes. As documented in the BLE architecture, all payloads—including voice bursts—undergo **Noise protocol encryption** before transmission. The `VoiceBurstPacket` data is treated as an opaque binary blob by `BLEService.shared.send`, meaning the voice content receives the same cryptographic protections as text messages within the BitChat mesh network.