Add SPS parser and AVC/HVC conf encoders
This commit is contained in:
+25
-12
@@ -1,10 +1,12 @@
|
||||
package bits
|
||||
|
||||
type Reader struct {
|
||||
buf []byte // packets buffer
|
||||
byte byte
|
||||
bits byte
|
||||
pos int
|
||||
EOF bool // if end of buffer raised during reading
|
||||
|
||||
buf []byte // total buf
|
||||
byte byte // current byte
|
||||
bits byte // bits left in byte
|
||||
pos int // current pos in buf
|
||||
}
|
||||
|
||||
func NewReader(b []byte) *Reader {
|
||||
@@ -14,6 +16,11 @@ func NewReader(b []byte) *Reader {
|
||||
//goland:noinspection GoStandardMethods
|
||||
func (r *Reader) ReadByte() byte {
|
||||
if r.bits == 0 {
|
||||
if r.pos >= len(r.buf) {
|
||||
r.EOF = true
|
||||
return 0
|
||||
}
|
||||
|
||||
b := r.buf[r.pos]
|
||||
r.pos++
|
||||
return b
|
||||
@@ -54,14 +61,20 @@ func (r *Reader) ReadBits16(n byte) (res uint16) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) SkipBits(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
if r.bits == 0 {
|
||||
r.byte = r.buf[r.pos]
|
||||
r.pos++
|
||||
r.bits = 7
|
||||
} else {
|
||||
r.bits--
|
||||
func (r *Reader) ReadUEGolomb() uint32 {
|
||||
var size byte
|
||||
for size = 0; size < 32; size++ {
|
||||
if b := r.ReadBit(); b != 0 || r.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return r.ReadBits(size) + (1 << size) - 1
|
||||
}
|
||||
|
||||
func (r *Reader) ReadSEGolomb() int32 {
|
||||
if b := r.ReadUEGolomb(); b%2 == 0 {
|
||||
return -int32(b >> 1)
|
||||
} else {
|
||||
return int32(b >> 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,30 @@ func DecodeConfig(conf []byte) (profile []byte, sps []byte, pps []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func EncodeConfig(sps, pps []byte) []byte {
|
||||
spsSize := uint16(len(sps))
|
||||
ppsSize := uint16(len(pps))
|
||||
|
||||
buf := make([]byte, 5+3+spsSize+3+ppsSize)
|
||||
buf[0] = 1
|
||||
copy(buf[1:], sps[1:4]) // profile
|
||||
buf[4] = 3 | 0xFC // ? LengthSizeMinusOne
|
||||
|
||||
b := buf[5:]
|
||||
_ = b[3]
|
||||
b[0] = 1 | 0xE0 // ? sps count
|
||||
binary.BigEndian.PutUint16(b[1:], spsSize)
|
||||
copy(b[3:], sps)
|
||||
|
||||
b = buf[5+3+spsSize:]
|
||||
_ = b[3]
|
||||
b[0] = 1 // pps count
|
||||
binary.BigEndian.PutUint16(b[1:], ppsSize)
|
||||
copy(b[3:], pps)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func ConfigToCodec(conf []byte) *core.Codec {
|
||||
buf := bytes.NewBufferString("packetization-mode=1")
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ import (
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0"
|
||||
b, err := hex.DecodeString(s)
|
||||
src, err := hex.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
profile, sps, pps := DecodeConfig(b)
|
||||
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,164 @@
|
||||
package avc
|
||||
|
||||
import "github.com/AlexxIT/go2rtc/pkg/bits"
|
||||
|
||||
// http://www.itu.int/rec/T-REC-H.264
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/common_video/h264/sps_parser.cc
|
||||
|
||||
//goland:noinspection GoSnakeCaseUsage
|
||||
type SPS struct {
|
||||
profile_idc uint8
|
||||
profile_iop uint8
|
||||
level_idc uint8
|
||||
|
||||
seq_parameter_set_id uint32
|
||||
|
||||
chroma_format_idc uint32
|
||||
separate_colour_plane_flag byte
|
||||
bit_depth_luma_minus8 uint32
|
||||
bit_depth_chroma_minus8 uint32
|
||||
qpprime_y_zero_transform_bypass_flag byte
|
||||
seq_scaling_matrix_present_flag byte
|
||||
|
||||
log2_max_frame_num_minus4 uint32
|
||||
pic_order_cnt_type uint32
|
||||
log2_max_pic_order_cnt_lsb_minus4 uint32
|
||||
delta_pic_order_always_zero_flag byte
|
||||
offset_for_non_ref_pic int32
|
||||
offset_for_top_to_bottom_field int32
|
||||
num_ref_frames_in_pic_order_cnt_cycle uint32
|
||||
num_ref_frames uint32
|
||||
gaps_in_frame_num_value_allowed_flag byte
|
||||
|
||||
pic_width_in_mbs_minus_1 uint32
|
||||
pic_height_in_map_units_minus_1 uint32
|
||||
frame_mbs_only_flag byte
|
||||
mb_adaptive_frame_field_flag byte
|
||||
direct_8x8_inference_flag byte
|
||||
|
||||
frame_cropping_flag byte
|
||||
frame_crop_left_offset uint32
|
||||
frame_crop_right_offset uint32
|
||||
frame_crop_top_offset uint32
|
||||
frame_crop_bottom_offset uint32
|
||||
|
||||
vui_parameters_present_flag byte
|
||||
aspect_ratio_info_present_flag byte
|
||||
aspect_ratio_idc uint32
|
||||
sar_width uint32
|
||||
sar_height uint32
|
||||
}
|
||||
|
||||
func DecodeSPS(sps []byte) *SPS {
|
||||
r := bits.NewReader(sps)
|
||||
|
||||
hdr := r.ReadByte()
|
||||
if hdr&0x1F != 7 {
|
||||
return nil
|
||||
}
|
||||
|
||||
s := &SPS{
|
||||
profile_idc: r.ReadByte(),
|
||||
profile_iop: r.ReadByte(),
|
||||
level_idc: r.ReadByte(),
|
||||
seq_parameter_set_id: r.ReadUEGolomb(),
|
||||
}
|
||||
|
||||
switch s.profile_idc {
|
||||
case 100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135:
|
||||
n := byte(8)
|
||||
|
||||
s.chroma_format_idc = r.ReadUEGolomb()
|
||||
if s.chroma_format_idc == 3 {
|
||||
s.separate_colour_plane_flag = r.ReadBit()
|
||||
n = 12
|
||||
}
|
||||
|
||||
s.bit_depth_luma_minus8 = r.ReadUEGolomb()
|
||||
s.bit_depth_chroma_minus8 = r.ReadUEGolomb()
|
||||
s.qpprime_y_zero_transform_bypass_flag = r.ReadBit()
|
||||
|
||||
s.seq_scaling_matrix_present_flag = r.ReadBit()
|
||||
if s.seq_scaling_matrix_present_flag != 0 {
|
||||
for i := byte(0); i < n; i++ {
|
||||
ssl := r.ReadBit() // seq_scaling_list_present_flag[i]
|
||||
if ssl != 0 {
|
||||
return nil // not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.log2_max_frame_num_minus4 = r.ReadUEGolomb()
|
||||
|
||||
s.pic_order_cnt_type = r.ReadUEGolomb()
|
||||
switch s.pic_order_cnt_type {
|
||||
case 0:
|
||||
s.log2_max_pic_order_cnt_lsb_minus4 = r.ReadUEGolomb()
|
||||
case 1:
|
||||
s.delta_pic_order_always_zero_flag = r.ReadBit()
|
||||
s.offset_for_non_ref_pic = r.ReadSEGolomb()
|
||||
s.offset_for_top_to_bottom_field = r.ReadSEGolomb()
|
||||
|
||||
s.num_ref_frames_in_pic_order_cnt_cycle = r.ReadUEGolomb()
|
||||
for i := uint32(0); i < s.num_ref_frames_in_pic_order_cnt_cycle; i++ {
|
||||
_ = r.ReadSEGolomb() // offset_for_ref_frame[i]
|
||||
}
|
||||
}
|
||||
|
||||
s.num_ref_frames = r.ReadUEGolomb()
|
||||
s.gaps_in_frame_num_value_allowed_flag = r.ReadBit()
|
||||
|
||||
s.pic_width_in_mbs_minus_1 = r.ReadUEGolomb()
|
||||
s.pic_height_in_map_units_minus_1 = r.ReadUEGolomb()
|
||||
|
||||
s.frame_mbs_only_flag = r.ReadBit()
|
||||
if s.frame_mbs_only_flag == 0 {
|
||||
s.mb_adaptive_frame_field_flag = r.ReadBit()
|
||||
}
|
||||
|
||||
s.direct_8x8_inference_flag = r.ReadBit()
|
||||
|
||||
s.frame_cropping_flag = r.ReadBit()
|
||||
if s.frame_cropping_flag != 0 {
|
||||
s.frame_crop_left_offset = r.ReadUEGolomb()
|
||||
s.frame_crop_right_offset = r.ReadUEGolomb()
|
||||
s.frame_crop_top_offset = r.ReadUEGolomb()
|
||||
s.frame_crop_bottom_offset = r.ReadUEGolomb()
|
||||
}
|
||||
|
||||
s.vui_parameters_present_flag = r.ReadBit()
|
||||
if s.vui_parameters_present_flag != 0 {
|
||||
s.aspect_ratio_info_present_flag = r.ReadBit()
|
||||
if s.aspect_ratio_info_present_flag != 0 {
|
||||
s.aspect_ratio_idc = r.ReadBits(8)
|
||||
if s.aspect_ratio_idc == 255 {
|
||||
s.sar_width = r.ReadBits(16)
|
||||
s.sar_height = r.ReadBits(16)
|
||||
}
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
if r.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package avc
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeSPS(t *testing.T) {
|
||||
s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
sps := DecodeSPS(b)
|
||||
require.Equal(t, uint16(2560), sps.Width())
|
||||
require.Equal(t, uint16(1920), sps.Heigth())
|
||||
|
||||
s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff
|
||||
b, err = base64.StdEncoding.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
sps = DecodeSPS(b)
|
||||
require.Equal(t, uint16(1920), sps.Width())
|
||||
require.Equal(t, uint16(1080), sps.Heigth())
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
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,39 @@
|
||||
package hvc
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func EncodeConfig(vps, sps, pps []byte) []byte {
|
||||
vpsSize := uint16(len(vps))
|
||||
spsSize := uint16(len(sps))
|
||||
ppsSize := uint16(len(pps))
|
||||
|
||||
buf := make([]byte, 23+5+vpsSize+5+spsSize+5+ppsSize)
|
||||
|
||||
buf[0] = 1
|
||||
copy(buf[1:], sps[3:6]) // profile
|
||||
buf[21] = 3 // ?
|
||||
buf[22] = 3 // ?
|
||||
|
||||
b := buf[23:]
|
||||
_ = b[5]
|
||||
b[0] = (vps[0] >> 1) & 0x3F
|
||||
binary.BigEndian.PutUint16(b[1:], 1) // VPS count
|
||||
binary.BigEndian.PutUint16(b[3:], vpsSize)
|
||||
copy(b[5:], vps)
|
||||
|
||||
b = buf[23+5+vpsSize:]
|
||||
_ = b[5]
|
||||
b[0] = (sps[0] >> 1) & 0x3F
|
||||
binary.BigEndian.PutUint16(b[1:], 1) // SPS count
|
||||
binary.BigEndian.PutUint16(b[3:], spsSize)
|
||||
copy(b[5:], sps)
|
||||
|
||||
b = buf[23+5+vpsSize+5+spsSize:]
|
||||
_ = b[5]
|
||||
b[0] = (pps[0] >> 1) & 0x3F
|
||||
binary.BigEndian.PutUint16(b[1:], 1) // PPS count
|
||||
binary.BigEndian.PutUint16(b[3:], ppsSize)
|
||||
copy(b[5:], pps)
|
||||
|
||||
return buf
|
||||
}
|
||||
+8
-22
@@ -2,20 +2,20 @@ package ivideon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/fmp4/fmp4io"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pion/rtp"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
|
||||
"github.com/deepch/vdk/format/fmp4/fmp4io"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type State byte
|
||||
@@ -197,29 +197,15 @@ func (c *Client) getTracks() error {
|
||||
continue
|
||||
}
|
||||
|
||||
codec := &core.Codec{
|
||||
Name: core.CodecH264,
|
||||
ClockRate: 90000,
|
||||
FmtpLine: "profile-level-id=" + msg.CodecString[i+1:],
|
||||
PayloadType: core.PayloadTypeRAW,
|
||||
}
|
||||
|
||||
i = bytes.Index(msg.Data, []byte("avcC")) - 4
|
||||
if i < 0 {
|
||||
return fmt.Errorf("wrong AVC: %s", msg.Data)
|
||||
return fmt.Errorf("ivideon: wrong AVC: %s", msg.Data)
|
||||
}
|
||||
|
||||
avccLen := binary.BigEndian.Uint32(msg.Data[i:])
|
||||
data = msg.Data[i+8 : i+int(avccLen)]
|
||||
|
||||
record := h264parser.AVCDecoderConfRecord{}
|
||||
if _, err = record.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
codec.FmtpLine += ";sprop-parameter-sets=" +
|
||||
base64.StdEncoding.EncodeToString(record.SPS[0]) + "," +
|
||||
base64.StdEncoding.EncodeToString(record.PPS[0])
|
||||
codec := avc.ConfigToCodec(data)
|
||||
|
||||
media := &core.Media{
|
||||
Kind: core.KindVideo,
|
||||
|
||||
+11
-14
@@ -2,13 +2,14 @@ package mp4
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h265/hvc"
|
||||
"github.com/AlexxIT/go2rtc/pkg/iso"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/codec/h265parser"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
@@ -73,15 +74,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
|
||||
pps = []byte{0x68, 0xce, 0x38, 0x80}
|
||||
}
|
||||
|
||||
codecData, err := h264parser.NewCodecDataFromSPSAndPPS(sps, pps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
s := avc.DecodeSPS(sps)
|
||||
if s == nil {
|
||||
return nil, errors.New("mp4: can't parse SPS")
|
||||
}
|
||||
|
||||
mv.WriteVideoTrack(
|
||||
uint32(i+1), codec.Name, codec.ClockRate,
|
||||
uint16(codecData.Width()), uint16(codecData.Height()),
|
||||
codecData.AVCDecoderConfRecordBytes(),
|
||||
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), avc.EncodeConfig(sps, pps),
|
||||
)
|
||||
|
||||
case core.CodecH265:
|
||||
@@ -97,15 +96,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
|
||||
pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90}
|
||||
}
|
||||
|
||||
codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
s := avc.DecodeSPS(sps)
|
||||
if s == nil {
|
||||
return nil, errors.New("mp4: can't parse SPS")
|
||||
}
|
||||
|
||||
mv.WriteVideoTrack(
|
||||
uint32(i+1), codec.Name, codec.ClockRate,
|
||||
uint16(codecData.Width()), uint16(codecData.Height()),
|
||||
codecData.AVCDecoderConfRecordBytes(),
|
||||
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), hvc.EncodeConfig(vps, sps, pps),
|
||||
)
|
||||
|
||||
case core.CodecAAC:
|
||||
|
||||
Reference in New Issue
Block a user