pion/webrtc · error

: ambisonics family type 3 is not supported

Error message

%w: ambisonics family type 3 is not supported

What it means

ParseOpusHead refuses to parse an Opus identification header whose ChannelMappingFamily byte is 3 (ambisonics projection). The library supports family 0 (RTP/vorbis order), 1/255 (discrete), and 2 (extended mapping); family 3 requires ambisonics channel ordering the reader does not implement. The returned error wraps errUnsupportedChannelMappingFamily so callers can detect it with errors.Is.

Solutions

  1. Remap or transcode the stream to channel mapping family 1 or 2 before feeding it to ParseOpusHead (e.g. with ffmpeg -c:a libopus -mapping_family 1 or 0).
  2. If ambisonics metadata is not needed, patch parseChannelMapping to treat family 3 as discrete channels (route it to parseExtendedChannelMapping) and document the limitation.
  3. Handle the error at the call site: check errors.Is(err, oggreader.ErrUnsupportedChannelMappingFamily) and skip or reject the stream gracefully.

Example fix

// before
# ffmpeg -i in.opus -c copy out.opus
// after
# force a supported channel mapping family
# ffmpeg -i in.opus -c:a libopus -mapping_family 1 out.opus
Defensive patterns

Strategy: validation

Validate before calling

func isAmbisonicsOpus(head []byte) bool {
    return len(head) > 18 && head[0]=='O' && head[1]=='p' && head[2]=='u' && head[3]=='s' &&
        head[4]=='H' && head[5]=='e' && head[6]=='a' && head[7]=='d' && head[18] == 3
}
// skip or transcode when isAmbisonicsOpus(packet) is true

Type guard

func isSupportedOpusHead(p []byte) bool {
    if len(p) < 19 || string(p[:8]) != "OpusHead" {
        return false
    }
    switch p[18] {
    case 0, 1, 2, 255:
        return true
    default:
        return false
    }
}

Prevention

When it happens

Trigger: Calling oggreader.ParseOpusHead on an OpusHead packet whose byte at offset 18 (ChannelMappingFamily) equals 3; readOpusHeader reaches parseChannelMapping and hits the case 3 branch.

Common situations: Decoding ambisonic/VR surround streams (e.g. YouTube 360 audio, B-format first-order ambisonics Opus streams); working with recently standardized ambisonics Opus content that older tooling, including this reader, does not yet support.

Related errors


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

Appendix: source

Thrown at pkg/media/oggreader/oggreader.go:245

	header.PreSkip = binary.LittleEndian.Uint16(payload[10:12])
	header.SampleRate = binary.LittleEndian.Uint32(payload[12:16])
	header.OutputGain = binary.LittleEndian.Uint16(payload[16:18])
	header.ChannelMap = payload[18]

	return header
}

// parseChannelMapping parses channel mapping data based on the channel map family.
// https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1
// family mapping of 2 and 3 are defined in https://datatracker.ietf.org/doc/html/rfc8486
func parseChannelMapping(header *OggHeader, payload []byte) error {
	switch header.ChannelMap {
	case 0:
		return validatePayloadLength(payload, idPageBasePayloadLength)
	case 1, 2, 255:
		return parseExtendedChannelMapping(header, payload)
	case 3:
		return fmt.Errorf("%w: ambisonics family type 3 is not supported", errUnsupportedChannelMappingFamily)
	default:
		return errUnsupportedChannelMappingFamily
	}
}

func validatePayloadLength(payload []byte, expectedLen int) error {
	if len(payload) != expectedLen {
		return errBadIDPageLength
	}

	return nil
}

func parseExtendedChannelMapping(header *OggHeader, payload []byte) error {
	expectedPayloadLen := 21 + int(header.Channels)
	if err := validatePayloadLength(payload, expectedPayloadLen); err != nil {
		return err
	}

View on GitHub (pinned to 8c25dc09fa)