pion/webrtc · error

%w: 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.

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)

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.