|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
// Copyright 2020, Chef. All rights reserved.
|
|
|
|
|
// Copyright 2021, Chef. All rights reserved.
|
|
|
|
|
// https://github.com/q191201771/lal
|
|
|
|
|
//
|
|
|
|
|
// Use of this source code is governed by a MIT-style license
|
|
|
|
@ -14,177 +14,277 @@ import (
|
|
|
|
|
"github.com/q191201771/lal/pkg/base"
|
|
|
|
|
"github.com/q191201771/lal/pkg/hevc"
|
|
|
|
|
"github.com/q191201771/lal/pkg/rtmp"
|
|
|
|
|
"github.com/q191201771/lal/pkg/rtprtcp"
|
|
|
|
|
"github.com/q191201771/lal/pkg/sdp"
|
|
|
|
|
"github.com/q191201771/naza/pkg/bele"
|
|
|
|
|
"github.com/q191201771/naza/pkg/nazalog"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// @return 返回的内存块为新申请的独立内存块
|
|
|
|
|
// AVPacket转换为RTMP
|
|
|
|
|
// 目前AVPacket来自RTSP的sdp以及rtp包。理论上也支持webrtc,后续接入webrtc时再验证
|
|
|
|
|
type AVPacket2RTMPRemuxer struct {
|
|
|
|
|
onRTMPAVMsg rtmp.OnReadRTMPAVMsg
|
|
|
|
|
|
|
|
|
|
hasEmittedMetadata bool
|
|
|
|
|
audioType base.AVPacketPT
|
|
|
|
|
videoType base.AVPacketPT
|
|
|
|
|
|
|
|
|
|
vps []byte // 从AVPacket数据中获取
|
|
|
|
|
sps []byte
|
|
|
|
|
pps []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAVPacket2RTMPRemuxer(onRTMPAVMsg rtmp.OnReadRTMPAVMsg) *AVPacket2RTMPRemuxer {
|
|
|
|
|
return &AVPacket2RTMPRemuxer{
|
|
|
|
|
onRTMPAVMsg: onRTMPAVMsg,
|
|
|
|
|
audioType: base.AVPacketPTUnknown,
|
|
|
|
|
videoType: base.AVPacketPTUnknown,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 实现RTSP回调数据的三个接口,使得接入时方便些
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) OnRTPPacket(pkt rtprtcp.RTPPacket) {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) OnSDP(sdpCtx sdp.LogicContext) {
|
|
|
|
|
r.InitWithAVConfig(sdpCtx.ASC, sdpCtx.VPS, sdpCtx.SPS, sdpCtx.PPS)
|
|
|
|
|
}
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) OnAVPacket(pkt base.AVPacket) {
|
|
|
|
|
r.FeedAVPacket(pkt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// rtsp场景下,有时sps、pps等信息只包含在sdp中,有时包含在rtp包中,
|
|
|
|
|
// 这里提供输入sdp的sps、pps等信息的机会,如果没有,可以不调用
|
|
|
|
|
//
|
|
|
|
|
func AVConfig2RTMPMsg(asc, vps, sps, pps []byte) (metadata, ash, vsh *base.RTMPMsg, err error) {
|
|
|
|
|
var bMetadata []byte
|
|
|
|
|
// 内部不持有输入参数的内存块
|
|
|
|
|
//
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) InitWithAVConfig(asc, vps, sps, pps []byte) {
|
|
|
|
|
var err error
|
|
|
|
|
var bVsh []byte
|
|
|
|
|
var bAsh []byte
|
|
|
|
|
|
|
|
|
|
hasAudio := asc != nil
|
|
|
|
|
hasVideo := sps != nil && pps != nil
|
|
|
|
|
isHEVC := vps != nil
|
|
|
|
|
if asc != nil {
|
|
|
|
|
r.audioType = base.AVPacketPTAAC
|
|
|
|
|
}
|
|
|
|
|
if sps != nil && pps != nil {
|
|
|
|
|
if vps != nil {
|
|
|
|
|
r.videoType = base.AVPacketPTHEVC
|
|
|
|
|
} else {
|
|
|
|
|
r.videoType = base.AVPacketPTAVC
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !hasAudio && !hasVideo {
|
|
|
|
|
err = ErrRemux
|
|
|
|
|
if r.audioType == base.AVPacketPTUnknown && r.videoType == base.AVPacketPTUnknown {
|
|
|
|
|
nazalog.Warn("has no audio or video")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
audiocodecid := -1
|
|
|
|
|
if hasAudio {
|
|
|
|
|
audiocodecid = int(base.RTMPSoundFormatAAC)
|
|
|
|
|
if r.audioType != base.AVPacketPTUnknown {
|
|
|
|
|
bAsh, err = aac.BuildAACSeqHeader(asc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build aac seq header failed. err=%+v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
videocodecid := -1
|
|
|
|
|
width := -1
|
|
|
|
|
height := -1
|
|
|
|
|
if hasVideo {
|
|
|
|
|
if isHEVC {
|
|
|
|
|
videocodecid = int(base.RTMPCodecIDHEVC)
|
|
|
|
|
var ctx hevc.Context
|
|
|
|
|
if err = hevc.ParseSPS(sps, &ctx); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
width = int(ctx.PicWidthInLumaSamples)
|
|
|
|
|
height = int(ctx.PicHeightInLumaSamples)
|
|
|
|
|
|
|
|
|
|
if r.videoType != base.AVPacketPTUnknown {
|
|
|
|
|
if r.videoType == base.AVPacketPTHEVC {
|
|
|
|
|
bVsh, err = hevc.BuildSeqHeaderFromVPSSPSPPS(vps, sps, pps)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build hevc seq header failed. err=%+v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
videocodecid = int(base.RTMPCodecIDAVC)
|
|
|
|
|
var ctx avc.Context
|
|
|
|
|
err = avc.ParseSPS(sps, &ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ctx.Width != 0 {
|
|
|
|
|
width = int(ctx.Width)
|
|
|
|
|
}
|
|
|
|
|
if ctx.Height != 0 {
|
|
|
|
|
height = int(ctx.Height)
|
|
|
|
|
}
|
|
|
|
|
bVsh, err = avc.BuildSeqHeaderFromSPSPPS(sps, pps)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build avc seq header failed. err=%+v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasAudio {
|
|
|
|
|
bAsh, err = aac.BuildAACSeqHeader(asc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var h base.RTMPHeader
|
|
|
|
|
|
|
|
|
|
bMetadata, err = rtmp.BuildMetadata(width, height, audiocodecid, videocodecid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
if r.audioType != base.AVPacketPTUnknown {
|
|
|
|
|
r.emitRTMPAVMsg(true, bAsh, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.MsgLen = uint32(len(bMetadata))
|
|
|
|
|
h.TimestampAbs = 0
|
|
|
|
|
h.MsgTypeID = base.RTMPTypeIDMetadata
|
|
|
|
|
h.MsgStreamID = rtmp.MSID1
|
|
|
|
|
h.CSID = rtmp.CSIDAMF
|
|
|
|
|
metadata = &base.RTMPMsg{
|
|
|
|
|
Header: h,
|
|
|
|
|
Payload: bMetadata,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasVideo {
|
|
|
|
|
h.MsgLen = uint32(len(bVsh))
|
|
|
|
|
h.TimestampAbs = 0
|
|
|
|
|
h.MsgTypeID = base.RTMPTypeIDVideo
|
|
|
|
|
h.MsgStreamID = rtmp.MSID1
|
|
|
|
|
h.CSID = rtmp.CSIDVideo
|
|
|
|
|
vsh = &base.RTMPMsg{
|
|
|
|
|
Header: h,
|
|
|
|
|
Payload: bVsh,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasAudio {
|
|
|
|
|
h.MsgLen = uint32(len(bAsh))
|
|
|
|
|
h.TimestampAbs = 0
|
|
|
|
|
h.MsgTypeID = base.RTMPTypeIDAudio
|
|
|
|
|
h.MsgStreamID = rtmp.MSID1
|
|
|
|
|
h.CSID = rtmp.CSIDAudio
|
|
|
|
|
ash = &base.RTMPMsg{
|
|
|
|
|
Header: h,
|
|
|
|
|
Payload: bAsh,
|
|
|
|
|
}
|
|
|
|
|
if r.videoType != base.AVPacketPTUnknown {
|
|
|
|
|
r.emitRTMPAVMsg(false, bVsh, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @return 返回的内存块为新申请的独立内存块
|
|
|
|
|
func AVPacket2RTMPMsg(pkt base.AVPacket) (msg base.RTMPMsg, err error) {
|
|
|
|
|
// @param pkt: 内部不持有该内存块
|
|
|
|
|
//
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) FeedAVPacket(pkt base.AVPacket) {
|
|
|
|
|
switch pkt.PayloadType {
|
|
|
|
|
case base.AVPacketPTAVC:
|
|
|
|
|
fallthrough
|
|
|
|
|
case base.AVPacketPTHEVC:
|
|
|
|
|
msg.Header.TimestampAbs = pkt.Timestamp
|
|
|
|
|
msg.Header.MsgStreamID = rtmp.MSID1
|
|
|
|
|
|
|
|
|
|
msg.Header.MsgTypeID = base.RTMPTypeIDVideo
|
|
|
|
|
msg.Header.CSID = rtmp.CSIDVideo
|
|
|
|
|
msg.Header.MsgLen = uint32(len(pkt.Payload)) + 5
|
|
|
|
|
|
|
|
|
|
msg.Payload = make([]byte, msg.Header.MsgLen)
|
|
|
|
|
|
|
|
|
|
var nals [][]byte
|
|
|
|
|
nals, err = avc.SplitNALUAVCC(pkt.Payload)
|
|
|
|
|
nals, err := avc.SplitNALUAVCC(pkt.Payload)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("iterate nalu failed. err=%+v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos := 5
|
|
|
|
|
maxLength := len(pkt.Payload) + pos
|
|
|
|
|
payload := make([]byte, maxLength)
|
|
|
|
|
|
|
|
|
|
for _, nal := range nals {
|
|
|
|
|
switch pkt.PayloadType {
|
|
|
|
|
case base.AVPacketPTAVC:
|
|
|
|
|
if pkt.PayloadType == base.AVPacketPTAVC {
|
|
|
|
|
t := avc.ParseNALUType(nal[0])
|
|
|
|
|
if t == avc.NALUTypeIDRSlice {
|
|
|
|
|
msg.Payload[0] = base.RTMPAVCKeyFrame
|
|
|
|
|
if t == avc.NALUTypeSPS || t == avc.NALUTypePPS {
|
|
|
|
|
// 如果有sps,pps,先把它们抽离出来进行缓存
|
|
|
|
|
if t == avc.NALUTypeSPS {
|
|
|
|
|
r.setSPS(nal)
|
|
|
|
|
} else {
|
|
|
|
|
r.setPPS(nal)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.sps != nil && r.pps != nil {
|
|
|
|
|
// TODO(chef): 是否应该判断sps、pps是连续的,比如rtp seq的关系,或者timestamp是相等的
|
|
|
|
|
// 凑齐了,发送video seq header
|
|
|
|
|
|
|
|
|
|
bVsh, err := avc.BuildSeqHeaderFromSPSPPS(r.sps, r.pps)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build avc seq header failed. err=%+v", err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
r.emitRTMPAVMsg(false, bVsh, pkt.Timestamp)
|
|
|
|
|
r.clearVideoSeqHeader()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
msg.Payload[0] = base.RTMPAVCInterFrame
|
|
|
|
|
// 重组实际数据
|
|
|
|
|
|
|
|
|
|
if t == avc.NALUTypeIDRSlice {
|
|
|
|
|
payload[0] = base.RTMPAVCKeyFrame
|
|
|
|
|
} else {
|
|
|
|
|
payload[0] = base.RTMPAVCInterFrame
|
|
|
|
|
}
|
|
|
|
|
payload[1] = base.RTMPAVCPacketTypeNALU
|
|
|
|
|
bele.BEPutUint32(payload[pos:], uint32(len(nal)))
|
|
|
|
|
pos += 4
|
|
|
|
|
copy(payload[pos:], nal)
|
|
|
|
|
pos += len(nal)
|
|
|
|
|
}
|
|
|
|
|
msg.Payload[1] = base.RTMPAVCPacketTypeNALU
|
|
|
|
|
case base.AVPacketPTHEVC:
|
|
|
|
|
} else if pkt.PayloadType == base.AVPacketPTHEVC {
|
|
|
|
|
t := hevc.ParseNALUType(nal[0])
|
|
|
|
|
if t == hevc.NALUTypeSliceIDR || t == hevc.NALUTypeSliceIDRNLP {
|
|
|
|
|
msg.Payload[0] = base.RTMPHEVCKeyFrame
|
|
|
|
|
if t == hevc.NALUTypeVPS || t == hevc.NALUTypeSPS || t == hevc.NALUTypePPS {
|
|
|
|
|
if t == hevc.NALUTypeVPS {
|
|
|
|
|
r.setVPS(nal)
|
|
|
|
|
} else if t == hevc.NALUTypeSPS {
|
|
|
|
|
r.setSPS(nal)
|
|
|
|
|
} else {
|
|
|
|
|
r.setPPS(nal)
|
|
|
|
|
}
|
|
|
|
|
if r.vps != nil && r.sps != nil && r.pps != nil {
|
|
|
|
|
bVsh, err := hevc.BuildSeqHeaderFromVPSSPSPPS(r.vps, r.sps, r.pps)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build hevc seq header failed. err=%+v", err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
r.emitRTMPAVMsg(false, bVsh, pkt.Timestamp)
|
|
|
|
|
r.clearVideoSeqHeader()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
msg.Payload[0] = base.RTMPHEVCInterFrame
|
|
|
|
|
if t == hevc.NALUTypeSliceIDR || t == hevc.NALUTypeSliceIDRNLP {
|
|
|
|
|
payload[0] = base.RTMPHEVCKeyFrame
|
|
|
|
|
} else {
|
|
|
|
|
payload[0] = base.RTMPHEVCInterFrame
|
|
|
|
|
}
|
|
|
|
|
payload[1] = base.RTMPHEVCPacketTypeNALU
|
|
|
|
|
bele.BEPutUint32(payload[pos:], uint32(len(nal)))
|
|
|
|
|
pos += 4
|
|
|
|
|
copy(payload[pos:], nal)
|
|
|
|
|
pos += len(nal)
|
|
|
|
|
}
|
|
|
|
|
msg.Payload[1] = base.RTMPHEVCPacketTypeNALU
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg.Payload[2] = 0x0 // cts
|
|
|
|
|
msg.Payload[3] = 0x0
|
|
|
|
|
msg.Payload[4] = 0x0
|
|
|
|
|
copy(msg.Payload[5:], pkt.Payload)
|
|
|
|
|
//nazalog.Debugf("%d %s", len(msg.Payload), hex.Dump(msg.Payload[:32]))
|
|
|
|
|
// 有实际数据
|
|
|
|
|
if pos > 5 {
|
|
|
|
|
r.emitRTMPAVMsg(false, payload[:pos], pkt.Timestamp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case base.AVPacketPTAAC:
|
|
|
|
|
msg.Header.TimestampAbs = pkt.Timestamp
|
|
|
|
|
msg.Header.MsgStreamID = rtmp.MSID1
|
|
|
|
|
length := len(pkt.Payload) + 2
|
|
|
|
|
payload := make([]byte, length)
|
|
|
|
|
// TODO(chef) 处理此处的魔数0xAF
|
|
|
|
|
payload[0] = 0xAF
|
|
|
|
|
payload[1] = base.RTMPAACPacketTypeRaw
|
|
|
|
|
copy(payload[2:], pkt.Payload)
|
|
|
|
|
r.emitRTMPAVMsg(true, payload, pkt.Timestamp)
|
|
|
|
|
default:
|
|
|
|
|
nazalog.Warnf("unsupported packet. type=%d", pkt.PayloadType)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg.Header.MsgTypeID = base.RTMPTypeIDAudio
|
|
|
|
|
msg.Header.CSID = rtmp.CSIDAudio
|
|
|
|
|
msg.Header.MsgLen = uint32(len(pkt.Payload)) + 2
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) emitRTMPAVMsg(isAudio bool, payload []byte, timestamp uint32) {
|
|
|
|
|
if !r.hasEmittedMetadata {
|
|
|
|
|
// TODO(chef): 此处简化了从sps中获取宽高写入metadata的逻辑
|
|
|
|
|
audiocodecid := -1
|
|
|
|
|
videocodecid := -1
|
|
|
|
|
if r.audioType == base.AVPacketPTAAC {
|
|
|
|
|
audiocodecid = int(base.RTMPSoundFormatAAC)
|
|
|
|
|
}
|
|
|
|
|
switch r.videoType {
|
|
|
|
|
case base.AVPacketPTAVC:
|
|
|
|
|
videocodecid = int(base.RTMPCodecIDAVC)
|
|
|
|
|
case base.AVPacketPTHEVC:
|
|
|
|
|
videocodecid = int(base.RTMPCodecIDHEVC)
|
|
|
|
|
}
|
|
|
|
|
bMetadata, err := rtmp.BuildMetadata(-1, -1, audiocodecid, videocodecid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
nazalog.Errorf("build metadata failed. err=%+v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
r.onRTMPAVMsg(base.RTMPMsg{
|
|
|
|
|
Header: base.RTMPHeader{
|
|
|
|
|
CSID: rtmp.CSIDAMF,
|
|
|
|
|
MsgLen: uint32(len(bMetadata)),
|
|
|
|
|
MsgTypeID: base.RTMPTypeIDMetadata,
|
|
|
|
|
MsgStreamID: rtmp.MSID1,
|
|
|
|
|
TimestampAbs: 0,
|
|
|
|
|
},
|
|
|
|
|
Payload: bMetadata,
|
|
|
|
|
})
|
|
|
|
|
r.hasEmittedMetadata = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg.Payload = make([]byte, msg.Header.MsgLen)
|
|
|
|
|
msg.Payload[0] = 0xAF
|
|
|
|
|
msg.Payload[1] = base.RTMPAACPacketTypeRaw
|
|
|
|
|
copy(msg.Payload[2:], pkt.Payload)
|
|
|
|
|
default:
|
|
|
|
|
err = ErrRemux
|
|
|
|
|
return
|
|
|
|
|
var msg base.RTMPMsg
|
|
|
|
|
msg.Header.MsgStreamID = rtmp.MSID1
|
|
|
|
|
|
|
|
|
|
if isAudio {
|
|
|
|
|
msg.Header.CSID = rtmp.CSIDAudio
|
|
|
|
|
msg.Header.MsgTypeID = base.RTMPTypeIDAudio
|
|
|
|
|
} else {
|
|
|
|
|
msg.Header.CSID = rtmp.CSIDVideo
|
|
|
|
|
msg.Header.MsgTypeID = base.RTMPTypeIDVideo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
msg.Header.MsgLen = uint32(len(payload))
|
|
|
|
|
msg.Header.TimestampAbs = timestamp
|
|
|
|
|
msg.Payload = payload
|
|
|
|
|
|
|
|
|
|
r.onRTMPAVMsg(msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) setVPS(b []byte) {
|
|
|
|
|
r.vps = r.vps[0:0]
|
|
|
|
|
r.vps = append(r.vps, b...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) setSPS(b []byte) {
|
|
|
|
|
r.sps = r.sps[0:0]
|
|
|
|
|
r.sps = append(r.sps, b...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) setPPS(b []byte) {
|
|
|
|
|
r.pps = r.pps[0:0]
|
|
|
|
|
r.pps = append(r.pps, b...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *AVPacket2RTMPRemuxer) clearVideoSeqHeader() {
|
|
|
|
|
r.vps = r.vps[0:0]
|
|
|
|
|
r.sps = r.sps[0:0]
|
|
|
|
|
r.pps = r.pps[0:0]
|
|
|
|
|
}
|
|
|
|
|