diff --git a/pkg/bits/reader.go b/pkg/bits/reader.go index cea80353..a5bf78bb 100644 --- a/pkg/bits/reader.go +++ b/pkg/bits/reader.go @@ -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) + } } diff --git a/pkg/h264/avc/avc.go b/pkg/h264/avc/avc.go index 8b43537d..6a65181c 100644 --- a/pkg/h264/avc/avc.go +++ b/pkg/h264/avc/avc.go @@ -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") diff --git a/pkg/h264/avc/avc_test.go b/pkg/h264/avc/avc_test.go index eb1019ac..c81e2867 100644 --- a/pkg/h264/avc/avc_test.go +++ b/pkg/h264/avc/avc_test.go @@ -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) } diff --git a/pkg/h264/avc/sps.go b/pkg/h264/avc/sps.go new file mode 100644 index 00000000..5271c110 --- /dev/null +++ b/pkg/h264/avc/sps.go @@ -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) +} diff --git a/pkg/h264/avc/sps_test.go b/pkg/h264/avc/sps_test.go new file mode 100644 index 00000000..2a33e7c5 --- /dev/null +++ b/pkg/h264/avc/sps_test.go @@ -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()) +} diff --git a/pkg/h264/golomb/golomb_reader.go b/pkg/h264/golomb/golomb_reader.go deleted file mode 100644 index 80167cd7..00000000 --- a/pkg/h264/golomb/golomb_reader.go +++ /dev/null @@ -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 -} diff --git a/pkg/h264/golomb/golomb_writer.go b/pkg/h264/golomb/golomb_writer.go deleted file mode 100644 index fd2f2ad0..00000000 --- a/pkg/h264/golomb/golomb_writer.go +++ /dev/null @@ -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 -} diff --git a/pkg/h264/ps/pps.go b/pkg/h264/ps/pps.go deleted file mode 100644 index 9b91bcae..00000000 --- a/pkg/h264/ps/pps.go +++ /dev/null @@ -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 -} diff --git a/pkg/h264/ps/sps.go b/pkg/h264/ps/sps.go deleted file mode 100644 index 7ea1e858..00000000 --- a/pkg/h264/ps/sps.go +++ /dev/null @@ -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 -} diff --git a/pkg/h265/hvc/hvc.go b/pkg/h265/hvc/hvc.go new file mode 100644 index 00000000..06d764fe --- /dev/null +++ b/pkg/h265/hvc/hvc.go @@ -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 +} diff --git a/pkg/ivideon/client.go b/pkg/ivideon/client.go index 875dbab8..2fed1726 100644 --- a/pkg/ivideon/client.go +++ b/pkg/ivideon/client.go @@ -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, diff --git a/pkg/mp4/muxer.go b/pkg/mp4/muxer.go index f6a2fb79..daaa0b4a 100644 --- a/pkg/mp4/muxer.go +++ b/pkg/mp4/muxer.go @@ -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: