Rewrite AnnexB/AVCC parsers
This commit is contained in:
@@ -13,3 +13,4 @@ Payloader code taken from [pion](https://github.com/pion/rtp) library. And chang
|
||||
- [AVC profiles table](https://developer.mozilla.org/ru/docs/Web/Media/Formats/codecs_parameter)
|
||||
- [Supported Media for Google Cast](https://developers.google.com/cast/docs/media)
|
||||
- [Two stream formats, Annex-B, AVCC (H.264) and HVCC (H.265)](https://www.programmersought.com/article/3901815022/)
|
||||
- https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/producer-reference-nal.html
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// Package annexb - universal for H264 and H265
|
||||
package annexb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const StartCode = "\x00\x00\x00\x01"
|
||||
const startAUD = StartCode + "\x09\xF0" + StartCode
|
||||
|
||||
// EncodeToAVCC
|
||||
// will change original slice data!
|
||||
// safeAppend should be used if original slice has useful data after end (part of other slice)
|
||||
//
|
||||
// FFmpeg MPEG-TS: 00000001 AUD 00000001 SPS 00000001 PPS 000001 IFrame
|
||||
// FFmpeg H264: 00000001 SPS 00000001 PPS 000001 IFrame 00000001 PFrame
|
||||
func EncodeToAVCC(b []byte, safeAppend bool) []byte {
|
||||
const minSize = len(StartCode) + 1
|
||||
|
||||
// 1. Check frist "start code"
|
||||
if len(b) < len(startAUD) || string(b[:len(StartCode)]) != StartCode {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. Skip Access unit delimiter (AUD) from FFmpeg
|
||||
if string(b[:len(startAUD)]) == startAUD {
|
||||
b = b[6:]
|
||||
}
|
||||
|
||||
var start int
|
||||
|
||||
for i, n := minSize, len(b)-minSize; i < n; {
|
||||
// 3. Check "start code" (first 2 bytes)
|
||||
if b[i] != 0 || b[i+1] != 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// 4. Check "start code" (3 bytes size or 4 bytes size)
|
||||
if b[i+2] == 1 {
|
||||
if safeAppend {
|
||||
// protect original slice from "damage"
|
||||
b = bytes.Clone(b)
|
||||
safeAppend = false
|
||||
}
|
||||
|
||||
// convert start code from 3 bytes to 4 bytes
|
||||
b = append(b, 0)
|
||||
copy(b[i+1:], b[i:])
|
||||
n++
|
||||
} else if b[i+2] != 0 || b[i+3] != 1 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// 5. Set size for previous AU
|
||||
size := uint32(i - start - len(StartCode))
|
||||
binary.BigEndian.PutUint32(b[start:], size)
|
||||
|
||||
start = i
|
||||
|
||||
i += minSize
|
||||
}
|
||||
|
||||
// 6. Set size for last AU
|
||||
size := uint32(len(b) - start - len(StartCode))
|
||||
binary.BigEndian.PutUint32(b[start:], size)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func DecodeAVCC(b []byte) []byte {
|
||||
b = bytes.Clone(b)
|
||||
for i := 0; i < len(b); {
|
||||
size := int(binary.BigEndian.Uint32(b[i:]))
|
||||
b[i] = 0
|
||||
b[i+1] = 0
|
||||
b[i+2] = 0
|
||||
b[i+3] = 1
|
||||
i += 4 + size
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
const (
|
||||
h264PFrame = 1
|
||||
h264IFrame = 5
|
||||
h264SPS = 7
|
||||
h264PPS = 8
|
||||
|
||||
h265VPS = 32
|
||||
h265PFrame = 1
|
||||
)
|
||||
|
||||
// IndexFrame - get new frame start position in the AnnexB stream
|
||||
func IndexFrame(b []byte) int {
|
||||
if len(b) < len(startAUD) {
|
||||
return -1
|
||||
}
|
||||
|
||||
for i := len(startAUD); ; {
|
||||
if di := bytes.Index(b[i:], []byte(StartCode)); di < 0 {
|
||||
break
|
||||
} else {
|
||||
i += di + 4 // move to NALU start
|
||||
}
|
||||
|
||||
if i >= len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
h264Type := b[i] & 0b1_1111
|
||||
switch h264Type {
|
||||
case h264PFrame, h264SPS:
|
||||
return i - 4 // move to start code
|
||||
case h264IFrame, h264PPS:
|
||||
continue
|
||||
}
|
||||
|
||||
h265Type := (b[i] >> 1) & 0b11_1111
|
||||
switch h265Type {
|
||||
case h265PFrame, h265VPS:
|
||||
return i - 4 // move to start code
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
+1
-102
@@ -3,46 +3,12 @@ package h264
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func AnnexB2AVC(b []byte) []byte {
|
||||
for i := 0; i < len(b); {
|
||||
if i+4 >= len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
size := bytes.Index(b[i+4:], []byte{0, 0, 0, 1})
|
||||
if size < 0 {
|
||||
size = len(b) - (i + 4)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(b[i:], uint32(size))
|
||||
|
||||
i += size + 4
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func AVCtoAnnexB(b []byte) []byte {
|
||||
b = bytes.Clone(b)
|
||||
for i := 0; i < len(b); {
|
||||
size := int(binary.BigEndian.Uint32(b[i:]))
|
||||
b[i] = 0
|
||||
b[i+1] = 0
|
||||
b[i+2] = 0
|
||||
b[i+3] = 1
|
||||
i += 4 + size
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
const forbiddenZeroBit = 0x80
|
||||
const nalUnitType = 0x1F
|
||||
|
||||
// DecodeStream - find and return first AU in AVC format
|
||||
// Deprecated: DecodeStream - find and return first AU in AVC format
|
||||
// useful for processing live streams with unknown separator size
|
||||
func DecodeStream(annexb []byte) ([]byte, int) {
|
||||
startPos := -1
|
||||
@@ -154,70 +120,3 @@ func IndexFrom(b []byte, sep []byte, from int) int {
|
||||
|
||||
return bytes.Index(b, sep)
|
||||
}
|
||||
|
||||
func EncodeAVC(nals ...[]byte) (avc []byte) {
|
||||
var i, n int
|
||||
|
||||
for _, nal := range nals {
|
||||
if i = len(nal); i > 0 {
|
||||
n += 4 + i
|
||||
}
|
||||
}
|
||||
|
||||
avc = make([]byte, n)
|
||||
|
||||
n = 0
|
||||
for _, nal := range nals {
|
||||
if i = len(nal); i > 0 {
|
||||
binary.BigEndian.PutUint32(avc[n:], uint32(i))
|
||||
n += 4 + copy(avc[n+4:], nal)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func RepairAVC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
sps, pps := GetParameterSet(codec.FmtpLine)
|
||||
ps := EncodeAVC(sps, pps)
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
if NALUType(packet.Payload) == NALUTypeIFrame {
|
||||
packet.Payload = Join(ps, packet.Payload)
|
||||
}
|
||||
handler(packet)
|
||||
}
|
||||
}
|
||||
|
||||
func SplitAVC(data []byte) [][]byte {
|
||||
var nals [][]byte
|
||||
for {
|
||||
// get AVC length
|
||||
size := int(binary.BigEndian.Uint32(data)) + 4
|
||||
|
||||
// check if multiple items in one packet
|
||||
if size < len(data) {
|
||||
nals = append(nals, data[:size])
|
||||
data = data[size:]
|
||||
} else {
|
||||
nals = append(nals, data)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nals
|
||||
}
|
||||
|
||||
func Types(data []byte) []byte {
|
||||
var types []byte
|
||||
for {
|
||||
types = append(types, NALUType(data))
|
||||
|
||||
size := 4 + int(binary.BigEndian.Uint32(data))
|
||||
if size < len(data) {
|
||||
data = data[size:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package avc
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0"
|
||||
src, err := hex.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
profile, sps, pps := DecodeConfig(src)
|
||||
require.NotNil(t, profile)
|
||||
require.NotNil(t, sps)
|
||||
require.NotNil(t, pps)
|
||||
|
||||
dst := EncodeConfig(sps, pps)
|
||||
require.Equal(t, src, dst)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Package h264 - AVCC format related functions
|
||||
package h264
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func RepairAVCC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
sps, pps := GetParameterSet(codec.FmtpLine)
|
||||
ps := JoinNALU(sps, pps)
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
if NALUType(packet.Payload) == NALUTypeIFrame {
|
||||
packet.Payload = Join(ps, packet.Payload)
|
||||
}
|
||||
handler(packet)
|
||||
}
|
||||
}
|
||||
|
||||
func JoinNALU(nalus ...[]byte) (avcc []byte) {
|
||||
var i, n int
|
||||
|
||||
for _, nalu := range nalus {
|
||||
if i = len(nalu); i > 0 {
|
||||
n += 4 + i
|
||||
}
|
||||
}
|
||||
|
||||
avcc = make([]byte, n)
|
||||
|
||||
n = 0
|
||||
for _, nal := range nalus {
|
||||
if i = len(nal); i > 0 {
|
||||
binary.BigEndian.PutUint32(avcc[n:], uint32(i))
|
||||
n += 4 + copy(avcc[n+4:], nal)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func SplitNALU(avcc []byte) [][]byte {
|
||||
var nals [][]byte
|
||||
for {
|
||||
// get AVC length
|
||||
size := int(binary.BigEndian.Uint32(avcc)) + 4
|
||||
|
||||
// check if multiple items in one packet
|
||||
if size < len(avcc) {
|
||||
nals = append(nals, avcc[:size])
|
||||
avcc = avcc[size:]
|
||||
} else {
|
||||
nals = append(nals, avcc)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nals
|
||||
}
|
||||
|
||||
func NALUTypes(avcc []byte) []byte {
|
||||
var types []byte
|
||||
for {
|
||||
types = append(types, NALUType(avcc))
|
||||
|
||||
size := 4 + int(binary.BigEndian.Uint32(avcc))
|
||||
if size < len(avcc) {
|
||||
avcc = avcc[size:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func AVCCToCodec(avcc []byte) *core.Codec {
|
||||
buf := bytes.NewBufferString("packetization-mode=1")
|
||||
|
||||
for {
|
||||
size := 4 + int(binary.BigEndian.Uint32(avcc))
|
||||
|
||||
switch NALUType(avcc) {
|
||||
case NALUTypeSPS:
|
||||
buf.WriteString(";profile-level-id=")
|
||||
buf.WriteString(hex.EncodeToString(avcc[5:8]))
|
||||
buf.WriteString(";sprop-parameter-sets=")
|
||||
buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size]))
|
||||
case NALUTypePPS:
|
||||
buf.WriteString(",")
|
||||
buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size]))
|
||||
}
|
||||
|
||||
if size < len(avcc) {
|
||||
avcc = avcc[size:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &core.Codec{
|
||||
Name: core.CodecH264,
|
||||
ClockRate: 90000,
|
||||
FmtpLine: buf.String(),
|
||||
PayloadType: core.PayloadTypeRAW,
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
package avc
|
||||
package h264
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0"
|
||||
src, err := hex.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
profile, sps, pps := DecodeConfig(src)
|
||||
require.NotNil(t, profile)
|
||||
require.NotNil(t, sps)
|
||||
require.NotNil(t, pps)
|
||||
|
||||
dst := EncodeConfig(sps, pps)
|
||||
require.Equal(t, src, dst)
|
||||
}
|
||||
|
||||
func TestDecodeSPS(t *testing.T) {
|
||||
s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
@@ -14,7 +29,7 @@ func TestDecodeSPS(t *testing.T) {
|
||||
|
||||
sps := DecodeSPS(b)
|
||||
require.Equal(t, uint16(2560), sps.Width())
|
||||
require.Equal(t, uint16(1920), sps.Heigth())
|
||||
require.Equal(t, uint16(1920), sps.Height())
|
||||
|
||||
s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff
|
||||
b, err = base64.StdEncoding.DecodeString(s)
|
||||
@@ -22,5 +37,5 @@ func TestDecodeSPS(t *testing.T) {
|
||||
|
||||
sps = DecodeSPS(b)
|
||||
require.Equal(t, uint16(1920), sps.Width())
|
||||
require.Equal(t, uint16(1080), sps.Heigth())
|
||||
require.Equal(t, uint16(1080), sps.Height())
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package avc
|
||||
// Package h264 - MPEG4 format related functions
|
||||
package h264
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
)
|
||||
|
||||
// DecodeConfig - extract profile, SPS and PPS from MPEG4 config
|
||||
func DecodeConfig(conf []byte) (profile []byte, sps []byte, pps []byte) {
|
||||
if len(conf) < 6 || conf[0] != 1 {
|
||||
return
|
||||
+4
-2
@@ -2,7 +2,9 @@ package h264
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
)
|
||||
@@ -15,7 +17,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
depack := &codecs.H264Packet{IsAVC: true}
|
||||
|
||||
sps, pps := GetParameterSet(codec.FmtpLine)
|
||||
ps := EncodeAVC(sps, pps)
|
||||
ps := JoinNALU(sps, pps)
|
||||
|
||||
buf := make([]byte, 0, 512*1024) // 512K
|
||||
|
||||
@@ -81,7 +83,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
// some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01
|
||||
// https://github.com/AlexxIT/WebRTC/issues/391
|
||||
// https://github.com/AlexxIT/WebRTC/issues/392
|
||||
AnnexB2AVC(payload)
|
||||
payload = annexb.EncodeToAVCC(payload, false)
|
||||
}
|
||||
|
||||
//log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package avc
|
||||
package h264
|
||||
|
||||
import "github.com/AlexxIT/go2rtc/pkg/bits"
|
||||
|
||||
@@ -49,11 +49,26 @@ type SPS struct {
|
||||
sar_height uint32
|
||||
}
|
||||
|
||||
func (s *SPS) Width() uint16 {
|
||||
width := 16 * (s.pic_width_in_mbs_minus_1 + 1)
|
||||
crop := 2 * (s.frame_crop_left_offset + s.frame_crop_right_offset)
|
||||
return uint16(width - crop)
|
||||
}
|
||||
|
||||
func (s *SPS) Height() uint16 {
|
||||
height := 16 * (s.pic_height_in_map_units_minus_1 + 1)
|
||||
crop := 2 * (s.frame_crop_top_offset + s.frame_crop_bottom_offset)
|
||||
if s.frame_mbs_only_flag == 0 {
|
||||
height *= 2
|
||||
}
|
||||
return uint16(height - crop)
|
||||
}
|
||||
|
||||
func DecodeSPS(sps []byte) *SPS {
|
||||
r := bits.NewReader(sps)
|
||||
|
||||
hdr := r.ReadByte()
|
||||
if hdr&0x1F != 7 {
|
||||
if hdr&0x1F != NALUTypeSPS {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,18 +162,3 @@ func DecodeSPS(sps []byte) *SPS {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SPS) Width() uint16 {
|
||||
width := 16 * (s.pic_width_in_mbs_minus_1 + 1)
|
||||
crop := 2 * (s.frame_crop_left_offset + s.frame_crop_right_offset)
|
||||
return uint16(width - crop)
|
||||
}
|
||||
|
||||
func (s *SPS) Heigth() uint16 {
|
||||
height := 16 * (s.pic_height_in_map_units_minus_1 + 1)
|
||||
crop := 2 * (s.frame_crop_top_offset + s.frame_crop_bottom_offset)
|
||||
if s.frame_mbs_only_flag == 0 {
|
||||
height *= 2
|
||||
}
|
||||
return uint16(height - crop)
|
||||
}
|
||||
Reference in New Issue
Block a user