Files
upstream-mirror 8acb1a3a29
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s
snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
2026-09-15 20:06:07 +05:00

265 lines
5.8 KiB
Go

// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package h265reader implements a H265/HEVC Annex-B Reader
package h265reader
import (
"bytes"
"errors"
"io"
)
// H265Reader reads data from stream and constructs h265 nal units.
type H265Reader struct {
stream io.Reader
nalBuffer []byte
countOfConsecutiveZeroBytes int
nalPrefixParsed bool
readBuffer []byte
tmpReadBuf []byte
includeSEI bool
}
var (
errNilReader = errors.New("stream is nil")
errDataIsNotH265Stream = errors.New("data is not a H265/HEVC bitstream")
)
func (reader *H265Reader) shouldSkipNAL(naluType NalUnitType) bool {
return !reader.includeSEI && (naluType == NalUnitTypePrefixSei || naluType == NalUnitTypeSuffixSei)
}
// NewReader creates new H265Reader.
func NewReader(in io.Reader) (*H265Reader, error) {
if in == nil {
return nil, errNilReader
}
reader := &H265Reader{
stream: in,
nalBuffer: make([]byte, 0),
nalPrefixParsed: false,
readBuffer: make([]byte, 0),
tmpReadBuf: make([]byte, 4096),
includeSEI: false,
}
return reader, nil
}
// Option configures the behavior of H265Reader.
type Option func(*H265Reader) error
// NewReaderWithOptions creates new H265Reader with options.
// The default behavior is to skip SEI NAL units.
func NewReaderWithOptions(in io.Reader, options ...Option) (*H265Reader, error) {
reader, err := NewReader(in)
if err != nil {
return nil, err
}
for _, option := range options {
if err := option(reader); err != nil {
return nil, err
}
}
return reader, nil
}
// WithIncludeSEI controls whether SEI (Supplemental Enhancement Information) NAL units are returned.
// Default is false (SEI is skipped).
func WithIncludeSEI(include bool) Option {
return func(r *H265Reader) error {
r.includeSEI = include
return nil
}
}
// NAL H.265/HEVC Network Abstraction Layer.
type NAL struct {
PictureOrderCount uint32
/* NAL Unit header https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
+---------------+---------------+
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F| Type | LayerId | TID |
+-------------+-----------------+
*/
ForbiddenZeroBit bool
NalUnitType NalUnitType
LayerID uint8
TemporalIDPlus1 uint8
Data []byte // header bytes + rbsp
}
func (reader *H265Reader) read(numToRead int) (data []byte, e error) {
for len(reader.readBuffer) < numToRead {
n, err := reader.stream.Read(reader.tmpReadBuf)
if err != nil {
return nil, err
}
if n == 0 {
break
}
reader.readBuffer = append(reader.readBuffer, reader.tmpReadBuf[0:n]...)
}
numShouldRead := min(numToRead, len(reader.readBuffer))
data = reader.readBuffer[0:numShouldRead]
reader.readBuffer = reader.readBuffer[numShouldRead:]
return data, nil
}
func (reader *H265Reader) bitStreamStartsWithH265Prefix() (prefixLength int, e error) {
nalPrefix3Bytes := []byte{0, 0, 1}
nalPrefix4Bytes := []byte{0, 0, 0, 1}
prefixBuffer, e := reader.read(4)
if e != nil {
return prefixLength, e
}
n := len(prefixBuffer)
if n == 0 {
return 0, io.EOF
}
if n < 3 {
return 0, errDataIsNotH265Stream
}
nalPrefix3BytesFound := bytes.Equal(nalPrefix3Bytes, prefixBuffer[:3])
if n == 3 {
if nalPrefix3BytesFound {
return 0, io.EOF
}
return 0, errDataIsNotH265Stream
}
// n == 4
if nalPrefix3BytesFound {
reader.nalBuffer = append(reader.nalBuffer, prefixBuffer[3])
return 3, nil
}
nalPrefix4BytesFound := bytes.Equal(nalPrefix4Bytes, prefixBuffer)
if nalPrefix4BytesFound {
return 4, nil
}
return 0, errDataIsNotH265Stream
}
// NextNAL reads from stream and returns then next NAL,
// and an error if there is incomplete frame data.
// Returns all nil values when no more NALs are available.
func (reader *H265Reader) NextNAL() (*NAL, error) {
if !reader.nalPrefixParsed {
_, err := reader.bitStreamStartsWithH265Prefix()
if err != nil {
return nil, err
}
reader.nalPrefixParsed = true
}
for {
buffer, err := reader.read(1)
if err != nil {
break
}
n := len(buffer)
if n != 1 {
break
}
readByte := buffer[0]
nalFound := reader.processByte(readByte)
if nalFound {
naluType := NalUnitType((reader.nalBuffer[0] & 0x7E) >> 1)
if reader.shouldSkipNAL(naluType) {
reader.nalBuffer = nil
continue
}
break
}
reader.nalBuffer = append(reader.nalBuffer, readByte)
}
if len(reader.nalBuffer) == 0 {
return nil, io.EOF
}
nal := newNal(reader.nalBuffer)
reader.nalBuffer = nil
nal.parseHeader()
return nal, nil
}
func (reader *H265Reader) processByte(readByte byte) (nalFound bool) {
nalFound = false
switch readByte {
case 0:
reader.countOfConsecutiveZeroBytes++
case 1:
if reader.countOfConsecutiveZeroBytes >= 2 {
countOfConsecutiveZeroBytesInPrefix := 2
if reader.countOfConsecutiveZeroBytes > 2 {
countOfConsecutiveZeroBytesInPrefix = 3
}
if nalUnitLength := len(reader.nalBuffer) - countOfConsecutiveZeroBytesInPrefix; nalUnitLength > 0 {
reader.nalBuffer = reader.nalBuffer[0:nalUnitLength]
nalFound = true
}
}
reader.countOfConsecutiveZeroBytes = 0
default:
reader.countOfConsecutiveZeroBytes = 0
}
return nalFound
}
func newNal(data []byte) *NAL {
return &NAL{
PictureOrderCount: 0,
ForbiddenZeroBit: false,
NalUnitType: NalUnitTypeTrailN,
LayerID: 0,
TemporalIDPlus1: 0,
Data: data,
}
}
func (h *NAL) parseHeader() {
if len(h.Data) < 2 {
return
}
// H.265 NAL header is 2 bytes
firstByte := h.Data[0]
secondByte := h.Data[1]
h.ForbiddenZeroBit = (firstByte & 0x80) != 0
h.NalUnitType = NalUnitType((firstByte & 0x7E) >> 1)
h.LayerID = ((firstByte & 0x01) << 5) | ((secondByte & 0xF8) >> 3)
h.TemporalIDPlus1 = secondByte & 0x07
}