// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package vp9 contains a VP9 header parser.
package vp9 import ( ) var ( errInvalidFrameMarker = errors.New("invalid frame marker") errWrongFrameSyncByte0 = errors.New("wrong frame_sync_byte_0") errWrongFrameSyncByte1 = errors.New("wrong frame_sync_byte_1") errWrongFrameSyncByte2 = errors.New("wrong frame_sync_byte_2") ) // HeaderColorConfig is the color_config member of an header. type HeaderColorConfig struct { TenOrTwelveBit bool BitDepth uint8 ColorSpace uint8 ColorRange bool SubsamplingX bool SubsamplingY bool } func ( *HeaderColorConfig) ( uint8, []byte, *int) error { // nolint:cyclop if >= 2 { var error .TenOrTwelveBit, = readFlag(, ) if != nil { return } if .TenOrTwelveBit { .BitDepth = 12 } else { .BitDepth = 10 } } else { .BitDepth = 8 } , := readBits(, , 3) if != nil { return } .ColorSpace = uint8() // nolint: gosec // G115, no overflow we read 3 bits if .ColorSpace != 7 { // nolint: nestif var error .ColorRange, = readFlag(, ) if != nil { return } if == 1 || == 3 { := hasSpace(, *, 3) if != nil { return } .SubsamplingX = readFlagUnsafe(, ) .SubsamplingY = readFlagUnsafe(, ) *++ } else { .SubsamplingX = true .SubsamplingY = true } } else { .ColorRange = true if == 1 || == 3 { .SubsamplingX = false .SubsamplingY = false := hasSpace(, *, 1) if != nil { return } *++ } } return nil } // HeaderFrameSize is the frame_size member of an header. type HeaderFrameSize struct { FrameWidthMinus1 uint16 FrameHeightMinus1 uint16 } func ( *HeaderFrameSize) ( []byte, *int) error { := hasSpace(, *, 32) if != nil { return } .FrameWidthMinus1 = uint16(readBitsUnsafe(, , 16)) // nolint: gosec // G115 no overflow, we read 16 bits .FrameHeightMinus1 = uint16(readBitsUnsafe(, , 16)) // nolint: gosec // G115 return nil } // Header is a VP9 Frame header. // Specification: // https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf type Header struct { Profile uint8 ShowExistingFrame bool FrameToShowMapIdx uint8 NonKeyFrame bool ShowFrame bool ErrorResilientMode bool ColorConfig *HeaderColorConfig FrameSize *HeaderFrameSize } // Unmarshal decodes a Header. func ( *Header) ( []byte) error { //nolint:cyclop := 0 := hasSpace(, , 4) if != nil { return } := readBitsUnsafe(, &, 2) if != 2 { return errInvalidFrameMarker } := uint8(readBitsUnsafe(, &, 1)) // nolint: gosec // no overflow, we read 1 bit := uint8(readBitsUnsafe(, &, 1)) // nolint: gosec // G115 .Profile = <<1 + if .Profile == 3 { = hasSpace(, , 1) if != nil { return } ++ } .ShowExistingFrame, = readFlag(, &) if != nil { return } if .ShowExistingFrame { var uint64 , = readBits(, &, 3) if != nil { return } .FrameToShowMapIdx = uint8() // nolint: gosec // no overflow, we read 3 bits return nil } = hasSpace(, , 3) if != nil { return } .NonKeyFrame = readFlagUnsafe(, &) .ShowFrame = readFlagUnsafe(, &) .ErrorResilientMode = readFlagUnsafe(, &) if !.NonKeyFrame { // nolint: nestif := hasSpace(, , 24) if != nil { return } := uint8(readBitsUnsafe(, &, 8)) // nolint: gosec // no overflow, we read 8 bits if != 0x49 { return errWrongFrameSyncByte0 } := uint8(readBitsUnsafe(, &, 8)) // nolint: gosec // no overflow, we read 8 bits if != 0x83 { return errWrongFrameSyncByte1 } := uint8(readBitsUnsafe(, &, 8)) // nolint: gosec // no overflow, we read 8 bits if != 0x42 { return errWrongFrameSyncByte2 } .ColorConfig = &HeaderColorConfig{} = .ColorConfig.unmarshal(.Profile, , &) if != nil { return } .FrameSize = &HeaderFrameSize{} = .FrameSize.unmarshal(, &) if != nil { return } } return nil } // Width returns the video width. func ( Header) () uint16 { if .FrameSize == nil { return 0 } return .FrameSize.FrameWidthMinus1 + 1 } // Height returns the video height. func ( Header) () uint16 { if .FrameSize == nil { return 0 } return .FrameSize.FrameHeightMinus1 + 1 }