Skip to content

Commit 4c1af4c

Browse files
committed
H265 reader & writer
H265 raeder & writer
1 parent e602e15 commit 4c1af4c

File tree

5 files changed

+786
-0
lines changed

5 files changed

+786
-0
lines changed

pkg/media/h265reader/h265reader.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
// Package h265reader implements a H265/HEVC Annex-B Reader
5+
package h265reader
6+
7+
import (
8+
"bytes"
9+
"errors"
10+
"io"
11+
)
12+
13+
// H265Reader reads data from stream and constructs h265 nal units.
14+
type H265Reader struct {
15+
stream io.Reader
16+
nalBuffer []byte
17+
countOfConsecutiveZeroBytes int
18+
nalPrefixParsed bool
19+
readBuffer []byte
20+
tmpReadBuf []byte
21+
}
22+
23+
var (
24+
errNilReader = errors.New("stream is nil")
25+
errDataIsNotH265Stream = errors.New("data is not a H265/HEVC bitstream")
26+
)
27+
28+
// NewReader creates new H265Reader.
29+
func NewReader(in io.Reader) (*H265Reader, error) {
30+
if in == nil {
31+
return nil, errNilReader
32+
}
33+
34+
reader := &H265Reader{
35+
stream: in,
36+
nalBuffer: make([]byte, 0),
37+
nalPrefixParsed: false,
38+
readBuffer: make([]byte, 0),
39+
tmpReadBuf: make([]byte, 4096),
40+
}
41+
42+
return reader, nil
43+
}
44+
45+
// NAL H.265/HEVC Network Abstraction Layer.
46+
type NAL struct {
47+
PictureOrderCount uint32
48+
49+
/* NAL Unit header https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
50+
+---------------+---------------+
51+
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
52+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53+
|F| Type | LayerId | TID |
54+
+-------------+-----------------+
55+
*/
56+
ForbiddenZeroBit bool
57+
NalUnitType NalUnitType
58+
LayerID uint8
59+
TemporalIDPlus1 uint8
60+
61+
Data []byte // header bytes + rbsp
62+
}
63+
64+
func (reader *H265Reader) read(numToRead int) (data []byte, e error) {
65+
for len(reader.readBuffer) < numToRead {
66+
n, err := reader.stream.Read(reader.tmpReadBuf)
67+
if err != nil {
68+
return nil, err
69+
}
70+
if n == 0 {
71+
break
72+
}
73+
reader.readBuffer = append(reader.readBuffer, reader.tmpReadBuf[0:n]...)
74+
}
75+
var numShouldRead int
76+
if numToRead <= len(reader.readBuffer) {
77+
numShouldRead = numToRead
78+
} else {
79+
numShouldRead = len(reader.readBuffer)
80+
}
81+
data = reader.readBuffer[0:numShouldRead]
82+
reader.readBuffer = reader.readBuffer[numShouldRead:]
83+
84+
return data, nil
85+
}
86+
87+
func (reader *H265Reader) bitStreamStartsWithH265Prefix() (prefixLength int, e error) {
88+
nalPrefix3Bytes := []byte{0, 0, 1}
89+
nalPrefix4Bytes := []byte{0, 0, 0, 1}
90+
91+
prefixBuffer, e := reader.read(4)
92+
if e != nil {
93+
return prefixLength, e
94+
}
95+
96+
n := len(prefixBuffer)
97+
98+
if n == 0 {
99+
return 0, io.EOF
100+
}
101+
102+
if n < 3 {
103+
return 0, errDataIsNotH265Stream
104+
}
105+
106+
nalPrefix3BytesFound := bytes.Equal(nalPrefix3Bytes, prefixBuffer[:3])
107+
if n == 3 {
108+
if nalPrefix3BytesFound {
109+
return 0, io.EOF
110+
}
111+
112+
return 0, errDataIsNotH265Stream
113+
}
114+
115+
// n == 4
116+
if nalPrefix3BytesFound {
117+
reader.nalBuffer = append(reader.nalBuffer, prefixBuffer[3])
118+
119+
return 3, nil
120+
}
121+
122+
nalPrefix4BytesFound := bytes.Equal(nalPrefix4Bytes, prefixBuffer)
123+
if nalPrefix4BytesFound {
124+
return 4, nil
125+
}
126+
127+
return 0, errDataIsNotH265Stream
128+
}
129+
130+
// NextNAL reads from stream and returns then next NAL,
131+
// and an error if there is incomplete frame data.
132+
// Returns all nil values when no more NALs are available.
133+
func (reader *H265Reader) NextNAL() (*NAL, error) {
134+
if !reader.nalPrefixParsed {
135+
_, err := reader.bitStreamStartsWithH265Prefix()
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
reader.nalPrefixParsed = true
141+
}
142+
143+
for {
144+
buffer, err := reader.read(1)
145+
if err != nil {
146+
break
147+
}
148+
149+
n := len(buffer)
150+
151+
if n != 1 {
152+
break
153+
}
154+
readByte := buffer[0]
155+
nalFound := reader.processByte(readByte)
156+
if nalFound {
157+
naluType := NalUnitType((reader.nalBuffer[0] & 0x7E) >> 1)
158+
if naluType == NalUnitTypePrefixSei || naluType == NalUnitTypeSuffixSei {
159+
reader.nalBuffer = nil
160+
161+
continue
162+
}
163+
164+
break
165+
}
166+
167+
reader.nalBuffer = append(reader.nalBuffer, readByte)
168+
}
169+
170+
if len(reader.nalBuffer) == 0 {
171+
return nil, io.EOF
172+
}
173+
174+
nal := newNal(reader.nalBuffer)
175+
reader.nalBuffer = nil
176+
nal.parseHeader()
177+
178+
return nal, nil
179+
}
180+
181+
func (reader *H265Reader) processByte(readByte byte) (nalFound bool) {
182+
nalFound = false
183+
184+
switch readByte {
185+
case 0:
186+
reader.countOfConsecutiveZeroBytes++
187+
case 1:
188+
if reader.countOfConsecutiveZeroBytes >= 2 {
189+
countOfConsecutiveZeroBytesInPrefix := 2
190+
if reader.countOfConsecutiveZeroBytes > 2 {
191+
countOfConsecutiveZeroBytesInPrefix = 3
192+
}
193+
194+
if nalUnitLength := len(reader.nalBuffer) - countOfConsecutiveZeroBytesInPrefix; nalUnitLength > 0 {
195+
reader.nalBuffer = reader.nalBuffer[0:nalUnitLength]
196+
nalFound = true
197+
}
198+
}
199+
200+
reader.countOfConsecutiveZeroBytes = 0
201+
default:
202+
reader.countOfConsecutiveZeroBytes = 0
203+
}
204+
205+
return nalFound
206+
}
207+
208+
func newNal(data []byte) *NAL {
209+
return &NAL{
210+
PictureOrderCount: 0,
211+
ForbiddenZeroBit: false,
212+
NalUnitType: NalUnitTypeTrailN,
213+
LayerID: 0,
214+
TemporalIDPlus1: 0,
215+
Data: data,
216+
}
217+
}
218+
219+
func (h *NAL) parseHeader() {
220+
if len(h.Data) < 2 {
221+
return
222+
}
223+
224+
// H.265 NAL header is 2 bytes
225+
firstByte := h.Data[0]
226+
secondByte := h.Data[1]
227+
228+
h.ForbiddenZeroBit = (firstByte & 0x80) != 0
229+
h.NalUnitType = NalUnitType((firstByte & 0x7E) >> 1)
230+
h.LayerID = ((firstByte & 0x01) << 5) | ((secondByte & 0xF8) >> 3)
231+
h.TemporalIDPlus1 = secondByte & 0x07
232+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package h265reader
5+
6+
import (
7+
"bytes"
8+
"io"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestH265Reader_NextNAL(t *testing.T) {
15+
// Test with invalid data
16+
reader, err := NewReader(bytes.NewReader([]byte{0xFF, 0xFF, 0xFF, 0xFF}))
17+
assert.NoError(t, err)
18+
19+
_, err = reader.NextNAL()
20+
assert.Equal(t, errDataIsNotH265Stream.Error(), err.Error())
21+
22+
// Test with valid H265 prefix but no NAL data
23+
reader, err = NewReader(bytes.NewReader([]byte{0, 0, 1}))
24+
assert.NoError(t, err)
25+
26+
_, err = reader.NextNAL()
27+
assert.Equal(t, io.EOF, err)
28+
29+
// Test with valid H265 NAL unit (VPS example)
30+
nalData := []byte{
31+
0x0, 0x0, 0x0, 0x1, 0x40, 0x01, 0x0C, 0x01, 0xFF, 0xFF, 0x01, 0x60, 0x00,
32+
0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xAC, 0x09,
33+
}
34+
reader, err = NewReader(bytes.NewReader(nalData))
35+
assert.NoError(t, err)
36+
37+
nal, err := reader.NextNAL()
38+
assert.NoError(t, err)
39+
assert.NotNil(t, nal)
40+
41+
assert.Equal(t, NalUnitTypeVps, nal.NalUnitType)
42+
assert.False(t, nal.ForbiddenZeroBit)
43+
44+
// Test reading multiple NAL units
45+
nalData = append(nalData, []byte{
46+
0x0, 0x0, 0x0, 0x1, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03,
47+
0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xA0,
48+
0x03, 0xC0, 0x80, 0x10, 0xE5, 0x96, 0x56, 0x69, 0x24, 0xCA, 0xE0,
49+
0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01, 0xE0, 0x80,
50+
}...)
51+
reader, err = NewReader(bytes.NewReader(nalData))
52+
assert.NoError(t, err)
53+
54+
// First NAL (VPS)
55+
nal1, err := reader.NextNAL()
56+
assert.NoError(t, err)
57+
assert.Equal(t, NalUnitTypeVps, nal1.NalUnitType)
58+
59+
// Second NAL (SPS)
60+
nal2, err := reader.NextNAL()
61+
assert.NoError(t, err)
62+
assert.Equal(t, NalUnitTypeSps, nal2.NalUnitType)
63+
64+
// Test EOF
65+
_, err = reader.NextNAL()
66+
assert.Equal(t, io.EOF, err)
67+
}
68+
69+
func TestH265Reader_processByte(t *testing.T) {
70+
reader := &H265Reader{
71+
nalBuffer: []byte{1, 2, 3, 0, 0},
72+
countOfConsecutiveZeroBytes: 2,
73+
}
74+
75+
// Test finding NAL boundary
76+
nalFound := reader.processByte(1)
77+
assert.True(t, nalFound)
78+
assert.Equal(t, 3, len(reader.nalBuffer))
79+
80+
// Test zero byte counting
81+
reader.countOfConsecutiveZeroBytes = 0
82+
nalFound = reader.processByte(0)
83+
assert.False(t, nalFound)
84+
assert.Equal(t, 1, reader.countOfConsecutiveZeroBytes)
85+
86+
// Test non-zero, non-one byte
87+
reader.countOfConsecutiveZeroBytes = 5
88+
nalFound = reader.processByte(0xFF)
89+
assert.False(t, nalFound)
90+
assert.Equal(t, 0, reader.countOfConsecutiveZeroBytes)
91+
}
92+
93+
func TestNAL_parseHeader(t *testing.T) {
94+
// Test VPS NAL header parsing
95+
data := []byte{0x40, 0x01, 0x0C, 0x01} // VPS NAL unit
96+
nal := newNal(data)
97+
nal.parseHeader()
98+
99+
assert.False(t, nal.ForbiddenZeroBit)
100+
assert.Equal(t, NalUnitTypeVps, nal.NalUnitType)
101+
assert.Equal(t, uint8(0), nal.LayerID)
102+
assert.Equal(t, uint8(1), nal.TemporalIDPlus1)
103+
104+
// Test SPS NAL header parsing
105+
data = []byte{0x42, 0x01, 0x01, 0x01} // SPS NAL unit
106+
nal = newNal(data)
107+
nal.parseHeader()
108+
109+
assert.False(t, nal.ForbiddenZeroBit)
110+
assert.Equal(t, NalUnitTypeSps, nal.NalUnitType)
111+
112+
// Test with insufficient data
113+
data = []byte{0x40} // Only one byte
114+
nal = newNal(data)
115+
nal.parseHeader() // Should not panic
116+
117+
// Test forbidden bit set
118+
data = []byte{0x80, 0x01} // Forbidden bit set
119+
nal = newNal(data)
120+
nal.parseHeader()
121+
122+
assert.True(t, nal.ForbiddenZeroBit)
123+
}

0 commit comments

Comments
 (0)