---
title: "Fix Android SRT Stream Drops: ADTS, Buffer & Latency"
slug: "fix-android-srt-stream-drops-adts-buffer-latency"
published: "2026-05-21"
updated: "2026-05-28"
categories:
  - "Tools"
tags:
  - "Android SRT stream drops"
  - "ADTS invalid syncword"
  - "BUFFER OVERFLOW"
  - "SRT latency tuning"
  - "adaptive bitrate ABR"
  - "RootEncoder"
  - "MediaMTX"
  - "hasCongestion setVideoBitrateOnFly"
  - "audio sample rate 48000Hz"
  - "MPEG-TS PMT audio mismatch"
  - "ffplay rtsp tcp"
  - "multicam live streaming"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "rootencoder"
  - "mediamtx"
  - "kotlin"
  - "android"
  - "obs"
status: "stable"
llm-purpose: "Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…"
llm-prereqs:
  - "Access to RootEncoder"
  - "Access to MediaMTX"
  - "Access to Kotlin"
  - "Access to Android"
  - "Access to OBS"
llm-outputs:
  - "Completed outcome: Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…"
---

**Summary Triples**
- (`unable to decode ADTS: invalid syncword`, root_cause, Audio sample-rate or ADTS parameter ordering mismatch with the hardware encoder)
- (Fix ADTS invalid syncword, action, Force encoder audio sample rate to 48000Hz and correct named-parameter ordering for ADTS headers (ensure PMT audio matches 48kHz in MPEG-TS))
- (`Error send packet, BUFFER OVERFLOW`, root_cause, Configured target bitrate exceeds available Wi‑Fi throughput causing send buffer saturation)
- (Fix BUFFER OVERFLOW, action, Implement ABR: detect congestion (hasCongestion()) and call setVideoBitrateOnFly() to reduce bitrate dynamically)
- (Choppy stream / high frame drops, root_cause, SRT latency too small relative to network RTT and jitter)
- (Fix SRT latency, action, Set SRT latency to >= 3–4× observed RTT (increase further for jitter/packet loss); test and iterate)
- (RootEncoder + MediaMTX ABR, integration, Use RootEncoder's congestion detection hooks to call MediaMTX's setVideoBitrateOnFly() so SRT streams adapt and recover automatically)
- (Debugging tools, recommendation, Use MediaMTX logs plus ffplay -rtsp_transport tcp and ffmpeg probes to correlate error strings to root causes)

### {GOAL}
Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…

### {PREREQS}
- Access to RootEncoder
- Access to MediaMTX
- Access to Kotlin
- Access to Android
- Access to OBS

### {STEPS}
1. Use the quick error reference
2. Fix ADTS syncword issues
3. Set appropriate SRT latency
4. Prevent buffer overflow with ABR
5. Implement ABR monitor coroutine
6. Resolve MPEG‑TS stream errors
7. Align versions and test players

<!-- llm:goal="Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…" -->
<!-- llm:prereq="Access to RootEncoder" -->
<!-- llm:prereq="Access to MediaMTX" -->
<!-- llm:prereq="Access to Kotlin" -->
<!-- llm:prereq="Access to Android" -->
<!-- llm:prereq="Access to OBS" -->
<!-- llm:output="Completed outcome: Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…" -->

# Fix Android SRT Stream Drops: ADTS, Buffer & Latency
> Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…
Matija Žiberna · 2026-05-21

If MediaMTX is dropping your Android SRT stream with errors like `unable to decode ADTS: invalid syncword` or `Error send packet, BUFFER OVERFLOW`, the cause is almost always one of three things: wrong audio sample rate for your hardware encoder, SRT latency configured too low for your actual network RTT, or a target bitrate that exceeds what your Wi-Fi link can physically push. Each of these produces a distinct error string in the MediaMTX logs, and each one has a specific fix. This article maps every error to its root cause and walks through the solution — including how to implement adaptive bitrate so the stream recovers automatically when the network gets congested.

I hit all of these while building a multicam live streaming setup with Android phones, RootEncoder, and MediaMTX. The debug log grew to eleven tested configurations before everything stabilized. If you are staring at a disconnect loop right now, this article should get you to a working stream faster than I did.

---

## The Quick Reference Map

Before diving into each failure in detail, here is the full error-to-cause-to-fix table. If you already know your error string, jump straight to the relevant section.

