pion/webrtc · error

duplicate Ogg track SSRC

Error message

duplicate Ogg track SSRC

What it means

errDuplicateTrackSSRC is returned by Writer.NewTrack when a track with the same RTP SSRC already exists in the Ogg writer. Each Ogg logical stream (track) must have a unique SSRC to keep page streams distinguishable on demux.

Source

Thrown at pkg/media/oggwriter/oggwriter.go:51

	channelMappingFamily0              = 0
	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

View on GitHub (pinned to 8c25dc09fa)

Solutions

  1. Use a unique SSRC per track; generate one (e.g. random uint32) per NewTrack call
  2. Look up the existing track by SSRC and reuse it instead of creating a new one
  3. If the remote SSRC genuinely changed, remove/close the old track first

Example fix

// before
writer.NewTrack(1111)
writer.NewTrack(1111) // errDuplicateTrackSSRC
// after
if _, exists := tracks[1111]; !exists {
    writer.NewTrack(1111)
}
Defensive patterns

Strategy: validation

Validate before calling

if ssrc == existingSSRC {
    return fmt.Errorf("SSRC %d already registered as a track", ssrc)
}
track, err := writer.NewTrack(ssrc, opts...)

Type guard

func ssrcInUse(tracks map[uint32]struct{}, ssrc uint32) bool {
    _, ok := tracks[ssrc]
    return ok
}

Try / catch

track, err := writer.NewTrack(ssrc, opts...)
if errors.Is(err, oggwriter.ErrDuplicateTrackSSRC) {
    // reuse the existing track for this SSRC instead of failing
}

Prevention

When it happens

Trigger: Calling writer.NewTrack(1111, ...) twice with the same SSRC (oggwriter.go:438 lookup in w.tracks); asserted in oggwriter_test.go:1151 with WithSerial(0x05060708).

Common situations: Re-adding the same remote track after a renegotiation, copying track SSRCs from two peers that collided, hardcoding the same SSRC for multiple audio streams.

Related errors


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