Initial commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
## WebRTC
|
||||
|
||||
Video codec | Media string | Device
|
||||
----------------|--------------|-------
|
||||
H.264/baseline! | avc1.42E0xx | Chromecast
|
||||
H.264/baseline! | avc1.42E0xx | Chrome/Safari WebRTC
|
||||
H.264/baseline! | avc1.42C0xx | FFmpeg ultrafast
|
||||
H.264/baseline! | avc1.4240xx | Dahua H264B
|
||||
H.264/baseline | avc1.4200xx | Chrome WebRTC
|
||||
H.264/main! | avc1.4D40xx | Chromecast
|
||||
H.264/main! | avc1.4D40xx | FFmpeg superfast main
|
||||
H.264/main! | avc1.4D40xx | Dahua H264
|
||||
H.264/main | avc1.4D00xx | Chrome WebRTC
|
||||
H.264/high! | avc1.640Cxx | Safari WebRTC
|
||||
H.264/high | avc1.6400xx | Chromecast
|
||||
H.264/high | avc1.6400xx | FFmpeg superfast
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [RTP Payload Format for H.264 Video](https://datatracker.ietf.org/doc/html/rfc6184)
|
||||
- [The H264 Sequence parameter set](https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set)
|
||||
- [H.264 Video Types (Microsoft)](https://docs.microsoft.com/en-us/windows/win32/directshow/h-264-video-types)
|
||||
- [Automatic Generation of H.264 Parameter Sets to Recover Video File Fragments](https://arxiv.org/pdf/2104.14522.pdf)
|
||||
- [Chromium sources](https://chromium.googlesource.com/external/webrtc/+/HEAD/common_video/h264)
|
||||
- [AVC levels](https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels)
|
||||
- [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)
|
||||
@@ -0,0 +1,87 @@
|
||||
package golomb
|
||||
|
||||
import "bytes"
|
||||
|
||||
type Reader struct {
|
||||
r *bytes.Reader
|
||||
b byte
|
||||
shift byte
|
||||
}
|
||||
|
||||
func NewReader(b []byte) *Reader {
|
||||
return &Reader{
|
||||
r: bytes.NewReader(b),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Reader) ReadBit() (b byte, err error) {
|
||||
if g.shift == 0 {
|
||||
if g.b, err = g.r.ReadByte(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
g.shift = 7
|
||||
} else {
|
||||
g.shift--
|
||||
}
|
||||
b = (g.b >> g.shift) & 0b1
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Reader) ReadBits(n byte) (res uint, err error) {
|
||||
var b byte
|
||||
for i := n - 1; i != 255; i-- {
|
||||
if b, err = g.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
res |= uint(b) << i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Reader) ReadUEGolomb() (res uint, err error) {
|
||||
var b uint
|
||||
var i byte
|
||||
for i = 0; i < 32; i++ {
|
||||
if b, err = g.ReadBits(1); err != nil {
|
||||
return
|
||||
}
|
||||
if b != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if res, err = g.ReadBits(i); err != nil {
|
||||
return
|
||||
}
|
||||
res += (1 << i) - 1
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Reader) ReadSEGolomb() (res int, err error) {
|
||||
var b uint
|
||||
if b, err = g.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
if b%2 == 0 {
|
||||
res = -int(b >> 1)
|
||||
} else {
|
||||
res = int(b>>1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Reader) ReadByte() (byte, error) {
|
||||
return g.r.ReadByte()
|
||||
}
|
||||
|
||||
func (g *Reader) End() bool {
|
||||
// if only one bit in next byte left
|
||||
if g.shift == 0 && g.r.Len() == 1 {
|
||||
b, _ := g.r.ReadByte()
|
||||
_ = g.r.UnreadByte()
|
||||
return b == 0x80
|
||||
}
|
||||
if g.r.Len() == 0 {
|
||||
//panic("not implemented")
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package golomb
|
||||
|
||||
import "math/bits"
|
||||
|
||||
type Writer struct {
|
||||
buf []byte
|
||||
b byte // last byte
|
||||
i int // last byte index
|
||||
shift byte
|
||||
}
|
||||
|
||||
func NewWriter() *Writer {
|
||||
return &Writer{i: -1}
|
||||
}
|
||||
|
||||
func (g *Writer) WriteBit(b byte) {
|
||||
if g.shift == 0 {
|
||||
g.buf = append(g.buf, 0)
|
||||
g.b = 0
|
||||
g.i++
|
||||
g.shift = 7
|
||||
} else {
|
||||
g.shift--
|
||||
}
|
||||
g.b |= b << g.shift
|
||||
g.buf[g.i] = g.b
|
||||
}
|
||||
|
||||
func (g *Writer) WriteBits(b, n byte) {
|
||||
for i := n - 1; i != 255; i-- {
|
||||
g.WriteBit((b >> i) & 0b1)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Writer) WriteByte(b byte) {
|
||||
g.buf = append(g.buf, b)
|
||||
g.i++
|
||||
}
|
||||
|
||||
func (g *Writer) WriteUEGolomb(b byte) {
|
||||
b++
|
||||
n := uint8(bits.Len8(b))*2 - 1
|
||||
g.WriteBits(b, n)
|
||||
}
|
||||
|
||||
func (g *Writer) WriteSEGolomb(b int8) {
|
||||
if b > 0 {
|
||||
g.WriteUEGolomb(byte(b)*2 - 1)
|
||||
} else {
|
||||
g.WriteUEGolomb(byte(-b) * 2)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Writer) Bytes() []byte {
|
||||
return g.buf
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
NALUTypePFrame = 1
|
||||
NALUTypeIFrame = 5
|
||||
NALUTypeSPS = 7
|
||||
NALUTypePPS = 8
|
||||
|
||||
PayloadTypeAVC = 255
|
||||
)
|
||||
|
||||
func NALUType(b []byte) byte {
|
||||
return b[4] & 0x1F
|
||||
}
|
||||
|
||||
func EncodeAVC(raw []byte) (avc []byte) {
|
||||
avc = make([]byte, len(raw)+4)
|
||||
binary.BigEndian.PutUint32(avc, uint32(len(raw)))
|
||||
copy(avc[4:], raw)
|
||||
return
|
||||
}
|
||||
|
||||
func IsAVC(codec *streamer.Codec) bool {
|
||||
return codec.PayloadType == PayloadTypeAVC
|
||||
}
|
||||
|
||||
func GetParameterSet(fmtp string) (sps, pps []byte) {
|
||||
if fmtp == "" {
|
||||
return
|
||||
}
|
||||
|
||||
s := streamer.Between(fmtp, "sprop-parameter-sets=", ";")
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
i := strings.IndexByte(s, ',')
|
||||
if i < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
sps, _ = base64.StdEncoding.DecodeString(s[:i])
|
||||
pps, _ = base64.StdEncoding.DecodeString(s[i+1:])
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package h264
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// Payloader payloads H264 packets
|
||||
type Payloader struct {
|
||||
IsAVC bool
|
||||
spsNalu, ppsNalu []byte
|
||||
}
|
||||
|
||||
const (
|
||||
stapaNALUType = 24
|
||||
fuaNALUType = 28
|
||||
fubNALUType = 29
|
||||
spsNALUType = 7
|
||||
ppsNALUType = 8
|
||||
audNALUType = 9
|
||||
fillerNALUType = 12
|
||||
|
||||
fuaHeaderSize = 2
|
||||
//stapaHeaderSize = 1
|
||||
//stapaNALULengthSize = 2
|
||||
|
||||
naluTypeBitmask = 0x1F
|
||||
naluRefIdcBitmask = 0x60
|
||||
//fuStartBitmask = 0x80
|
||||
//fuEndBitmask = 0x40
|
||||
|
||||
outputStapAHeader = 0x78
|
||||
)
|
||||
|
||||
//func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} }
|
||||
|
||||
func emitNalus(nals []byte, isAVC bool, emit func([]byte)) {
|
||||
if !isAVC {
|
||||
nextInd := func(nalu []byte, start int) (indStart int, indLen int) {
|
||||
zeroCount := 0
|
||||
|
||||
for i, b := range nalu[start:] {
|
||||
if b == 0 {
|
||||
zeroCount++
|
||||
continue
|
||||
} else if b == 1 {
|
||||
if zeroCount >= 2 {
|
||||
return start + i - zeroCount, zeroCount + 1
|
||||
}
|
||||
}
|
||||
zeroCount = 0
|
||||
}
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
nextIndStart, nextIndLen := nextInd(nals, 0)
|
||||
if nextIndStart == -1 {
|
||||
emit(nals)
|
||||
} else {
|
||||
for nextIndStart != -1 {
|
||||
prevStart := nextIndStart + nextIndLen
|
||||
nextIndStart, nextIndLen = nextInd(nals, prevStart)
|
||||
if nextIndStart != -1 {
|
||||
emit(nals[prevStart:nextIndStart])
|
||||
} else {
|
||||
// Emit until end of stream, no end indicator found
|
||||
emit(nals[prevStart:])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for {
|
||||
end := 4 + binary.BigEndian.Uint32(nals)
|
||||
emit(nals[4:end])
|
||||
if int(end) >= len(nals) {
|
||||
break
|
||||
}
|
||||
nals = nals[end:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Payload fragments a H264 packet across one or more byte arrays
|
||||
func (p *Payloader) Payload(mtu uint16, payload []byte) [][]byte {
|
||||
var payloads [][]byte
|
||||
if len(payload) == 0 {
|
||||
return payloads
|
||||
}
|
||||
|
||||
emitNalus(payload, p.IsAVC, func(nalu []byte) {
|
||||
if len(nalu) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
naluType := nalu[0] & naluTypeBitmask
|
||||
naluRefIdc := nalu[0] & naluRefIdcBitmask
|
||||
|
||||
switch {
|
||||
case naluType == audNALUType || naluType == fillerNALUType:
|
||||
return
|
||||
case naluType == spsNALUType:
|
||||
p.spsNalu = nalu
|
||||
return
|
||||
case naluType == ppsNALUType:
|
||||
p.ppsNalu = nalu
|
||||
return
|
||||
case p.spsNalu != nil && p.ppsNalu != nil:
|
||||
// Pack current NALU with SPS and PPS as STAP-A
|
||||
spsLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(spsLen, uint16(len(p.spsNalu)))
|
||||
|
||||
ppsLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(ppsLen, uint16(len(p.ppsNalu)))
|
||||
|
||||
stapANalu := []byte{outputStapAHeader}
|
||||
stapANalu = append(stapANalu, spsLen...)
|
||||
stapANalu = append(stapANalu, p.spsNalu...)
|
||||
stapANalu = append(stapANalu, ppsLen...)
|
||||
stapANalu = append(stapANalu, p.ppsNalu...)
|
||||
if len(stapANalu) <= int(mtu) {
|
||||
out := make([]byte, len(stapANalu))
|
||||
copy(out, stapANalu)
|
||||
payloads = append(payloads, out)
|
||||
}
|
||||
|
||||
p.spsNalu = nil
|
||||
p.ppsNalu = nil
|
||||
}
|
||||
|
||||
// Single NALU
|
||||
if len(nalu) <= int(mtu) {
|
||||
out := make([]byte, len(nalu))
|
||||
copy(out, nalu)
|
||||
payloads = append(payloads, out)
|
||||
return
|
||||
}
|
||||
|
||||
// FU-A
|
||||
maxFragmentSize := int(mtu) - fuaHeaderSize
|
||||
|
||||
// The FU payload consists of fragments of the payload of the fragmented
|
||||
// NAL unit so that if the fragmentation unit payloads of consecutive
|
||||
// FUs are sequentially concatenated, the payload of the fragmented NAL
|
||||
// unit can be reconstructed. The NAL unit type octet of the fragmented
|
||||
// NAL unit is not included as such in the fragmentation unit payload,
|
||||
// but rather the information of the NAL unit type octet of the
|
||||
// fragmented NAL unit is conveyed in the F and NRI fields of the FU
|
||||
// indicator octet of the fragmentation unit and in the type field of
|
||||
// the FU header. An FU payload MAY have any number of octets and MAY
|
||||
// be empty.
|
||||
|
||||
naluData := nalu
|
||||
// According to the RFC, the first octet is skipped due to redundant information
|
||||
naluDataIndex := 1
|
||||
naluDataLength := len(nalu) - naluDataIndex
|
||||
naluDataRemaining := naluDataLength
|
||||
|
||||
if min(maxFragmentSize, naluDataRemaining) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for naluDataRemaining > 0 {
|
||||
currentFragmentSize := min(maxFragmentSize, naluDataRemaining)
|
||||
out := make([]byte, fuaHeaderSize+currentFragmentSize)
|
||||
|
||||
// +---------------+
|
||||
// |0|1|2|3|4|5|6|7|
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |F|NRI| Type |
|
||||
// +---------------+
|
||||
out[0] = fuaNALUType
|
||||
out[0] |= naluRefIdc
|
||||
|
||||
// +---------------+
|
||||
// |0|1|2|3|4|5|6|7|
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |S|E|R| Type |
|
||||
// +---------------+
|
||||
|
||||
out[1] = naluType
|
||||
if naluDataRemaining == naluDataLength {
|
||||
// Set start bit
|
||||
out[1] |= 1 << 7
|
||||
} else if naluDataRemaining-currentFragmentSize == 0 {
|
||||
// Set end bit
|
||||
out[1] |= 1 << 6
|
||||
}
|
||||
|
||||
copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize])
|
||||
payloads = append(payloads, out)
|
||||
|
||||
naluDataRemaining -= currentFragmentSize
|
||||
naluDataIndex += currentFragmentSize
|
||||
}
|
||||
})
|
||||
|
||||
return payloads
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/golomb"
|
||||
)
|
||||
|
||||
const PPSHeader = 0x68
|
||||
|
||||
// https://www.itu.int/rec/T-REC-H.264
|
||||
// 7.3.2.2 Picture parameter set RBSP syntax
|
||||
|
||||
type PPS struct{}
|
||||
|
||||
func (p *PPS) Marshal() []byte {
|
||||
w := golomb.NewWriter()
|
||||
|
||||
// this is typical PPS for most H264 cameras
|
||||
w.WriteByte(PPSHeader)
|
||||
w.WriteUEGolomb(0) // pic_parameter_set_id
|
||||
w.WriteUEGolomb(0) // seq_parameter_set_id
|
||||
w.WriteBit(1) // entropy_coding_mode_flag
|
||||
w.WriteBit(0) // bottom_field_pic_order_in_frame_present_flag
|
||||
w.WriteUEGolomb(0) // num_slice_groups_minus1
|
||||
w.WriteUEGolomb(0) // num_ref_idx_l0_default_active_minus1
|
||||
w.WriteUEGolomb(0) // num_ref_idx_l1_default_active_minus1
|
||||
w.WriteBit(0) // weighted_pred_flag
|
||||
w.WriteBits(0, 2) // weighted_bipred_idc
|
||||
w.WriteSEGolomb(0) // pic_init_qp_minus26
|
||||
w.WriteSEGolomb(0) // pic_init_qs_minus26
|
||||
w.WriteSEGolomb(0) // chroma_qp_index_offset
|
||||
w.WriteBit(1) // deblocking_filter_control_present_flag
|
||||
w.WriteBit(0) // constrained_intra_pred_flag
|
||||
w.WriteBit(0) // redundant_pic_cnt_present_flag
|
||||
|
||||
w.WriteBit(1) // rbsp_trailing_bits()
|
||||
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
func (p *PPS) Unmarshal(data []byte) (err error) {
|
||||
r := golomb.NewReader(data)
|
||||
|
||||
var b byte
|
||||
var u uint
|
||||
|
||||
if b, err = r.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
if b&0x1F != 8 {
|
||||
err = errors.New("not PPS data")
|
||||
return
|
||||
}
|
||||
|
||||
// pic_parameter_set_id
|
||||
if u, err = r.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// seq_parameter_set_id
|
||||
if u, err = r.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// entropy_coding_mode_flag
|
||||
if b, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// bottom_field_pic_order_in_frame_present_flag
|
||||
if b, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// num_slice_groups_minus1
|
||||
if u, err = r.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
if u > 0 {
|
||||
//panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
// num_ref_idx_l0_default_active_minus1
|
||||
if _, err = r.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// num_ref_idx_l1_default_active_minus1
|
||||
if _, err = r.ReadUEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// weighted_pred_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// weighted_bipred_idc
|
||||
if _, err = r.ReadBits(2); err != nil {
|
||||
return
|
||||
}
|
||||
// pic_init_qp_minus26
|
||||
if _, err = r.ReadSEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// pic_init_qs_minus26
|
||||
if _, err = r.ReadSEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// chroma_qp_index_offset
|
||||
if _, err = r.ReadSEGolomb(); err != nil {
|
||||
return
|
||||
}
|
||||
// deblocking_filter_control_present_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// constrained_intra_pred_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// redundant_pic_cnt_present_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !r.End() {
|
||||
//panic("not implemented")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/golomb"
|
||||
)
|
||||
|
||||
const firstByte = 0x67
|
||||
|
||||
// Google to "h264 specification pdf"
|
||||
// https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-201602-S!!PDF-E&type=items
|
||||
|
||||
type SPS struct {
|
||||
Profile string
|
||||
ProfileIDC uint8
|
||||
ProfileIOP uint8
|
||||
LevelIDC uint8
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
||||
func NewSPS(profile string, level uint8, width uint16, height uint16) *SPS {
|
||||
s := &SPS{
|
||||
Profile: profile, LevelIDC: level, Width: width, Height: height,
|
||||
}
|
||||
s.ProfileIDC, s.ProfileIOP = DecodeProfile(profile)
|
||||
return s
|
||||
}
|
||||
|
||||
// https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set
|
||||
|
||||
func (s *SPS) Marshal() []byte {
|
||||
w := golomb.NewWriter()
|
||||
|
||||
// this is typical SPS for most H264 cameras
|
||||
w.WriteByte(firstByte)
|
||||
w.WriteByte(s.ProfileIDC)
|
||||
w.WriteByte(s.ProfileIOP)
|
||||
w.WriteByte(s.LevelIDC)
|
||||
|
||||
w.WriteUEGolomb(0) // seq_parameter_set_id (0)
|
||||
w.WriteUEGolomb(0) // log2_max_frame_num_minus4 (depends)
|
||||
w.WriteUEGolomb(0) // pic_order_cnt_type (0 or 2)
|
||||
w.WriteUEGolomb(0) // log2_max_pic_order_cnt_lsb_minus4 (depends)
|
||||
w.WriteUEGolomb(1) // num_ref_frames (1)
|
||||
w.WriteBit(0) // gaps_in_frame_num_value_allowed_flag (0)
|
||||
|
||||
w.WriteUEGolomb(uint8(s.Width>>4) - 1) // pic_width_in_mbs_minus_1
|
||||
w.WriteUEGolomb(uint8(s.Height>>4) - 1) // pic_height_in_map_units_minus_1
|
||||
|
||||
w.WriteBit(1) // frame_mbs_only_flag (1)
|
||||
w.WriteBit(1) // direct_8x8_inference_flag (1)
|
||||
w.WriteBit(0) // frame_cropping_flag (0 is OK)
|
||||
w.WriteBit(0) // vui_prameters_present_flag (0 is OK)
|
||||
w.WriteBit(1) // rbsp_stop_one_bit
|
||||
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
func (s *SPS) Unmarshal(data []byte) (err error) {
|
||||
r := golomb.NewReader(data)
|
||||
|
||||
var b byte
|
||||
var u uint
|
||||
|
||||
if b, err = r.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
if b&0x1F != 7 {
|
||||
err = errors.New("not SPS data")
|
||||
return
|
||||
}
|
||||
|
||||
if s.ProfileIDC, err = r.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
if s.ProfileIOP, err = r.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
if s.LevelIDC, err = r.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Profile = EncodeProfile(s.ProfileIDC, s.ProfileIOP)
|
||||
|
||||
u, err = r.ReadUEGolomb() // seq_parameter_set_id
|
||||
|
||||
if s.ProfileIDC == 100 || s.ProfileIDC == 110 || s.ProfileIDC == 122 ||
|
||||
s.ProfileIDC == 244 || s.ProfileIDC == 44 || s.ProfileIDC == 83 ||
|
||||
s.ProfileIDC == 86 || s.ProfileIDC == 118 || s.ProfileIDC == 128 ||
|
||||
s.ProfileIDC == 138 || s.ProfileIDC == 139 || s.ProfileIDC == 134 ||
|
||||
s.ProfileIDC == 135 {
|
||||
var n byte
|
||||
|
||||
u, err = r.ReadUEGolomb() // chroma_format_idc
|
||||
if u == 3 {
|
||||
b, err = r.ReadBit() // separate_colour_plane_flag
|
||||
n = 12
|
||||
} else {
|
||||
n = 8
|
||||
}
|
||||
|
||||
u, err = r.ReadUEGolomb() // bit_depth_luma_minus8
|
||||
u, err = r.ReadUEGolomb() // bit_depth_chroma_minus8
|
||||
b, err = r.ReadBit() // qpprime_y_zero_transform_bypass_flag
|
||||
|
||||
b, err = r.ReadBit() // seq_scaling_matrix_present_flag
|
||||
if b > 0 {
|
||||
for i := byte(0); i < n; i++ {
|
||||
b, err = r.ReadBit() // seq_scaling_list_present_flag[i]
|
||||
if b > 0 {
|
||||
panic("not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u, err = r.ReadUEGolomb() // log2_max_frame_num_minus4
|
||||
|
||||
u, err = r.ReadUEGolomb() // pic_order_cnt_type
|
||||
switch u {
|
||||
case 0:
|
||||
u, err = r.ReadUEGolomb() // log2_max_pic_order_cnt_lsb_minus4
|
||||
case 1:
|
||||
b, err = r.ReadBit() // delta_pic_order_always_zero_flag
|
||||
_, err = r.ReadSEGolomb() // offset_for_non_ref_pic
|
||||
_, err = r.ReadSEGolomb() // offset_for_top_to_bottom_field
|
||||
u, err = r.ReadUEGolomb() // num_ref_frames_in_pic_order_cnt_cycle
|
||||
for i := byte(0); i < b; i++ {
|
||||
_, err = r.ReadSEGolomb() // offset_for_ref_frame[i]
|
||||
}
|
||||
}
|
||||
|
||||
u, err = r.ReadUEGolomb() // num_ref_frames
|
||||
b, err = r.ReadBit() // gaps_in_frame_num_value_allowed_flag
|
||||
|
||||
u, err = r.ReadUEGolomb() // pic_width_in_mbs_minus_1
|
||||
s.Width = uint16(u+1) << 4
|
||||
u, err = r.ReadUEGolomb() // pic_height_in_map_units_minus_1
|
||||
s.Height = uint16(u+1) << 4
|
||||
|
||||
b, err = r.ReadBit() // frame_mbs_only_flag
|
||||
if b == 0 {
|
||||
_, err = r.ReadBit()
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // direct_8x8_inference_flag
|
||||
|
||||
b, err = r.ReadBit() // frame_cropping_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadUEGolomb() // frame_crop_left_offset
|
||||
s.Width -= uint16(u) << 1
|
||||
u, err = r.ReadUEGolomb() // frame_crop_right_offset
|
||||
s.Width -= uint16(u) << 1
|
||||
u, err = r.ReadUEGolomb() // frame_crop_top_offset
|
||||
s.Height -= uint16(u) << 1
|
||||
u, err = r.ReadUEGolomb() // frame_crop_bottom_offset
|
||||
s.Height -= uint16(u) << 1
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // vui_prameters_present_flag
|
||||
if b > 0 {
|
||||
b, err = r.ReadBit() // vui_prameters_present_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadBits(8) // aspect_ratio_idc
|
||||
if b == 255 {
|
||||
u, err = r.ReadBits(16) // sar_width
|
||||
u, err = r.ReadBits(16) // sar_height
|
||||
}
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // overscan_info_present_flag
|
||||
if b > 0 {
|
||||
b, err = r.ReadBit() // overscan_appropriate_flag
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // video_signal_type_present_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadBits(3) // video_format
|
||||
b, err = r.ReadBit() // video_full_range_flag
|
||||
|
||||
b, err = r.ReadBit() // colour_description_present_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadBits(8) // colour_primaries
|
||||
u, err = r.ReadBits(8) // transfer_characteristics
|
||||
u, err = r.ReadBits(8) // matrix_coefficients
|
||||
}
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // chroma_loc_info_present_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadUEGolomb() // chroma_sample_loc_type_top_field
|
||||
u, err = r.ReadUEGolomb() // chroma_sample_loc_type_bottom_field
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // timing_info_present_flag
|
||||
if b > 0 {
|
||||
u, err = r.ReadBits(32) // num_units_in_tick
|
||||
u, err = r.ReadBits(32) // time_scale
|
||||
b, err = r.ReadBit() // fixed_frame_rate_flag
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // nal_hrd_parameters_present_flag
|
||||
if b > 0 {
|
||||
//panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // vcl_hrd_parameters_present_flag
|
||||
if b > 0 {
|
||||
//panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
// if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag)
|
||||
// b, err = r.ReadBit() // low_delay_hrd_flag
|
||||
|
||||
b, err = r.ReadBit() // pic_struct_present_flag
|
||||
|
||||
b, err = r.ReadBit() // bitstream_restriction_flag
|
||||
if b > 0 {
|
||||
b, err = r.ReadBit() // motion_vectors_over_pic_boundaries_flag
|
||||
u, err = r.ReadUEGolomb() // max_bytes_per_pic_denom
|
||||
u, err = r.ReadUEGolomb() // max_bits_per_mb_denom
|
||||
u, err = r.ReadUEGolomb() // log2_max_mv_length_horizontal
|
||||
u, err = r.ReadUEGolomb() // log2_max_mv_length_vertical
|
||||
u, err = r.ReadUEGolomb() // max_num_reorder_frames
|
||||
u, err = r.ReadUEGolomb() // max_dec_frame_buffering
|
||||
}
|
||||
}
|
||||
|
||||
b, err = r.ReadBit() // rbsp_stop_one_bit
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func EncodeProfile(idc, iop byte) string {
|
||||
// https://datatracker.ietf.org/doc/html/rfc6184#page-41
|
||||
switch {
|
||||
// 4240xx 42C0xx 42E0xx
|
||||
case idc == 0x42 && iop&0b01001111 == 0b01000000:
|
||||
return "CB"
|
||||
case idc == 0x4D && iop&0b10001111 == 0b10000000:
|
||||
return "CB"
|
||||
case idc == 0x58 && iop&0b11001111 == 0b11000000:
|
||||
return "CB"
|
||||
// 4200xx
|
||||
case idc == 0x42 && iop&0b01001111 == 0:
|
||||
return "B"
|
||||
case idc == 0x58 && iop&0b11001111 == 0b10000000:
|
||||
return "B"
|
||||
// 4d40xx
|
||||
case idc == 0x4D && iop&0b10101111 == 0:
|
||||
return "M"
|
||||
case idc == 0x58 && iop&0b11001111 == 0:
|
||||
return "E"
|
||||
case idc == 0x64 && iop == 0:
|
||||
return "H"
|
||||
case idc == 0x6E && iop == 0:
|
||||
return "H10"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func DecodeProfile(profile string) (idc, iop byte) {
|
||||
switch profile {
|
||||
case "CB":
|
||||
return 0x42, 0b01000000
|
||||
case "B":
|
||||
return 0x42, 0 // 66
|
||||
case "M":
|
||||
return 0x4D, 0 // 77
|
||||
case "E":
|
||||
return 0x58, 0 // 88
|
||||
case "H":
|
||||
return 0x64, 0
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalSPS(t *testing.T) {
|
||||
raw := []byte{0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2}
|
||||
s := SPS{}
|
||||
if err := s.Unmarshal(raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw2 := s.Marshal()
|
||||
if bytes.Compare(raw, raw2) != 0 {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalPPS(t *testing.T) {
|
||||
raw := []byte{0x68, 0xce, 0x38, 0x80}
|
||||
p := PPS{}
|
||||
if err := p.Unmarshal(raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw2 := p.Marshal()
|
||||
if bytes.Compare(raw, raw2) != 0 {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalPPS2(t *testing.T) {
|
||||
raw := []byte{72, 238, 60, 128}
|
||||
p := PPS{}
|
||||
if err := p.Unmarshal(raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw2 := p.Marshal()
|
||||
if bytes.Compare(raw, raw2) != 0 {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafari(t *testing.T) {
|
||||
// CB66, L3.1: chrome, edge, safari, android chrome
|
||||
s := EncodeProfile(0x42, 0xE0)
|
||||
t.Logf("Profile: %s, Level: %d", s, 0x1F)
|
||||
|
||||
// B66, L3.1: chrome, edge
|
||||
s = EncodeProfile(0x42, 0x00)
|
||||
t.Logf("Profile: %s, Level: %d", s, 0x1F)
|
||||
|
||||
// M77, L3.1: chrome, edge
|
||||
s = EncodeProfile(0x4D, 0x00)
|
||||
t.Logf("Profile: %s, Level: %d", s, 0x1F)
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
)
|
||||
|
||||
const RTPPacketVersionAVC = 0
|
||||
|
||||
func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
|
||||
depack := &codecs.H264Packet{IsAVC: true}
|
||||
|
||||
sps, pps := GetParameterSet(track.Codec.FmtpLine)
|
||||
sps = EncodeAVC(sps)
|
||||
pps = EncodeAVC(pps)
|
||||
|
||||
var buffer []byte
|
||||
|
||||
return func(push streamer.WriterFunc) streamer.WriterFunc {
|
||||
return func(packet *rtp.Packet) error {
|
||||
//println(packet.SequenceNumber, packet.Payload[0]&0x1F, packet.Payload[0], packet.Payload[1], packet.Marker, packet.Timestamp)
|
||||
|
||||
data, err := depack.Unmarshal(packet.Payload)
|
||||
if len(data) == 0 || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
naluType := NALUType(data)
|
||||
//println(naluType, len(data))
|
||||
|
||||
switch naluType {
|
||||
case NALUTypeSPS:
|
||||
//println("new SPS")
|
||||
sps = data
|
||||
return nil
|
||||
case NALUTypePPS:
|
||||
//println("new PPS")
|
||||
pps = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// ffmpeg with `-tune zerolatency` enable option `-x264opts sliced-threads=1`
|
||||
// and every NALU will be sliced to multiple NALUs
|
||||
if !packet.Marker {
|
||||
buffer = append(buffer, data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
if buffer != nil {
|
||||
buffer = append(buffer, data...)
|
||||
data = buffer
|
||||
buffer = nil
|
||||
}
|
||||
|
||||
var clone rtp.Packet
|
||||
|
||||
if naluType == NALUTypeIFrame {
|
||||
clone = *packet
|
||||
clone.Version = RTPPacketVersionAVC
|
||||
clone.Payload = sps
|
||||
if err = push(&clone); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clone = *packet
|
||||
clone.Version = RTPPacketVersionAVC
|
||||
clone.Payload = pps
|
||||
if err = push(&clone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
clone = *packet
|
||||
clone.Version = RTPPacketVersionAVC
|
||||
clone.Payload = data
|
||||
return push(&clone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RTPPay(mtu uint16) streamer.WrapperFunc {
|
||||
payloader := &Payloader{IsAVC: true}
|
||||
sequencer := rtp.NewRandomSequencer()
|
||||
mtu -= 12 // rtp.Header size
|
||||
|
||||
return func(push streamer.WriterFunc) streamer.WriterFunc {
|
||||
return func(packet *rtp.Packet) error {
|
||||
if packet.Version == RTPPacketVersionAVC {
|
||||
payloads := payloader.Payload(mtu, packet.Payload)
|
||||
for i, payload := range payloads {
|
||||
clone := rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: i == len(payloads)-1,
|
||||
//PayloadType: packet.PayloadType,
|
||||
SequenceNumber: sequencer.NextSequenceNumber(),
|
||||
Timestamp: packet.Timestamp,
|
||||
//SSRC: packet.SSRC,
|
||||
},
|
||||
Payload: payload,
|
||||
}
|
||||
if err := push(&clone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return push(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user