Add support H265 to FLV source #822
This commit is contained in:
+61
-8
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/aac"
|
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||||
"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/h265"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +41,21 @@ const (
|
|||||||
CodecAVC = 7
|
CodecAVC = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PacketTypeAVCHeader = iota
|
||||||
|
PacketTypeAVCNALU
|
||||||
|
PacketTypeAVCEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PacketTypeSequenceStart = iota
|
||||||
|
PacketTypeCodedFrames
|
||||||
|
PacketTypeSequenceEnd
|
||||||
|
PacketTypeCodedFramesX
|
||||||
|
PacketTypeMetadata
|
||||||
|
PacketTypeMPEG2TSSequenceStart
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Producer) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
|
func (c *Producer) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
|
||||||
receiver, _ := c.SuperProducer.GetTrack(media, codec)
|
receiver, _ := c.SuperProducer.GetTrack(media, codec)
|
||||||
if media.Kind == core.KindVideo {
|
if media.Kind == core.KindVideo {
|
||||||
@@ -70,13 +86,32 @@ func (c *Producer) Start() error {
|
|||||||
c.audio.WriteRTP(pkt)
|
c.audio.WriteRTP(pkt)
|
||||||
|
|
||||||
case TagVideo:
|
case TagVideo:
|
||||||
// frame type 4b, codecID 4b, avc packet type 8b, composition time 24b
|
if c.video == nil {
|
||||||
if c.video == nil || pkt.Payload[1] == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt.Timestamp = TimeToRTP(pkt.Timestamp, c.video.Codec.ClockRate)
|
if isExHeader(pkt.Payload) {
|
||||||
|
switch packetType := pkt.Payload[0] & 0b1111; packetType {
|
||||||
|
case PacketTypeCodedFrames:
|
||||||
|
// frame type 4b, packet type 4b, fourCC 32b, composition time 24b
|
||||||
|
pkt.Payload = pkt.Payload[8:]
|
||||||
|
case PacketTypeCodedFramesX:
|
||||||
|
// frame type 4b, packet type 4b, fourCC 32b
|
||||||
pkt.Payload = pkt.Payload[5:]
|
pkt.Payload = pkt.Payload[5:]
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch pkt.Payload[1] {
|
||||||
|
case PacketTypeAVCNALU:
|
||||||
|
// frame type 4b, codecID 4b, avc packet type 8b, composition time 24b
|
||||||
|
pkt.Payload = pkt.Payload[5:]
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.Timestamp = TimeToRTP(pkt.Timestamp, c.video.Codec.ClockRate)
|
||||||
c.video.WriteRTP(pkt)
|
c.video.WriteRTP(pkt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,20 +180,32 @@ func (c *Producer) probe() error {
|
|||||||
c.Medias = append(c.Medias, media)
|
c.Medias = append(c.Medias, media)
|
||||||
|
|
||||||
case TagVideo:
|
case TagVideo:
|
||||||
_ = pkt.Payload[1] // bounds
|
var codec *core.Codec
|
||||||
|
|
||||||
|
if isExHeader(pkt.Payload) {
|
||||||
|
if string(pkt.Payload[1:5]) != "hvc1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if packetType := pkt.Payload[0] & 0b1111; packetType != PacketTypeSequenceStart {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
codec = h265.ConfigToCodec(pkt.Payload[5:])
|
||||||
|
} else {
|
||||||
_ = pkt.Payload[0] >> 4 // FrameType
|
_ = pkt.Payload[0] >> 4 // FrameType
|
||||||
codecID := pkt.Payload[0] & 0b1111 // CodecID
|
|
||||||
|
|
||||||
if codecID != CodecAVC {
|
if codecID := pkt.Payload[0] & 0b1111; codecID != CodecAVC {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkt.Payload[1] != 0 { // check if header
|
if packetType := pkt.Payload[1]; packetType != PacketTypeAVCHeader { // check if header
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
codec := h264.ConfigToCodec(pkt.Payload[5:])
|
codec = h264.ConfigToCodec(pkt.Payload[5:])
|
||||||
|
}
|
||||||
|
|
||||||
media := &core.Media{
|
media := &core.Media{
|
||||||
Kind: core.KindVideo,
|
Kind: core.KindVideo,
|
||||||
Direction: core.DirectionRecvonly,
|
Direction: core.DirectionRecvonly,
|
||||||
@@ -229,9 +276,15 @@ func (c *Producer) readPacket() (*rtp.Packet, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//log.Printf("[FLV] %d %.40x", pkt.PayloadType, pkt.Payload)
|
||||||
|
|
||||||
return pkt, nil
|
return pkt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TimeToRTP(timeMS uint32, clockRate uint32) uint32 {
|
func TimeToRTP(timeMS uint32, clockRate uint32) uint32 {
|
||||||
return timeMS * clockRate / 1000
|
return timeMS * clockRate / 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isExHeader(data []byte) bool {
|
||||||
|
return data[0]&0b1000_0000 != 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,26 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func RepairAVCC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||||
|
vds, sps, pps := GetParameterSet(codec.FmtpLine)
|
||||||
|
ps := h264.JoinNALU(vds, sps, pps)
|
||||||
|
|
||||||
|
return func(packet *rtp.Packet) {
|
||||||
|
switch NALUType(packet.Payload) {
|
||||||
|
case NALUTypeIFrame, NALUTypeIFrame2, NALUTypeIFrame3:
|
||||||
|
clone := *packet
|
||||||
|
clone.Payload = h264.Join(ps, packet.Payload)
|
||||||
|
handler(&clone)
|
||||||
|
default:
|
||||||
|
handler(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AVCCToCodec(avcc []byte) *core.Codec {
|
func AVCCToCodec(avcc []byte) *core.Codec {
|
||||||
buf := bytes.NewBufferString("profile-id=1")
|
buf := bytes.NewBufferString("profile-id=1")
|
||||||
|
|
||||||
|
|||||||
+59
-1
@@ -1,7 +1,40 @@
|
|||||||
// Package h265 - MPEG4 format related functions
|
// Package h265 - MPEG4 format related functions
|
||||||
package h265
|
package h265
|
||||||
|
|
||||||
import "encoding/binary"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DecodeConfig(conf []byte) (profile, vps, sps, pps []byte) {
|
||||||
|
profile = conf[1:4]
|
||||||
|
|
||||||
|
b := conf[23:]
|
||||||
|
if binary.BigEndian.Uint16(b[1:]) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vpsSize := binary.BigEndian.Uint16(b[3:])
|
||||||
|
vps = b[5 : 5+vpsSize]
|
||||||
|
|
||||||
|
b = conf[23+5+vpsSize:]
|
||||||
|
if binary.BigEndian.Uint16(b[1:]) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spsSize := binary.BigEndian.Uint16(b[3:])
|
||||||
|
sps = b[5 : 5+spsSize]
|
||||||
|
|
||||||
|
b = conf[23+5+vpsSize+5+spsSize:]
|
||||||
|
if binary.BigEndian.Uint16(b[1:]) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ppsSize := binary.BigEndian.Uint16(b[3:])
|
||||||
|
pps = b[5 : 5+ppsSize]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func EncodeConfig(vps, sps, pps []byte) []byte {
|
func EncodeConfig(vps, sps, pps []byte) []byte {
|
||||||
vpsSize := uint16(len(vps))
|
vpsSize := uint16(len(vps))
|
||||||
@@ -38,3 +71,28 @@ func EncodeConfig(vps, sps, pps []byte) []byte {
|
|||||||
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConfigToCodec(conf []byte) *core.Codec {
|
||||||
|
buf := bytes.NewBufferString("profile-id=1")
|
||||||
|
|
||||||
|
_, vps, sps, pps := DecodeConfig(conf)
|
||||||
|
if vps != nil {
|
||||||
|
buf.WriteString(";sprop-vps=")
|
||||||
|
buf.WriteString(base64.StdEncoding.EncodeToString(vps))
|
||||||
|
}
|
||||||
|
if sps != nil {
|
||||||
|
buf.WriteString(";sprop-sps=")
|
||||||
|
buf.WriteString(base64.StdEncoding.EncodeToString(sps))
|
||||||
|
}
|
||||||
|
if pps != nil {
|
||||||
|
buf.WriteString(";sprop-pps=")
|
||||||
|
buf.WriteString(base64.StdEncoding.EncodeToString(pps))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &core.Codec{
|
||||||
|
Name: core.CodecH265,
|
||||||
|
ClockRate: 90000,
|
||||||
|
FmtpLine: buf.String(),
|
||||||
|
PayloadType: core.PayloadTypeRAW,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv
|
|||||||
|
|
||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
handler.Handler = h265.RTPDepay(track.Codec, handler.Handler)
|
handler.Handler = h265.RTPDepay(track.Codec, handler.Handler)
|
||||||
|
} else {
|
||||||
|
handler.Handler = h265.RepairAVCC(track.Codec, handler.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user