snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
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
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
This commit is contained in:
239
pkg/media/h264reader/h264reader.go
Normal file
239
pkg/media/h264reader/h264reader.go
Normal file
@@ -0,0 +1,239 @@
|
||||
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package h264reader implements a H264 Annex-B Reader
|
||||
package h264reader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// H264Reader reads data from stream and constructs h264 nal units.
|
||||
type H264Reader struct {
|
||||
stream io.Reader
|
||||
nalBuffer []byte
|
||||
countOfConsecutiveZeroBytes int
|
||||
nalPrefixParsed bool
|
||||
readBuffer []byte
|
||||
tmpReadBuf []byte
|
||||
includeSEI bool
|
||||
}
|
||||
|
||||
var (
|
||||
errNilReader = errors.New("stream is nil")
|
||||
errDataIsNotH264Stream = errors.New("data is not a H264 bitstream")
|
||||
)
|
||||
|
||||
// NewReader creates new H264Reader.
|
||||
func NewReader(in io.Reader) (*H264Reader, error) {
|
||||
if in == nil {
|
||||
return nil, errNilReader
|
||||
}
|
||||
|
||||
reader := &H264Reader{
|
||||
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 H264Reader.
|
||||
type Option func(*H264Reader) error
|
||||
|
||||
// NewReaderWithOptions creates new H264Reader with options.
|
||||
// The default behavior is to skip SEI NAL units.
|
||||
func NewReaderWithOptions(in io.Reader, options ...Option) (*H264Reader, 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 *H264Reader) error {
|
||||
r.includeSEI = include
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NAL H.264 Network Abstraction Layer.
|
||||
type NAL struct {
|
||||
PictureOrderCount uint32
|
||||
|
||||
// NAL header
|
||||
ForbiddenZeroBit bool
|
||||
RefIdc uint8
|
||||
UnitType NalUnitType
|
||||
|
||||
Data []byte // header byte + rbsp
|
||||
}
|
||||
|
||||
func (reader *H264Reader) 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 *H264Reader) bitStreamStartsWithH264Prefix() (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, errDataIsNotH264Stream
|
||||
}
|
||||
|
||||
nalPrefix3BytesFound := bytes.Equal(nalPrefix3Bytes, prefixBuffer[:3])
|
||||
if n == 3 {
|
||||
if nalPrefix3BytesFound {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
return 0, errDataIsNotH264Stream
|
||||
}
|
||||
|
||||
// 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, errDataIsNotH264Stream
|
||||
}
|
||||
|
||||
// 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 *H264Reader) NextNAL() (*NAL, error) {
|
||||
if !reader.nalPrefixParsed {
|
||||
_, err := reader.bitStreamStartsWithH264Prefix()
|
||||
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 {
|
||||
nal := newNal(reader.nalBuffer)
|
||||
nal.parseHeader()
|
||||
if !reader.includeSEI && nal.UnitType == NalUnitTypeSEI {
|
||||
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 *H264Reader) 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, RefIdc: 0, UnitType: NalUnitTypeUnspecified, Data: data}
|
||||
}
|
||||
|
||||
func (h *NAL) parseHeader() {
|
||||
firstByte := h.Data[0]
|
||||
h.ForbiddenZeroBit = (((firstByte & 0x80) >> 7) == 1) // 0x80 = 0b10000000
|
||||
h.RefIdc = (firstByte & 0x60) >> 5 // 0x60 = 0b01100000
|
||||
h.UnitType = NalUnitType((firstByte & 0x1F) >> 0) // 0x1F = 0b00011111
|
||||
}
|
||||
Reference in New Issue
Block a user