Add SPS parser and AVC/HVC conf encoders
This commit is contained in:
+25
-12
@@ -1,10 +1,12 @@
|
|||||||
package bits
|
package bits
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
buf []byte // packets buffer
|
EOF bool // if end of buffer raised during reading
|
||||||
byte byte
|
|
||||||
bits byte
|
buf []byte // total buf
|
||||||
pos int
|
byte byte // current byte
|
||||||
|
bits byte // bits left in byte
|
||||||
|
pos int // current pos in buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(b []byte) *Reader {
|
func NewReader(b []byte) *Reader {
|
||||||
@@ -14,6 +16,11 @@ func NewReader(b []byte) *Reader {
|
|||||||
//goland:noinspection GoStandardMethods
|
//goland:noinspection GoStandardMethods
|
||||||
func (r *Reader) ReadByte() byte {
|
func (r *Reader) ReadByte() byte {
|
||||||
if r.bits == 0 {
|
if r.bits == 0 {
|
||||||
|
if r.pos >= len(r.buf) {
|
||||||
|
r.EOF = true
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
b := r.buf[r.pos]
|
b := r.buf[r.pos]
|
||||||
r.pos++
|
r.pos++
|
||||||
return b
|
return b
|
||||||
@@ -54,14 +61,20 @@ func (r *Reader) ReadBits16(n byte) (res uint16) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) SkipBits(n int) {
|
func (r *Reader) ReadUEGolomb() uint32 {
|
||||||
for i := 0; i < n; i++ {
|
var size byte
|
||||||
if r.bits == 0 {
|
for size = 0; size < 32; size++ {
|
||||||
r.byte = r.buf[r.pos]
|
if b := r.ReadBit(); b != 0 || r.EOF {
|
||||||
r.pos++
|
break
|
||||||
r.bits = 7
|
|
||||||
} else {
|
|
||||||
r.bits--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
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 {
|
func ConfigToCodec(conf []byte) *core.Codec {
|
||||||
buf := bytes.NewBufferString("packetization-mode=1")
|
buf := bytes.NewBufferString("packetization-mode=1")
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import (
|
|||||||
|
|
||||||
func TestDecodeConfig(t *testing.T) {
|
func TestDecodeConfig(t *testing.T) {
|
||||||
s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0"
|
s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0"
|
||||||
b, err := hex.DecodeString(s)
|
src, err := hex.DecodeString(s)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
profile, sps, pps := DecodeConfig(b)
|
profile, sps, pps := DecodeConfig(src)
|
||||||
require.NotNil(t, profile)
|
require.NotNil(t, profile)
|
||||||
require.NotNil(t, sps)
|
require.NotNil(t, sps)
|
||||||
require.NotNil(t, pps)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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
|
type State byte
|
||||||
@@ -197,29 +197,15 @@ func (c *Client) getTracks() error {
|
|||||||
continue
|
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
|
i = bytes.Index(msg.Data, []byte("avcC")) - 4
|
||||||
if i < 0 {
|
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:])
|
avccLen := binary.BigEndian.Uint32(msg.Data[i:])
|
||||||
data = msg.Data[i+8 : i+int(avccLen)]
|
data = msg.Data[i+8 : i+int(avccLen)]
|
||||||
|
|
||||||
record := h264parser.AVCDecoderConfRecord{}
|
codec := avc.ConfigToCodec(data)
|
||||||
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])
|
|
||||||
|
|
||||||
media := &core.Media{
|
media := &core.Media{
|
||||||
Kind: core.KindVideo,
|
Kind: core.KindVideo,
|
||||||
|
|||||||
+11
-14
@@ -2,13 +2,14 @@ package mp4
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"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"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h265/hvc"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/iso"
|
"github.com/AlexxIT/go2rtc/pkg/iso"
|
||||||
"github.com/deepch/vdk/codec/h264parser"
|
|
||||||
"github.com/deepch/vdk/codec/h265parser"
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,15 +74,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
|
|||||||
pps = []byte{0x68, 0xce, 0x38, 0x80}
|
pps = []byte{0x68, 0xce, 0x38, 0x80}
|
||||||
}
|
}
|
||||||
|
|
||||||
codecData, err := h264parser.NewCodecDataFromSPSAndPPS(sps, pps)
|
s := avc.DecodeSPS(sps)
|
||||||
if err != nil {
|
if s == nil {
|
||||||
return nil, err
|
return nil, errors.New("mp4: can't parse SPS")
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.WriteVideoTrack(
|
mv.WriteVideoTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate,
|
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), avc.EncodeConfig(sps, pps),
|
||||||
uint16(codecData.Width()), uint16(codecData.Height()),
|
|
||||||
codecData.AVCDecoderConfRecordBytes(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case core.CodecH265:
|
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}
|
pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90}
|
||||||
}
|
}
|
||||||
|
|
||||||
codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps)
|
s := avc.DecodeSPS(sps)
|
||||||
if err != nil {
|
if s == nil {
|
||||||
return nil, err
|
return nil, errors.New("mp4: can't parse SPS")
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.WriteVideoTrack(
|
mv.WriteVideoTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate,
|
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), hvc.EncodeConfig(vps, sps, pps),
|
||||||
uint16(codecData.Width()), uint16(codecData.Height()),
|
|
||||||
codecData.AVCDecoderConfRecordBytes(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case core.CodecAAC:
|
case core.CodecAAC:
|
||||||
|
|||||||
Reference in New Issue
Block a user