| Error / Symptom | Root cause | Fix |
|---|---|---|
| `unable to decode ADTS: invalid syncword` | Audio sample rate mismatch with hardware encoder | Set sample rate to `48000Hz`, confirm bitrate parameter order |
| `Error send packet, BUFFER OVERFLOW` | Target bitrate exceeds actual Wi-Fi link capacity | Implement ABR with `hasCongestion()` + `setVideoBitrateOnFly()` |
| Choppy stream, high frame drops at high bitrate | SRT latency buffer too narrow for network RTT | Set SRT latency to at least 3–4× RTT (e.g. `2_000_000µs` for Wi-Fi) |
| `closed: received unexpected interleaved frame` | VLC RTSP TCP parser bug | Switch to OBS or `ffplay -rtsp_transport tcp` for testing |
| `decode error: initial delimiter not found` | Audio track declared but not transmitted in MPEG-TS | Keep both audio and video streams active |
| App crash `IllegalStateException` | `prepareAudio()` never called | Always call `prepareAudio()` even when streaming video only |
| No audio in Chrome WebRTC | Chrome does not support AAC over WebRTC | Test audio via HLS or RTSP — not WebRTC |

---

## The ADTS Syncword Error — Audio Sample Rate Mismatch

### `closed: unable to decode ADTS: invalid syncword`

This is the most common disconnect on Samsung hardware. MediaMTX logs this error and immediately drops the client. It looks like a server-side problem but the fault is entirely on the Android encoder side.

AAC audio uses a 12-bit synchronization header (`0xFFF`) at the start of every frame. When the encoder outputs misaligned audio buffers — because the configured sample rate does not match what the hardware capture pipeline actually runs at — those sync bytes end up in the wrong position. MediaMTX reads the frame, cannot locate the expected header, and terminates the connection.

The Samsung S22 (and most modern Android hardware) runs its native audio capture pipeline at `48000Hz`. When you configure `44100Hz`, the microphone capture queue runs at a different clock than the encoder expects, producing buffer drift. The fix is straightforward: always configure audio at `48000Hz` on Samsung devices.

```kotlin
// File: MainActivity.kt
srtStream.prepareAudio(
    sampleRate = 48000,
    isStereo = false,
    bitrate = 128_000
)
```

Mono (`isStereo = false`) reduces the data volume per frame and produces cleaner alignment on the capture queue. Start with mono at 128 kbps — you can move to stereo once the stream is stable.

---

## The Silent Kotlin Bug — Swapped Parameters in RootEncoder

This failure deserves its own section because it produces the exact same `invalid syncword` error as the sample rate mismatch, so it is easy to miss. Kotlin compiles the code successfully. There is no warning. The stream just immediately disconnects.

The `prepareAudio` signature in RootEncoder is:

```kotlin
prepareAudio(sampleRate: Int, isStereo: Boolean, bitrate: Int)
```

And `prepareVideo`:

```kotlin
prepareVideo(width: Int, height: Int, fps: Int, bitrate: Int)
```

Both audio parameters after `isStereo` are `Int`. If you swap `sampleRate` and `bitrate`, the compiler accepts it. The hardware encoder receives a sample rate of `128000Hz` (invalid) and a bitrate of `48000bps` (effectively nothing). The resulting frames are malformed from the first packet.

```kotlin
// File: MainActivity.kt — WRONG (swapped parameters)
srtStream.prepareAudio(128_000, false, 48000)

// File: MainActivity.kt — CORRECT
srtStream.prepareAudio(
    sampleRate = 48000,
    isStereo = false,
    bitrate = 128_000
)
```

Always use named parameters when calling RootEncoder methods. It costs nothing and eliminates this entire class of bug.

The same problem applies to `prepareVideo`. The `fps` and `bitrate` arguments are both `Int` and easy to transpose:

```kotlin
// File: MainActivity.kt — WRONG
srtStream.prepareVideo(1280, 720, 2_500_000, 30)

// File: MainActivity.kt — CORRECT
srtStream.prepareVideo(
    width = 1280,
    height = 720,
    fps = 30,
    bitrate = 2_500_000
)
```

---

## SRT Latency and ARQ — Why 120ms Is Almost Always Wrong

### Choppy stream, massive frame drops at high bitrate

SRT uses ARQ (Automatic Repeat reQuest) to recover lost packets. When a packet is dropped on the network, the receiver requests a retransmission. The SRT latency buffer defines how long the receiver holds a slot open waiting for that retransmission before it gives up and drops the frame.

The default SRT latency in RootEncoder is `120_000` microseconds — 120ms. On a typical Wi-Fi network with an RTT of around 55ms, a 120ms buffer gives the ARQ mechanism less than two full round trips to recover a lost packet. At high bitrates, where packet loss is more frequent, the buffer empties faster than retransmissions can arrive. The player discards frames and the stream stutters visibly.

The rule of thumb: set SRT latency to at least 3–4× your measured RTT. On Wi-Fi, `2_000_000µs` (2 seconds) is a safe default.

```kotlin
// File: MainActivity.kt
val srtUrl = "srt://192.168.1.100:8890?streamid=publish:cam1&latency=2000000"
srtStream.connect(srtUrl)
```

A 2-second latency buffer does not mean 2 seconds of end-to-end delay in all cases — it means the receiver will wait up to 2 seconds for a retransmission before discarding. On a clean local network, actual latency stays well below that ceiling.

---

## Buffer Overflow — Bitrate vs Link Capacity

### `Error send packet, BUFFER OVERFLOW`

Increasing the SRT latency buffer solves the retransmission problem but surfaces a different one. If the encoder is producing 20 Mbps and the Wi-Fi link can only push 5–8 Mbps, the internal send queue in `SrtSender` fills continuously. Once it hits capacity, RootEncoder throws the overflow exception and aborts the stream.

The latency buffer and the bitrate ceiling are two separate knobs. Widening the latency buffer gives ARQ more time to work. It does nothing about the upstream bandwidth ceiling. Those need to be managed independently.

The correct solution is client-side adaptive bitrate — monitoring queue congestion and scaling the encoder down when the link is saturated.

---

## Implementing ABR with hasCongestion() and setVideoBitrateOnFly()

RootEncoder 2.7.2 exposes two methods that make client-side flow control straightforward:

- `hasCongestion()` returns true when the send queue is backing up
- `setVideoBitrateOnFly(bitrate)` adjusts the encoder output without restarting the stream

The pattern is a simple polling loop on a background coroutine that steps the bitrate down when congestion is detected, and gradually recovers when the link clears.

```kotlin
// File: MainActivity.kt
private val bitrateSteps = listOf(20_000_000, 15_000_000, 12_000_000, 8_000_000, 5_000_000)
private var currentBitrateIndex = 0

private fun startAbrMonitor() {
    CoroutineScope(Dispatchers.IO).launch {
        while (srtStream.isStreaming) {
            delay(3000)
            if (srtStream.getStreamClient().hasCongestion()) {
                if (currentBitrateIndex < bitrateSteps.lastIndex) {
                    currentBitrateIndex++
                    val newBitrate = bitrateSteps[currentBitrateIndex]
                    srtStream.setVideoBitrateOnFly(newBitrate)
                    Log.d("ABR", "Congestion detected — stepping down to $newBitrate bps")
                }
            } else {
                if (currentBitrateIndex > 0) {
                    currentBitrateIndex--
                    val newBitrate = bitrateSteps[currentBitrateIndex]
                    srtStream.setVideoBitrateOnFly(newBitrate)
                    Log.d("ABR", "Link clear — stepping up to $newBitrate bps")
                }
            }
        }
    }
}
```

Call `startAbrMonitor()` immediately after the stream connects. The 3-second polling interval gives the encoder time to stabilize between steps — stepping too aggressively in both directions causes visible quality oscillation.

With ABR running, the stream starts at 20 Mbps and steps down automatically if the access point gets saturated. When the link recovers, it climbs back up. The buffer overflow exception stops occurring entirely because the encoder output stays within what the link can sustain.

---

## The MPEG-TS Stream Structure Errors

Two errors relate to the MPEG-TS container structure rather than individual encoder parameters.

### `decode error: initial delimiter not found`

This appears when audio is prepared but not transmitted. In MPEG-TS, the Program Map Table declares which tracks are present. If the PMT declares an audio track that never sends packets, the demuxer receives mismatched timecodes and empty sync buffers. MediaMTX cannot locate frame boundaries and logs this error.

Keep both video and audio streams active. If you genuinely need video-only output, strip the audio track at the MediaMTX or OBS layer rather than suppressing it on the Android side.

### `java.lang.IllegalStateException` on stream start

If you remove the `prepareAudio()` call entirely, RootEncoder's `StreamBase` crashes on stream start. The library calls `startSources()` on all encoders during initialization. `AudioEncoder` must be initialized — even for video-only configurations. Always call `prepareAudio()`.

---

## Version Alignment — RootEncoder 2.7.2 and Kotlin 2.2.0

If you are upgrading RootEncoder to `2.7.2` for the ABR methods, the Kotlin compiler needs to match. Mismatched metadata versions produce build errors that look unrelated to RootEncoder. Update both together:

```kotlin
// File: build.gradle.kts (project level)
plugins {
    kotlin("android") version "2.2.0"
}

// File: build.gradle.kts (app level)
dependencies {
    implementation("com.github.pedroSG94.RootEncoder:library:2.7.2")
}
```

---

## FAQ

**Why does the stream work in OBS but disconnect in VLC?**

MediaMTX is configured for RTSP over TCP only. VLC has a known bug with interleaved RTSP/TCP frame parsing — under network latency or buffer gaps, it misreads the stream sync and drops the connection, then prompts for credentials as if it were an auth failure. Use OBS Studio or `ffplay -rtsp_transport tcp` for testing RTSP streams.

**Why is there no audio when playing the stream in Chrome via WebRTC?**

Chrome does not support AAC audio over WebRTC. The WebRTC standard requires Opus or G.711. When MediaMTX forwards an AAC stream over WebRTC, Chrome silently discards the audio track. Audio plays correctly over HLS (`http://<host>:8888/<path>/index.m3u8`) and RTSP in compatible players.

**Can I append `?pkt_size=1316` to the SRT URL in RootEncoder?**

No. RootEncoder's URL parser tokenizes credentials from the path using colons as delimiters. It does not strip query parameters before parsing, so `?pkt_size=1316` gets appended to the password string. The server rejects the malformed credential. Configure packet size at the MediaMTX server level instead.

**What sample rate should I use on non-Samsung Android devices?**

`48000Hz` is the standard hardware capture rate on virtually all modern Android devices. `44100Hz` is a software resampling target that introduces buffer drift on most hardware encoders. Default to `48000Hz` unless you have a specific reason to do otherwise.

**At what SRT latency should I start for a local Wi-Fi setup?**

Measure your RTT first with a ping test between the Android device and the MediaMTX server. Multiply by 4 and convert to microseconds. For typical home or venue Wi-Fi with 40–60ms RTT, `2_000_000µs` (2 seconds) is a safe starting point.

---

## Conclusion

Most Android SRT stream drops trace back to one of three misconfigurations: audio sample rate set to `44100Hz` instead of the hardware-native `48000Hz`, SRT latency too narrow to give ARQ enough room to retransmit on a Wi-Fi link, or a fixed high bitrate that exceeds actual upstream capacity. The `unable to decode ADTS: invalid syncword` error points to the first two. The `BUFFER OVERFLOW` error points to the third.

The stable configuration for a production setup on a Samsung S22 or similar device: `48000Hz` mono audio, named parameters in all `prepareAudio` and `prepareVideo` calls, SRT latency at `2_000_000µs`, and client-side ABR using `hasCongestion()` with `setVideoBitrateOnFly()` to handle network variation automatically.

If you are building toward a full multicam production setup on top of this foundation, the <a href="https://www.buildwithmatija.com/blog/multicamera-live-stream-android-phones">Multicam Live Broadcast Setup with Android Phones, OBS, and MediaMTX</a> guide covers the full architecture — stream routing, OBS scene switching, and CDN output.

Let me know in the comments if you hit an error string that is not covered here, and subscribe for more practical development guides.

Thanks, Matija

## LLM Response Snippet
```json
{
  "goal": "Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…",
  "responses": [
    {
      "question": "What does the article \"Fix Android SRT Stream Drops: ADTS, Buffer & Latency\" cover?",
      "answer": "Android SRT stream drops: identify ADTS syncword, BUFFER OVERFLOW and latency causes; apply RootEncoder fixes, SRT tuning, and ABR to stabilize your…"
    }
  ]
}
```