Fix ADTS syncword, BUFFER OVERFLOW, and latency drops; use ABR with RootEncoder and MediaMTX to stabilize SRT
·Updated on:··
📚 Get Practical Development Guides
Join developers getting comprehensive guides, code examples, optimization tips, and time-saving prompts to accelerate their development workflow.
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.
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.
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.
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.
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.
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.
// File: MainActivity.ktprivateval bitrateSteps = listOf(20_000_000, 15_000_000, 12_000_000, 8_000_000, 5_000_000)
privatevar currentBitrateIndex = 0privatefunstartAbrMonitor() {
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:
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.
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
I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.