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       OpusTags

View on GitHub (pinned to 8c25dc09fa)

Solutions

  1. Use a distinct WithSerial value for each track on the same writer
  2. Omit WithSerial and let the writer allocate a serial via allocateSerial()
  3. 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

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


AI-assisted analysis of pion/webrtc@8c25dc09fa (2026-09-03). Data as JSON: /api/errors/24bbd835f933fa6b. Report an issue: GitHub.