diff --git a/pkg/bits/reader.go b/pkg/bits/reader.go index 67804be5..cb58d789 100644 --- a/pkg/bits/reader.go +++ b/pkg/bits/reader.go @@ -82,6 +82,13 @@ func (r *Reader) ReadBits16(n byte) (res uint16) { return } +func (r *Reader) ReadBits64(n byte) (res uint64) { + for i := n - 1; i != 255; i-- { + res |= uint64(r.ReadBit()) << i + } + return +} + // ReadUEGolomb - ReadExponentialGolomb (unsigned) func (r *Reader) ReadUEGolomb() uint32 { var size byte diff --git a/pkg/bubble/client.go b/pkg/bubble/client.go index e436a450..e7a1e6c1 100644 --- a/pkg/bubble/client.go +++ b/pkg/bubble/client.go @@ -17,7 +17,7 @@ import ( "time" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/tcp" "github.com/pion/rtp" ) @@ -226,7 +226,7 @@ func (c *Client) Handle() error { Header: rtp.Header{ Timestamp: core.Now90000(), }, - Payload: h264.AnnexB2AVC(b[6:]), + Payload: annexb.EncodeToAVCC(b[6:], false), } c.videoTrack.WriteRTP(pkt) } else { diff --git a/pkg/dvrip/client.go b/pkg/dvrip/client.go index dc7e23fc..6df6ea68 100644 --- a/pkg/dvrip/client.go +++ b/pkg/dvrip/client.go @@ -8,14 +8,16 @@ import ( "encoding/json" "errors" "fmt" - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264" - "github.com/AlexxIT/go2rtc/pkg/h265" - "github.com/pion/rtp" "io" "net" "net/url" "time" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" + "github.com/AlexxIT/go2rtc/pkg/h265" + "github.com/pion/rtp" ) type Client struct { @@ -173,7 +175,7 @@ func (c *Client) Handle() error { switch dataType { case 0x1FC, 0x1FE: // video IFrame - payload := h264.AnnexB2AVC(b[16:]) + payload := annexb.EncodeToAVCC(b[16:], false) if c.videoTrack == nil { fps := b[5] @@ -208,7 +210,7 @@ func (c *Client) Handle() error { packet := &rtp.Packet{ Header: rtp.Header{Timestamp: c.videoTS}, - Payload: h264.AnnexB2AVC(b[8:]), + Payload: annexb.EncodeToAVCC(b[8:], false), } //log.Printf("[DVR] %v, len: %d, ts: %10d", h265.Types(packet.Payload), len(packet.Payload), packet.Timestamp) diff --git a/pkg/flv/client.go b/pkg/flv/client.go index f08ecd49..ad447780 100644 --- a/pkg/flv/client.go +++ b/pkg/flv/client.go @@ -7,7 +7,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/aac" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264/avc" + "github.com/AlexxIT/go2rtc/pkg/h264" "github.com/pion/rtp" ) @@ -101,7 +101,7 @@ func (c *Client) Describe() error { continue } - codec := avc.ConfigToCodec(b[5:]) + codec := h264.ConfigToCodec(b[5:]) media := &core.Media{ Kind: core.KindVideo, Direction: core.DirectionRecvonly, diff --git a/pkg/h264/README.md b/pkg/h264/README.md index 0708ff05..1cad2067 100644 --- a/pkg/h264/README.md +++ b/pkg/h264/README.md @@ -13,3 +13,4 @@ Payloader code taken from [pion](https://github.com/pion/rtp) library. And chang - [AVC profiles table](https://developer.mozilla.org/ru/docs/Web/Media/Formats/codecs_parameter) - [Supported Media for Google Cast](https://developers.google.com/cast/docs/media) - [Two stream formats, Annex-B, AVCC (H.264) and HVCC (H.265)](https://www.programmersought.com/article/3901815022/) +- https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/producer-reference-nal.html diff --git a/pkg/h264/annexb/annexb.go b/pkg/h264/annexb/annexb.go new file mode 100644 index 00000000..b7e6bd5d --- /dev/null +++ b/pkg/h264/annexb/annexb.go @@ -0,0 +1,129 @@ +// Package annexb - universal for H264 and H265 +package annexb + +import ( + "bytes" + "encoding/binary" +) + +const StartCode = "\x00\x00\x00\x01" +const startAUD = StartCode + "\x09\xF0" + StartCode + +// EncodeToAVCC +// will change original slice data! +// safeAppend should be used if original slice has useful data after end (part of other slice) +// +// FFmpeg MPEG-TS: 00000001 AUD 00000001 SPS 00000001 PPS 000001 IFrame +// FFmpeg H264: 00000001 SPS 00000001 PPS 000001 IFrame 00000001 PFrame +func EncodeToAVCC(b []byte, safeAppend bool) []byte { + const minSize = len(StartCode) + 1 + + // 1. Check frist "start code" + if len(b) < len(startAUD) || string(b[:len(StartCode)]) != StartCode { + return nil + } + + // 2. Skip Access unit delimiter (AUD) from FFmpeg + if string(b[:len(startAUD)]) == startAUD { + b = b[6:] + } + + var start int + + for i, n := minSize, len(b)-minSize; i < n; { + // 3. Check "start code" (first 2 bytes) + if b[i] != 0 || b[i+1] != 0 { + i++ + continue + } + + // 4. Check "start code" (3 bytes size or 4 bytes size) + if b[i+2] == 1 { + if safeAppend { + // protect original slice from "damage" + b = bytes.Clone(b) + safeAppend = false + } + + // convert start code from 3 bytes to 4 bytes + b = append(b, 0) + copy(b[i+1:], b[i:]) + n++ + } else if b[i+2] != 0 || b[i+3] != 1 { + i++ + continue + } + + // 5. Set size for previous AU + size := uint32(i - start - len(StartCode)) + binary.BigEndian.PutUint32(b[start:], size) + + start = i + + i += minSize + } + + // 6. Set size for last AU + size := uint32(len(b) - start - len(StartCode)) + binary.BigEndian.PutUint32(b[start:], size) + + return b +} + +func DecodeAVCC(b []byte) []byte { + b = bytes.Clone(b) + for i := 0; i < len(b); { + size := int(binary.BigEndian.Uint32(b[i:])) + b[i] = 0 + b[i+1] = 0 + b[i+2] = 0 + b[i+3] = 1 + i += 4 + size + } + return b +} + +const ( + h264PFrame = 1 + h264IFrame = 5 + h264SPS = 7 + h264PPS = 8 + + h265VPS = 32 + h265PFrame = 1 +) + +// IndexFrame - get new frame start position in the AnnexB stream +func IndexFrame(b []byte) int { + if len(b) < len(startAUD) { + return -1 + } + + for i := len(startAUD); ; { + if di := bytes.Index(b[i:], []byte(StartCode)); di < 0 { + break + } else { + i += di + 4 // move to NALU start + } + + if i >= len(b) { + break + } + + h264Type := b[i] & 0b1_1111 + switch h264Type { + case h264PFrame, h264SPS: + return i - 4 // move to start code + case h264IFrame, h264PPS: + continue + } + + h265Type := (b[i] >> 1) & 0b11_1111 + switch h265Type { + case h265PFrame, h265VPS: + return i - 4 // move to start code + } + } + + return -1 +} diff --git a/pkg/h264/avc.go b/pkg/h264/avc.go index c21b2f11..e6a294c8 100644 --- a/pkg/h264/avc.go +++ b/pkg/h264/avc.go @@ -3,46 +3,12 @@ package h264 import ( "bytes" "encoding/binary" - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/rtp" ) -func AnnexB2AVC(b []byte) []byte { - for i := 0; i < len(b); { - if i+4 >= len(b) { - break - } - - size := bytes.Index(b[i+4:], []byte{0, 0, 0, 1}) - if size < 0 { - size = len(b) - (i + 4) - } - - binary.BigEndian.PutUint32(b[i:], uint32(size)) - - i += size + 4 - } - - return b -} - -func AVCtoAnnexB(b []byte) []byte { - b = bytes.Clone(b) - for i := 0; i < len(b); { - size := int(binary.BigEndian.Uint32(b[i:])) - b[i] = 0 - b[i+1] = 0 - b[i+2] = 0 - b[i+3] = 1 - i += 4 + size - } - return b -} - const forbiddenZeroBit = 0x80 const nalUnitType = 0x1F -// DecodeStream - find and return first AU in AVC format +// Deprecated: DecodeStream - find and return first AU in AVC format // useful for processing live streams with unknown separator size func DecodeStream(annexb []byte) ([]byte, int) { startPos := -1 @@ -154,70 +120,3 @@ func IndexFrom(b []byte, sep []byte, from int) int { return bytes.Index(b, sep) } - -func EncodeAVC(nals ...[]byte) (avc []byte) { - var i, n int - - for _, nal := range nals { - if i = len(nal); i > 0 { - n += 4 + i - } - } - - avc = make([]byte, n) - - n = 0 - for _, nal := range nals { - if i = len(nal); i > 0 { - binary.BigEndian.PutUint32(avc[n:], uint32(i)) - n += 4 + copy(avc[n+4:], nal) - } - } - - return -} - -func RepairAVC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { - sps, pps := GetParameterSet(codec.FmtpLine) - ps := EncodeAVC(sps, pps) - - return func(packet *rtp.Packet) { - if NALUType(packet.Payload) == NALUTypeIFrame { - packet.Payload = Join(ps, packet.Payload) - } - handler(packet) - } -} - -func SplitAVC(data []byte) [][]byte { - var nals [][]byte - for { - // get AVC length - size := int(binary.BigEndian.Uint32(data)) + 4 - - // check if multiple items in one packet - if size < len(data) { - nals = append(nals, data[:size]) - data = data[size:] - } else { - nals = append(nals, data) - break - } - } - return nals -} - -func Types(data []byte) []byte { - var types []byte - for { - types = append(types, NALUType(data)) - - size := 4 + int(binary.BigEndian.Uint32(data)) - if size < len(data) { - data = data[size:] - } else { - break - } - } - return types -} diff --git a/pkg/h264/avc/avc_test.go b/pkg/h264/avc/avc_test.go deleted file mode 100644 index c81e2867..00000000 --- a/pkg/h264/avc/avc_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package avc - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDecodeConfig(t *testing.T) { - s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0" - src, err := hex.DecodeString(s) - require.Nil(t, err) - - profile, sps, pps := DecodeConfig(src) - require.NotNil(t, profile) - require.NotNil(t, sps) - require.NotNil(t, pps) - - dst := EncodeConfig(sps, pps) - require.Equal(t, src, dst) -} diff --git a/pkg/h264/avcc.go b/pkg/h264/avcc.go new file mode 100644 index 00000000..c80ea083 --- /dev/null +++ b/pkg/h264/avcc.go @@ -0,0 +1,111 @@ +// Package h264 - AVCC format related functions +package h264 + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/hex" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/pion/rtp" +) + +func RepairAVCC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { + sps, pps := GetParameterSet(codec.FmtpLine) + ps := JoinNALU(sps, pps) + + return func(packet *rtp.Packet) { + if NALUType(packet.Payload) == NALUTypeIFrame { + packet.Payload = Join(ps, packet.Payload) + } + handler(packet) + } +} + +func JoinNALU(nalus ...[]byte) (avcc []byte) { + var i, n int + + for _, nalu := range nalus { + if i = len(nalu); i > 0 { + n += 4 + i + } + } + + avcc = make([]byte, n) + + n = 0 + for _, nal := range nalus { + if i = len(nal); i > 0 { + binary.BigEndian.PutUint32(avcc[n:], uint32(i)) + n += 4 + copy(avcc[n+4:], nal) + } + } + + return +} + +func SplitNALU(avcc []byte) [][]byte { + var nals [][]byte + for { + // get AVC length + size := int(binary.BigEndian.Uint32(avcc)) + 4 + + // check if multiple items in one packet + if size < len(avcc) { + nals = append(nals, avcc[:size]) + avcc = avcc[size:] + } else { + nals = append(nals, avcc) + break + } + } + return nals +} + +func NALUTypes(avcc []byte) []byte { + var types []byte + for { + types = append(types, NALUType(avcc)) + + size := 4 + int(binary.BigEndian.Uint32(avcc)) + if size < len(avcc) { + avcc = avcc[size:] + } else { + break + } + } + return types +} + +func AVCCToCodec(avcc []byte) *core.Codec { + buf := bytes.NewBufferString("packetization-mode=1") + + for { + size := 4 + int(binary.BigEndian.Uint32(avcc)) + + switch NALUType(avcc) { + case NALUTypeSPS: + buf.WriteString(";profile-level-id=") + buf.WriteString(hex.EncodeToString(avcc[5:8])) + buf.WriteString(";sprop-parameter-sets=") + buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) + case NALUTypePPS: + buf.WriteString(",") + buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) + } + + if size < len(avcc) { + avcc = avcc[size:] + } else { + break + } + } + + return &core.Codec{ + Name: core.CodecH264, + ClockRate: 90000, + FmtpLine: buf.String(), + PayloadType: core.PayloadTypeRAW, + } +} diff --git a/pkg/h264/helper.go b/pkg/h264/h264.go similarity index 100% rename from pkg/h264/helper.go rename to pkg/h264/h264.go diff --git a/pkg/h264/avc/sps_test.go b/pkg/h264/h264_test.go similarity index 51% rename from pkg/h264/avc/sps_test.go rename to pkg/h264/h264_test.go index 2a33e7c5..542c3dde 100644 --- a/pkg/h264/avc/sps_test.go +++ b/pkg/h264/h264_test.go @@ -1,12 +1,27 @@ -package avc +package h264 import ( "encoding/base64" + "encoding/hex" "testing" "github.com/stretchr/testify/require" ) +func TestDecodeConfig(t *testing.T) { + s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0" + src, err := hex.DecodeString(s) + require.Nil(t, err) + + profile, sps, pps := DecodeConfig(src) + require.NotNil(t, profile) + require.NotNil(t, sps) + require.NotNil(t, pps) + + dst := EncodeConfig(sps, pps) + require.Equal(t, src, dst) +} + func TestDecodeSPS(t *testing.T) { s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410 b, err := base64.StdEncoding.DecodeString(s) @@ -14,7 +29,7 @@ func TestDecodeSPS(t *testing.T) { sps := DecodeSPS(b) require.Equal(t, uint16(2560), sps.Width()) - require.Equal(t, uint16(1920), sps.Heigth()) + require.Equal(t, uint16(1920), sps.Height()) s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff b, err = base64.StdEncoding.DecodeString(s) @@ -22,5 +37,5 @@ func TestDecodeSPS(t *testing.T) { sps = DecodeSPS(b) require.Equal(t, uint16(1920), sps.Width()) - require.Equal(t, uint16(1080), sps.Heigth()) + require.Equal(t, uint16(1080), sps.Height()) } diff --git a/pkg/h264/avc/avc.go b/pkg/h264/mpeg4.go similarity index 93% rename from pkg/h264/avc/avc.go rename to pkg/h264/mpeg4.go index 6a65181c..c49e0e8a 100644 --- a/pkg/h264/avc/avc.go +++ b/pkg/h264/mpeg4.go @@ -1,4 +1,5 @@ -package avc +// Package h264 - MPEG4 format related functions +package h264 import ( "bytes" @@ -9,6 +10,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" ) +// DecodeConfig - extract profile, SPS and PPS from MPEG4 config func DecodeConfig(conf []byte) (profile []byte, sps []byte, pps []byte) { if len(conf) < 6 || conf[0] != 1 { return diff --git a/pkg/h264/rtp.go b/pkg/h264/rtp.go index a1bc93ea..1864423f 100644 --- a/pkg/h264/rtp.go +++ b/pkg/h264/rtp.go @@ -2,7 +2,9 @@ package h264 import ( "encoding/binary" + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/pion/rtp" "github.com/pion/rtp/codecs" ) @@ -15,7 +17,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { depack := &codecs.H264Packet{IsAVC: true} sps, pps := GetParameterSet(codec.FmtpLine) - ps := EncodeAVC(sps, pps) + ps := JoinNALU(sps, pps) buf := make([]byte, 0, 512*1024) // 512K @@ -81,7 +83,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { // some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01 // https://github.com/AlexxIT/WebRTC/issues/391 // https://github.com/AlexxIT/WebRTC/issues/392 - AnnexB2AVC(payload) + payload = annexb.EncodeToAVCC(payload, false) } //log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber) diff --git a/pkg/h264/avc/sps.go b/pkg/h264/sps.go similarity index 98% rename from pkg/h264/avc/sps.go rename to pkg/h264/sps.go index 5271c110..f34fb8cb 100644 --- a/pkg/h264/avc/sps.go +++ b/pkg/h264/sps.go @@ -1,4 +1,4 @@ -package avc +package h264 import "github.com/AlexxIT/go2rtc/pkg/bits" @@ -49,11 +49,26 @@ type SPS struct { sar_height uint32 } +func (s *SPS) Width() uint16 { + width := 16 * (s.pic_width_in_mbs_minus_1 + 1) + crop := 2 * (s.frame_crop_left_offset + s.frame_crop_right_offset) + return uint16(width - crop) +} + +func (s *SPS) Height() uint16 { + height := 16 * (s.pic_height_in_map_units_minus_1 + 1) + crop := 2 * (s.frame_crop_top_offset + s.frame_crop_bottom_offset) + if s.frame_mbs_only_flag == 0 { + height *= 2 + } + return uint16(height - crop) +} + func DecodeSPS(sps []byte) *SPS { r := bits.NewReader(sps) hdr := r.ReadByte() - if hdr&0x1F != 7 { + if hdr&0x1F != NALUTypeSPS { return nil } @@ -147,18 +162,3 @@ func DecodeSPS(sps []byte) *SPS { return s } - -func (s *SPS) Width() uint16 { - width := 16 * (s.pic_width_in_mbs_minus_1 + 1) - crop := 2 * (s.frame_crop_left_offset + s.frame_crop_right_offset) - return uint16(width - crop) -} - -func (s *SPS) Heigth() uint16 { - height := 16 * (s.pic_height_in_map_units_minus_1 + 1) - crop := 2 * (s.frame_crop_top_offset + s.frame_crop_bottom_offset) - if s.frame_mbs_only_flag == 0 { - height *= 2 - } - return uint16(height - crop) -} diff --git a/pkg/h265/avc.go b/pkg/h265/avc.go index f6d68559..8bab9a14 100644 --- a/pkg/h265/avc.go +++ b/pkg/h265/avc.go @@ -5,7 +5,7 @@ import "github.com/AlexxIT/go2rtc/pkg/h264" const forbiddenZeroBit = 0x80 const nalUnitType = 0x3F -// DecodeStream - find and return first AU in AVC format +// Deprecated: DecodeStream - find and return first AU in AVC format // useful for processing live streams with unknown separator size func DecodeStream(annexb []byte) ([]byte, int) { startPos := -1 diff --git a/pkg/h265/avcc.go b/pkg/h265/avcc.go new file mode 100644 index 00000000..3c3f0e41 --- /dev/null +++ b/pkg/h265/avcc.go @@ -0,0 +1,43 @@ +// Package h265 - AVCC format related functions +package h265 + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + + "github.com/AlexxIT/go2rtc/pkg/core" +) + +func AVCCToCodec(avcc []byte) *core.Codec { + buf := bytes.NewBufferString("profile-id=1") + + for { + size := 4 + int(binary.BigEndian.Uint32(avcc)) + + switch NALUType(avcc) { + case NALUTypeVPS: + buf.WriteString(";sprop-vps=") + buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) + case NALUTypeSPS: + buf.WriteString(";sprop-sps=") + buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) + case NALUTypePPS: + buf.WriteString(";sprop-pps=") + buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) + } + + if size < len(avcc) { + avcc = avcc[size:] + } else { + break + } + } + + return &core.Codec{ + Name: core.CodecH265, + ClockRate: 90000, + FmtpLine: buf.String(), + PayloadType: core.PayloadTypeRAW, + } +} diff --git a/pkg/h265/h265_test.go b/pkg/h265/h265_test.go new file mode 100644 index 00000000..75fa03d7 --- /dev/null +++ b/pkg/h265/h265_test.go @@ -0,0 +1,19 @@ +package h265 + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDecodeSPS(t *testing.T) { + s := "QgEBAWAAAAMAAAMAAAMAAAMAmaAAoAgBaH+KrTuiS7/8AAQABbAgApMuADN/mAE=" + b, err := base64.StdEncoding.DecodeString(s) + require.Nil(t, err) + + sps := DecodeSPS(b) + require.NotNil(t, sps) + require.Equal(t, uint16(5120), sps.Width()) + require.Equal(t, uint16(1440), sps.Height()) +} diff --git a/pkg/h265/hvc/hvc.go b/pkg/h265/mpeg4.go similarity index 93% rename from pkg/h265/hvc/hvc.go rename to pkg/h265/mpeg4.go index 06d764fe..ec78b17d 100644 --- a/pkg/h265/hvc/hvc.go +++ b/pkg/h265/mpeg4.go @@ -1,4 +1,5 @@ -package hvc +// Package h265 - MPEG4 format related functions +package h265 import "encoding/binary" diff --git a/pkg/h265/sps.go b/pkg/h265/sps.go new file mode 100644 index 00000000..5f61363b --- /dev/null +++ b/pkg/h265/sps.go @@ -0,0 +1,126 @@ +package h265 + +import ( + "bytes" + + "github.com/AlexxIT/go2rtc/pkg/bits" +) + +// http://www.itu.int/rec/T-REC-H.265 + +//goland:noinspection GoSnakeCaseUsage +type SPS struct { + sps_video_parameter_set_id uint8 + sps_max_sub_layers_minus1 uint8 + sps_temporal_id_nesting_flag byte + + general_profile_space uint8 + general_tier_flag byte + general_profile_idc uint8 + general_profile_compatibility_flags uint32 + + general_level_idc uint8 + sub_layer_profile_present_flag []byte + sub_layer_level_present_flag []byte + + sps_seq_parameter_set_id uint32 + chroma_format_idc uint32 + separate_colour_plane_flag byte + + pic_width_in_luma_samples uint32 + pic_height_in_luma_samples uint32 +} + +func (s *SPS) Width() uint16 { + return uint16(s.pic_width_in_luma_samples) +} + +func (s *SPS) Height() uint16 { + return uint16(s.pic_height_in_luma_samples) +} + +func DecodeSPS(nalu []byte) *SPS { + rbsp := bytes.ReplaceAll(nalu[2:], []byte{0, 0, 3}, []byte{0, 0}) + + r := bits.NewReader(rbsp) + s := &SPS{} + + s.sps_video_parameter_set_id = r.ReadBits8(4) + s.sps_max_sub_layers_minus1 = r.ReadBits8(3) + s.sps_temporal_id_nesting_flag = r.ReadBit() + + if !s.profile_tier_level(r) { + return nil + } + + s.sps_seq_parameter_set_id = r.ReadUEGolomb() + s.chroma_format_idc = r.ReadUEGolomb() + if s.chroma_format_idc == 3 { + s.separate_colour_plane_flag = r.ReadBit() + } + + s.pic_width_in_luma_samples = r.ReadUEGolomb() + s.pic_height_in_luma_samples = r.ReadUEGolomb() + + //... + + if r.EOF { + return nil + } + + return s +} + +// profile_tier_level supports ONLY general_profile_idc == 1 +// over variants very complicated... +// +//goland:noinspection GoSnakeCaseUsage +func (s *SPS) profile_tier_level(r *bits.Reader) bool { + s.general_profile_space = r.ReadBits8(2) + s.general_tier_flag = r.ReadBit() + s.general_profile_idc = r.ReadBits8(5) + + s.general_profile_compatibility_flags = r.ReadBits(32) + _ = r.ReadBits64(48) // other flags + + if s.general_profile_idc != 1 { + return false + } + + s.general_level_idc = r.ReadBits8(8) + + s.sub_layer_profile_present_flag = make([]byte, s.sps_max_sub_layers_minus1) + s.sub_layer_level_present_flag = make([]byte, s.sps_max_sub_layers_minus1) + + for i := byte(0); i < s.sps_max_sub_layers_minus1; i++ { + s.sub_layer_profile_present_flag[i] = r.ReadBit() + s.sub_layer_level_present_flag[i] = r.ReadBit() + } + + if s.sps_max_sub_layers_minus1 > 0 { + for i := s.sps_max_sub_layers_minus1; i < 8; i++ { + _ = r.ReadBits8(2) // reserved_zero_2bits + } + } + + for i := byte(0); i < s.sps_max_sub_layers_minus1; i++ { + if s.sub_layer_profile_present_flag[i] != 0 { + _ = r.ReadBits8(2) // sub_layer_profile_space + _ = r.ReadBit() // sub_layer_tier_flag + sub_layer_profile_idc := r.ReadBits8(5) // sub_layer_profile_idc + + _ = r.ReadBits(32) // sub_layer_profile_compatibility_flag + _ = r.ReadBits64(48) // other flags + + if sub_layer_profile_idc != 1 { + return false + } + } + + if s.sub_layer_level_present_flag[i] != 0 { + _ = r.ReadBits8(8) + } + } + + return true +} diff --git a/pkg/ivideon/client.go b/pkg/ivideon/client.go index e3bef473..0158f08d 100644 --- a/pkg/ivideon/client.go +++ b/pkg/ivideon/client.go @@ -12,7 +12,7 @@ import ( "time" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264/avc" + "github.com/AlexxIT/go2rtc/pkg/h264" "github.com/AlexxIT/go2rtc/pkg/iso" "github.com/gorilla/websocket" "github.com/pion/rtp" @@ -205,7 +205,7 @@ func (c *Client) getTracks() error { avccLen := binary.BigEndian.Uint32(msg.Data[i:]) data = msg.Data[i+8 : i+int(avccLen)] - codec := avc.ConfigToCodec(data) + codec := h264.ConfigToCodec(data) media := &core.Media{ Kind: core.KindVideo, diff --git a/pkg/magic/keyframe.go b/pkg/magic/keyframe.go index abe83e59..fb7e25aa 100644 --- a/pkg/magic/keyframe.go +++ b/pkg/magic/keyframe.go @@ -3,6 +3,7 @@ package magic import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/h265" "github.com/AlexxIT/go2rtc/pkg/mjpeg" "github.com/pion/rtp" @@ -42,7 +43,7 @@ func (k *Keyframe) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv if !h264.IsKeyframe(packet.Payload) { return } - b := h264.AVCtoAnnexB(packet.Payload) + b := annexb.DecodeAVCC(packet.Payload) k.Fire(b) } diff --git a/pkg/mp4/consumer.go b/pkg/mp4/consumer.go index 3060a532..9b08fb0c 100644 --- a/pkg/mp4/consumer.go +++ b/pkg/mp4/consumer.go @@ -86,7 +86,7 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv if track.Codec.IsRTP() { handler.Handler = h264.RTPDepay(track.Codec, handler.Handler) } else { - handler.Handler = h264.RepairAVC(track.Codec, handler.Handler) + handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler) } case core.CodecH265: diff --git a/pkg/mp4/muxer.go b/pkg/mp4/muxer.go index daaa0b4a..f70475fc 100644 --- a/pkg/mp4/muxer.go +++ b/pkg/mp4/muxer.go @@ -6,9 +6,7 @@ import ( "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/pion/rtp" ) @@ -74,13 +72,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) { pps = []byte{0x68, 0xce, 0x38, 0x80} } - s := avc.DecodeSPS(sps) + s := h264.DecodeSPS(sps) if s == nil { return nil, errors.New("mp4: can't parse SPS") } mv.WriteVideoTrack( - uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), avc.EncodeConfig(sps, pps), + uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Height(), h264.EncodeConfig(sps, pps), ) case core.CodecH265: @@ -96,13 +94,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) { pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90} } - s := avc.DecodeSPS(sps) + s := h265.DecodeSPS(sps) if s == nil { return nil, errors.New("mp4: can't parse SPS") } mv.WriteVideoTrack( - uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), hvc.EncodeConfig(vps, sps, pps), + uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Height(), h265.EncodeConfig(vps, sps, pps), ) case core.CodecAAC: diff --git a/pkg/mp4/segment.go b/pkg/mp4/segment.go index f6ca82ec..ad1559cd 100644 --- a/pkg/mp4/segment.go +++ b/pkg/mp4/segment.go @@ -2,6 +2,7 @@ package mp4 import ( "encoding/json" + "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" "github.com/AlexxIT/go2rtc/pkg/h265" @@ -101,7 +102,7 @@ func (c *Segment) AddTrack(media *core.Media, _ *core.Codec, track *core.Receive if track.Codec.IsRTP() { handler.Handler = h264.RTPDepay(track.Codec, handler.Handler) } else { - handler.Handler = h264.RepairAVC(track.Codec, handler.Handler) + handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler) } case core.CodecH265: diff --git a/pkg/mpegts/helpers.go b/pkg/mpegts/helpers.go index 5407f70b..7beb7f8e 100644 --- a/pkg/mpegts/helpers.go +++ b/pkg/mpegts/helpers.go @@ -1,11 +1,13 @@ package mpegts import ( + "time" + "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/h265" "github.com/pion/rtp" - "time" ) const ( @@ -113,7 +115,7 @@ func (p *PES) GetPacket() (pkt *rtp.Packet) { PayloadType: p.StreamType, Timestamp: ts, }, - Payload: h264.AnnexB2AVC(payload), + Payload: annexb.EncodeToAVCC(payload, false), } case StreamTypePCMATapo: diff --git a/pkg/mpegts/ts.go b/pkg/mpegts/ts.go index 20e86cc9..ae1b4b49 100644 --- a/pkg/mpegts/ts.go +++ b/pkg/mpegts/ts.go @@ -118,7 +118,7 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv if track.Codec.IsRTP() { handler.Handler = h264.RTPDepay(track.Codec, handler.Handler) } else { - handler.Handler = h264.RepairAVC(track.Codec, handler.Handler) + handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler) } case core.CodecAAC: diff --git a/pkg/multipart/client.go b/pkg/multipart/client.go index b95c225e..1f5df226 100644 --- a/pkg/multipart/client.go +++ b/pkg/multipart/client.go @@ -11,7 +11,7 @@ import ( "time" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/pion/rtp" ) @@ -103,7 +103,7 @@ func (c *Client) Handle() error { Header: rtp.Header{ Timestamp: uint32(ts * 90000), }, - Payload: h264.AnnexB2AVC(body), + Payload: annexb.EncodeToAVCC(body, false), } video.WriteRTP(pkt) } diff --git a/pkg/webrtc/consumer.go b/pkg/webrtc/consumer.go index 340ec30e..784b93fe 100644 --- a/pkg/webrtc/consumer.go +++ b/pkg/webrtc/consumer.go @@ -53,7 +53,7 @@ func (c *Conn) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiv if track.Codec.IsRTP() { sender.Handler = h264.RTPDepay(track.Codec, sender.Handler) } else { - sender.Handler = h264.RepairAVC(track.Codec, sender.Handler) + sender.Handler = h264.RepairAVCC(track.Codec, sender.Handler) } case core.CodecH265: