Help us shape the future of Go! Take the 2026 Go Developer Survey

bitstream

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: BSD-3-Clause Imports: 3 Imported by: 2

README

bitstream

Pure-Go (CGO=0) primitives every H.264/H.265-family reader needs before it can parse anything: a bit reader with the formats' own integer codings, the two NAL framings, and the unescaping that a payload is meaningless without.

units, err := bitstream.SplitAnnexB(data)   // 00 00 01 start codes
r := bitstream.NewReader(bitstream.Unescape(units[0]))
profile, err := r.Bits(8)
id, err := r.UE()                            // Exp-Golomb, unsigned
delta, err := r.SE()                         // Exp-Golomb, signed

What is in it

SplitAnnexB units separated by 00 00 01 / 00 00 00 01 start codes
SplitLengthPrefixed units behind a 1-, 2- or 4-byte length, as a sample from an MP4 track carries them
Unescape removes the emulation-prevention bytes (00 00 03)
Reader Bit, Bits, Peek, UE, SE, Pos, Left, MoreData

Unescape is the one that gets forgotten. A unit read without it is not broken, it is subtly wrong: the bytes are all there, in order, with an extra 03 wherever two zeroes met — so the fields after that point decode to plausible values that belong to nothing.

MoreData is more_rbsp_data(): it answers whether anything but the trailing stop bit and padding remains, which is how a reader knows an optional syntax element is present at all.

Every read returns an error rather than a zero: a bit beyond the end of a unit is a truncated stream, and a reader that answered 0 would carry on parsing whatever follows.

Windows, macOS, Linux; six 64-bit architectures. 100% statement coverage, gated in CI.

A width the value cannot hold

Bits returns a uint32, so it reads 0 to 32 bits and refuses anything else with ErrWidth. There is no 64-bit reader, so no caller can want more.

⛔ It used to answer silently. Bits(33) gave the second bit to the thirty-third — the top one shifted out of the uint32 — with no error and the position correctly advanced, so nothing downstream could notice. The value returned was a field nobody asked for:

Bits(33)          = 0xbd5b7ddf
Bit(); Bits(32)   = 0xbd5b7ddf    identical

A negative width read nothing and said nothing, which is how a width computed one field too early arrives here.

The refusal happens before any bit is consumed, so a caller that handles the error can read the field another way. Both ends are read: 0 and 32 are widths the value holds, and UE needs a 32-bit read to reach its largest value (2³²−1, from a 65-bit code) — a bound at 31 breaks it, and there is a witness.

Licence

BSD-3-Clause.

Documentation

Overview

Package bitstream holds what the video codecs of the ITU-T H.26x family read their headers with, in pure Go.

It exists because two codecs needed the same code and neither should depend on the other. H.264 and H.265 frame their units identically -- the same start codes, the same escaping inside a unit, the same variable-length integers -- and differ only in how they read the header bytes of a unit once it is found. That difference is the seam: this package finds the units and reads the bits, and knows nothing about what a unit means.

Measured before it was written: the framing of a real HEVC stream, split by the H.264 code this was lifted from, gave 65 units whose types read the H.265 way are exactly what such a stream holds -- one video parameter set, one sequence set, one picture set, one IDR picture, thirty trailing pictures and their SEI messages. The framing transfers; the header does not.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoStartCode means a byte stream holds no unit at all.
	ErrNoStartCode = errors.New("bitstream: no start code in the byte stream")
	// ErrLengthOverrun means a length-prefixed unit claims more bytes than the
	// stream holds.
	ErrLengthOverrun = errors.New("bitstream: unit length runs past the end")
	// ErrEmptySize means a length size is not one these formats use.
	ErrEmptySize = errors.New("bitstream: unit length size is not 1 to 4")
)

Errors framing can refuse with.

View Source
var (
	// ErrShort means the bitstream ended in the middle of a field.
	ErrShort = errors.New("bitstream: ends inside a field")
	// ErrTooLong means an Exp-Golomb code claims more leading zeros than any
	// value these formats state could need.
	ErrTooLong = errors.New("bitstream: Exp-Golomb code is longer than any value needs")

	// ErrWidth means a read was asked for a number of bits the value it returns
	// cannot hold, or a negative one. Both used to be answered silently.
	ErrWidth = errors.New("bitstream: a width the value cannot hold")
)

