Repository navigation
Stereo FLAC and Opus issues #4635
Copy link
Copy link
Open
Labels
questBeing tracked/planned in a quest. See `quest/`Being tracked/planned in a quest. See `quest/`
Description
After updating to moq-ffi 0.4.9 I receive the following error whenever writing a FLAC frame:
decode failed: frame timestamp is below the live edge
This is the code that calculates timestamps. I've thrown in some logging to verify the timestamps are correct:
currentTimestamp := initialTimestamp + (frameCount*frameLength*1_000_000+48000/2)/48000
log.Println("initialTimestamp:", initialTimestamp, "frameCount:", frameCount, "frameLength:", frameLength, "CURRENT TIMESTAMP:", currentTimestamp, "PREVIOUS TIMESTAMP:", previousTimestamp, "DELTA:", currentTimestamp-previousTimestamp)
previousTimestamp = currentTimestamp
err = out.WriteFrame(encoder.TakeBytes(), currentTimestamp)
if err != nil {
log.Println("Failed to output encoded Flac frame:", err)
}
frameCount += 1Logs look like this:
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 175 frameLength: 4096 CURRENT TIMESTAMP: 1790802494471765 PREVIOUS TIMESTAMP: 1790802494386432 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 176 frameLength: 4096 CURRENT TIMESTAMP: 1790802494557099 PREVIOUS TIMESTAMP: 1790802494471765 DELTA: 85334
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 177 frameLength: 4096 CURRENT TIMESTAMP: 1790802494642432 PREVIOUS TIMESTAMP: 1790802494557099 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 178 frameLength: 4096 CURRENT TIMESTAMP: 1790802494727765 PREVIOUS TIMESTAMP: 1790802494642432 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 179 frameLength: 4096 CURRENT TIMESTAMP: 1790802494813099 PREVIOUS TIMESTAMP: 1790802494727765 DELTA: 85334
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 180 frameLength: 4096 CURRENT TIMESTAMP: 1790802494898432 PREVIOUS TIMESTAMP: 1790802494813099 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 181 frameLength: 4096 CURRENT TIMESTAMP: 1790802494983765 PREVIOUS TIMESTAMP: 1790802494898432 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 182 frameLength: 4096 CURRENT TIMESTAMP: 1790802495069099 PREVIOUS TIMESTAMP: 1790802494983765 DELTA: 85334
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 183 frameLength: 4096 CURRENT TIMESTAMP: 1790802495154432 PREVIOUS TIMESTAMP: 1790802495069099 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 184 frameLength: 4096 CURRENT TIMESTAMP: 1790802495239765 PREVIOUS TIMESTAMP: 1790802495154432 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 185 frameLength: 4096 CURRENT TIMESTAMP: 1790802495325099 PREVIOUS TIMESTAMP: 1790802495239765 DELTA: 85334
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 186 frameLength: 4096 CURRENT TIMESTAMP: 1790802495410432 PREVIOUS TIMESTAMP: 1790802495325099 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 187 frameLength: 4096 CURRENT TIMESTAMP: 1790802495495765 PREVIOUS TIMESTAMP: 1790802495410432 DELTA: 85333
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 188 frameLength: 4096 CURRENT TIMESTAMP: 1790802495581099 PREVIOUS TIMESTAMP: 1790802495495765 DELTA: 85334
2026/09/30 23:08:00 initialTimestamp: 1790802479538432 frameCount: 189 frameLength: 4096 CURRENT TIMESTAMP: 1790802495666432 PREVIOUS TIMESTAMP: 1790802495581099 DELTA: 85333init data:
// "fLaC" + 4-byte block header + 34-byte STREAMINFO.
[]byte{
'f', 'L', 'a', 'C',
0x80, 0x00, 0x00, 0x22, // last block (1), type 0 STREAMINFO (0000000), payload length (34 bytes = 0x00 0x00 0x22)
0x10, 0x00, // Minimum block size (4096)
0x10, 0x00, // Maximum block size (4096)
0x00, 0x00, 0x00, // Minimum frame size (0 = unknown)
0x00, 0x00, 0x00, // Maximum frame size (0 = unknown)
// Sample rate (48000 Hz = 0B B8 0), channels (2 - 1 = 001), bits/sample (16 - 1 = 01111), total samples (000000)
// Packed into 8 bytes:
// 20 bits sample rate
// 3 bits (channels-1)
// 5 bits (bits/sample-1)
// 36 bits total samples - all zero = unknown
0x0B, 0xB8, (channelCount - 1) << 1, 0xF0,
0x00, 0x00, 0x00, 0x00,
// MD5 Signature - all zero = unknown
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
}After a quick check these deltas seem correct:
(4096*10^6)/48000 = 85333,3333us
This doesn't happen with Opus or AAC tracks. Here is the code and the logs for AAC:
currentTimestamp := uint64(initialTimestamp) + (frameCount*uint64(encoderInfo.FrameLength)*1_000_000+48000/2)/48000
log.Println("initialTimestamp:", initialTimestamp, "frameCount:", frameCount, "frameLength:", encoderInfo.FrameLength, "CURRENT TIMESTAMP:", currentTimestamp, "PREVIOUS TIMESTAMP:", previousTimestamp, "DELTA:", currentTimestamp-previousTimestamp)
previousTimestamp = currentTimestamp
err = out.WriteFrame(outBuf[:n], currentTimestamp)
if err != nil {
log.Println("Failed to output encoded AAC frame:", err)
}
frameCount += 12026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 234 frameLength: 1024 CURRENT TIMESTAMP: 1790803016287523 PREVIOUS TIMESTAMP: 1790803016266190 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 235 frameLength: 1024 CURRENT TIMESTAMP: 1790803016308856 PREVIOUS TIMESTAMP: 1790803016287523 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 236 frameLength: 1024 CURRENT TIMESTAMP: 1790803016330190 PREVIOUS TIMESTAMP: 1790803016308856 DELTA: 21334
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 237 frameLength: 1024 CURRENT TIMESTAMP: 1790803016351523 PREVIOUS TIMESTAMP: 1790803016330190 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 238 frameLength: 1024 CURRENT TIMESTAMP: 1790803016372856 PREVIOUS TIMESTAMP: 1790803016351523 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 239 frameLength: 1024 CURRENT TIMESTAMP: 1790803016394190 PREVIOUS TIMESTAMP: 1790803016372856 DELTA: 21334
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 240 frameLength: 1024 CURRENT TIMESTAMP: 1790803016415523 PREVIOUS TIMESTAMP: 1790803016394190 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 241 frameLength: 1024 CURRENT TIMESTAMP: 1790803016436856 PREVIOUS TIMESTAMP: 1790803016415523 DELTA: 21333
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 242 frameLength: 1024 CURRENT TIMESTAMP: 1790803016458190 PREVIOUS TIMESTAMP: 1790803016436856 DELTA: 21334
2026/09/30 23:16:48 initialTimestamp: 1790803011295523 frameCount: 243 frameLength: 1024 CURRENT TIMESTAMP: 1790803016479523 PREVIOUS TIMESTAMP: 1790803016458190 DELTA: 21333
2026/09/30 23:16:49 initialTimestamp: 1790803011295523 frameCount: 244 frameLength: 1024 CURRENT TIMESTAMP: 1790803016500856 PREVIOUS TIMESTAMP: 1790803016479523 DELTA: 21333
2026/09/30 23:16:49 initialTimestamp: 1790803011295523 frameCount: 245 frameLength: 1024 CURRENT TIMESTAMP: 1790803016522190 PREVIOUS TIMESTAMP: 1790803016500856 DELTA: 21334
2026/09/30 23:16:49 initialTimestamp: 1790803011295523 frameCount: 246 frameLength: 1024 CURRENT TIMESTAMP: 1790803016543523 PREVIOUS TIMESTAMP: 1790803016522190 DELTA: 21333Just a note that out.WriteFrame is a wrapper function implemented like this:
type frameWriter struct {
*moq.MoqMediaProducer
}
func (mp frameWriter) WriteFrame(payload []byte, timestampUs uint64) error {
if err := mp.MoqMediaProducer.WriteFrame(moq.MoqFrame{
Payload: payload,
TimestampUs: timestampUs,
}); err != nil {
return err
}
if err := mp.Cut(); err != nil {
return err
}
return nil
}So MediaProducer.Cut does get called and each frame is sent in a separate group.
Metadata
Metadata
Assignees
Labels
questBeing tracked/planned in a quest. See `quest/`Being tracked/planned in a quest. See `quest/`
Activity
quest(m1): name the frame and edge in a timestamp rewind (#4635)
kixelated commentedon Sep 30, 2026
Thanks for the detailed report. We couldn't reproduce this. FLAC and AAC take the same path in moq-ffi and moq-mux: the FLAC payload is never parsed, and both use a microsecond timescale. Your init bytes, your base timestamp, 85333/85334 µs steps, and a
Cut()after every frame run for 2000 frames without error, through bothmoq-muxandmoq-ffi.The error fires only when a frame's timestamp is below the previous one's, even by 1 µs. Because
Cut()runs after every frame, every frame starts a new group, and a group can't start before the previous group's content. Without the per-frame cut, the same dip would pass silently inside an open group. That may be why AAC and Opus look fine.Could you log the timestamp of the write that fails, along with the one before it? The logs above show only successful writes. Our guess (unverified) is that the FLAC encoder sometimes hands back a frame twice or out of order, or
frameCountchanges between computing the timestamp and writing it.We're also making the error name both values, so the next occurrence explains itself. That's tracked in a quest planned in #4637.
(Written by Claude Opus 5.5)
quest(m1): plan #4595, #4635, T-STD TS export, and Discord requests (#…
pisoj commentedon Oct 1, 2026
You were right, this is my fault, I'm restarting the encoder and tracking the
initialTimestampseparately and there is a race condition. An already processed buffer can be re-processed if the encoder gets restarted too quickly.Now that I fixed this, I realized it wasn't what's causing problems. For some reason, stereo FLAC does not play in any browser with
moq-watch 0.6.1, switching to mono fixed the issue, and yes, my catalog andinit dataare correct:{ "codec": "flac", "container": { "kind": "legacy" }, "description": "664c614380000022100010000000000000000bb802f00000000000000000000000000000000000000000", "sampleRate": 48000, "numberOfChannels": 2 }And what is even more interesting is that stereo Opus in Firefox seems to play at half the speed, while mono works fine. This is probably due to stereo being treated as mono somewhere becuase, after some time, groups start being skipped:
Again, the catalog is correct:
{ "codec": "opus", "container": { "kind": "legacy" }, "description": "4f707573486561640102000080bb0000000000", "sampleRate": 48000, "numberOfChannels": 2 }I'm aware this is a whole separate topic and I am willing to create a separate issue for this. Also, sorry for the false alarm with timestamping, I thought it was what's causing FLAC not to play anymore, but now that I discovered mono works, I will look further into it. What's problematic, is that nothing in the browser console shows any indications of an error, the closes thing to an error is this:
And to further clarify, the Opus issue is a Firefox specific one, it works fine in Chrome (Safari not tested), while stereo FLAC gives me problems in all browsers.
I hope this was useful info and it's not a bug in my code again.
kixelated commentedon Oct 1, 2026
I think this is a bug with moq.dev right now too. The latency is getting set to 0 for some reason and we skip everything.
[-]FLAC: frame timestamp is below the live edge[/-][+]Stereo FLAC and Opus issues[/+]kixelated commentedon Oct 1, 2026
@pisoj cdn.moq.pro had an overloaded node that was causing an issue. Are you running into these issues locally or was it targeting the CDN?
pisoj commentedon Oct 1, 2026