pion/webrtc · error
duplicate Ogg track serial
Error message
duplicate Ogg track serial
What it means
errDuplicateTrackSerial is returned by NewTrack when the caller explicitly requests a granulepos/serial number (via WithSerial) that is already allocated to another track in the same Ogg writer. Ogg logical streams are identified by a unique 32-bit serial number, so two tracks sharing one serial would corrupt the container. The writer pre-registers its own serial allocation, so collisions can occur even with explicit serials.
Source
Thrown at pkg/media/oggwriter/oggwriter.go:52
channelMappingFamily1 = 1
channelMappingFamily2 = 2
channelMappingFamily255 = 255
idPageSignature = "OpusHead"
commentPageSignature = "OpusTags"
defaultVendor = "pion"
pageHeaderSignature = "OggS"
pageHeaderSize = 27
maxOggPageSegments = 255
noGranulePosition = ^uint64(0)
maxUint32Length = uint64(1<<32 - 1)
)
var (
errFileNotOpened = errors.New("file not opened")
errOutputNotOpened = errors.New("output not opened")
errInvalidNilPacket = errors.New("invalid nil packet")
errDuplicateTrackSSRC = errors.New("duplicate Ogg track SSRC")
errDuplicateTrackSerial = errors.New("duplicate Ogg track serial")
errTracksStarted = errors.New("cannot add Ogg tracks after writing has started")
errPacketSSRCMismatch = errors.New("RTP packet SSRC does not match Ogg track SSRC")
errInvalidOpusPacket = errors.New("invalid Opus packet")
errInvalidChannelCount = errors.New("invalid channel count")
errInvalidChannelMap = errors.New("invalid channel mapping")
errInvalidOpusTags = errors.New("invalid OpusTags")
)
type pageRewriter interface {
io.Seeker
io.WriterAt
}
type writerConfig struct {
sampleRate uint32
channelMapping channelMapping
pageRewriter pageRewriter
opusTags OpusTagsView on GitHub (pinned to 8c25dc09fa)
Solutions
- Use a distinct WithSerial value for each track on the same writer
- Omit WithSerial and let the writer allocate a serial via allocateSerial()
- Ensure serials are unique across restarts by deriving them from a counter or random value
Example fix
// before track1, err := writer.NewTrack(1111, WithSerial(0x01020304)) track2, err := writer.NewTrack(2222, WithSerial(0x01020304)) // duplicate // after track1, err := writer.NewTrack(1111, WithSerial(0x01020304)) track2, err := writer.NewTrack(2222, WithSerial(0x05060708))
Defensive patterns
Strategy: validation
Validate before calling
func serialFree(w *oggwriter.Writer, serial uint32) bool {
return serial != 0 && !usedSerials[serial] // track serials you've handed out
} Try / catch
track, err := writer.NewTrack(ssrc, oggwriter.WithSerial(mySerial))
if errors.Is(err, oggwriter.errDuplicateTrackSerial) {
track, err = writer.NewTrack(ssrc, nil) // fall back to auto serial
} Prevention
- Omit WithSerial and let the writer auto-allocate
- Keep a registry of used serials per writer
- Never hard-code serial constants in shared fixtures
- Derive serials from a monotonically increasing counter
When it happens
Trigger: Calling writer.NewTrack(ssrc, WithSerial(n)) where serialInUse(n) is true — i.e. a previous track (or an internally allocated track) already holds serial n.
Common situations: Hard-coding serial constants in multiple NewTrack calls; reusing a serial after restarting track setup on a long-lived writer; copy-pasted test fixtures using the same WithSerial value for two tracks.
Related errors
- RTP packet SSRC does not match Ogg track SSRC
- stream is nil
- bad header signature
- bad opus tags signature
- invalid nil packet
AI-assisted analysis of pion/webrtc@8c25dc09fa (2026-09-03).
Data as JSON: /api/errors/24bbd835f933fa6b.
Report an issue: GitHub.