Errors a reader can refuse with.

Functions

func SplitAnnexB

func SplitAnnexB(data []byte) ([][]byte, error)

SplitAnnexB returns the units of a byte stream, the form a raw elementary stream and an MPEG-TS stream carry.

⛔ The units are returned as they lie, header bytes included, because how many of those bytes are header is the ONE thing that differs between the codecs sharing this: H.264 spends one and H.265 spends two. Reading them here would put a codec's rule in the one place that must not have one.

Units are separated by two or more zero bytes and a one. Three zeros and a one is the same separator with a leading zero, so the scan looks for the three-byte form and lets a fourth zero belong to the gap rather than to the unit -- a unit that began with a stray zero would have its header read out of a byte nobody wrote there. Trailing zeros are dropped for the same reason in reverse: an encoder may pad, and padding is not payload.

A stream cut after its last separator keeps the units that did arrive: a cut stream is the normal state of a download in progress, and refusing the whole of it would lose everything that was there.

func SplitLengthPrefixed

func SplitLengthPrefixed(data []byte, sizeBytes int) ([][]byte, error)

SplitLengthPrefixed returns the units of the form an MP4 sample carries, where each is preceded by its length in sizeBytes bytes.

sizeBytes comes from the codec's configuration record and is 1, 2 or 4 in practice; anything outside 1 to 4 is refused rather than guessed, since a wrong size reads a length out of payload and would walk the stream into nonsense.

func Unescape

func Unescape(unit []byte) []byte

Unescape undoes the escaping inside a unit.

These formats may not carry three consecutive bytes that look like a start code, so an encoder writes 00 00 03 where it means 00 00 and the reader drops the three.

⛔ Only a three that follows exactly two zeros is an escape: dropping every three after any zero would eat payload, and the byte after the escape is whatever it is -- including another zero, which starts the count again.

Types

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader reads the fields of a payload: single bits, fixed-width values, and the Exp-Golomb integers these formats write their parameter sets with.

It is given an UNESCAPED payload. Escaping is undone by Unescape, and keeping the two apart means a caller that only walks past units never pays for a copy of one.

func NewReader

func NewReader(data []byte) *Reader

NewReader reads the bits of data.

func (*Reader) Bit

func (r *Reader) Bit() (uint32, error)

Bit reads one bit.

func (*Reader) Bits

func (r *Reader) Bits(n int) (uint32, error)

Bits reads n bits, most significant first.

func (*Reader) Left

func (r *Reader) Left() int

Left is how many bits remain. It is what tells a reader whether an optional field follows or only the bits that end a payload.

func (*Reader) MoreData

func (r *Reader) MoreData() bool

MoreData says whether any syntax element remains before the bits that end a payload.

Those bits are a single one followed by zeros to the byte boundary, so what is left is a field only if a one appears after the first one, or the first one is not where the padding would put it. Reading them as a field is how an optional tail comes to be read from a payload that does not have one.

func (*Reader) Peek

func (r *Reader) Peek(n int) (uint32, error)

Peek reads n bits without consuming them.

It exists for one question these formats ask and which cannot be answered any other way: whether what is left is a syntax element or the padding that ends a payload. Answering it by reading would consume the field it was asking about.

func (*Reader) Pos

func (r *Reader) Pos() int

Pos is how many bits have been read, which is what lets a caller say where a refusal happened rather than only that one did.

func (*Reader) SE

func (r *Reader) SE() (int32, error)

SE reads a signed Exp-Golomb integer, which is the unsigned one folded so that 1 means +1, 2 means -1, 3 means +2, and so on.

func (*Reader) UE

func (r *Reader) UE() (uint32, error)

UE reads an unsigned Exp-Golomb integer: n zeros, a one, then n more bits, giving a value of 2^n - 1 plus those bits.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL