// Copyright 2021, Chef. All rights reserved. // https://github.com/q191201771/lal // // Use of this source code is governed by a MIT-style license // that can be found in the License file. // // Author: Chef (191201771@qq.com) package remux import ( "math/rand" "time" "github.com/q191201771/lal/pkg/aac" "github.com/q191201771/lal/pkg/avc" "github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/hevc" "github.com/q191201771/lal/pkg/rtprtcp" "github.com/q191201771/lal/pkg/sdp" "github.com/q191201771/naza/pkg/nazalog" ) // TODO(chef): refactor 将analyze部分独立出来作为一个filter var ( // config // TODO(chef): 提供option,另外还有ssrc和pt都支持自定义 maxAnalyzeAvMsgSize = 16 ) // 提供rtmp数据向sdp+rtp数据的转换 type Rtmp2RtspRemuxer struct { onSdp OnSdp onRtpPacket OnRtpPacket analyzeDone bool msgCache []base.RtmpMsg vps, sps, pps, asc []byte audioPt base.AvPacketPt videoPt base.AvPacketPt audioSsrc uint32 videoSsrc uint32 audioPacker *rtprtcp.RtpPacker videoPacker *rtprtcp.RtpPacker } type OnSdp func(sdpCtx sdp.LogicContext) type OnRtpPacket func(pkt rtprtcp.RtpPacket) // @param onSdp: 每次回调为独立的内存块,回调结束后,内部不再使用该内存块 // @param onRtpPacket: 每次回调为独立的内存块,回调结束后,内部不再使用该内存块 // func NewRtmp2RtspRemuxer(onSdp OnSdp, onRtpPacket OnRtpPacket) *Rtmp2RtspRemuxer { return &Rtmp2RtspRemuxer{ onSdp: onSdp, onRtpPacket: onRtpPacket, audioPt: base.AvPacketPtUnknown, videoPt: base.AvPacketPtUnknown, } } // @param msg: 函数调用结束后,内部不持有`msg`内存块 // func (r *Rtmp2RtspRemuxer) FeedRtmpMsg(msg base.RtmpMsg) { var err error if msg.Header.MsgTypeId == base.RtmpTypeIdMetadata { // noop return } // 我们需要先接收一部分rtmp数据,得到音频头、视频头 // 并且考虑,流中只有音频或只有视频的情况 // 我们把前面这个阶段叫做Analyze分析阶段 if !r.analyzeDone { if msg.IsAvcKeySeqHeader() || msg.IsHevcKeySeqHeader() { if msg.IsAvcKeySeqHeader() { r.sps, r.pps, err = avc.ParseSpsPpsFromSeqHeader(msg.Payload) nazalog.Assert(nil, err) } else if msg.IsHevcKeySeqHeader() { r.vps, r.sps, r.pps, err = hevc.ParseVpsSpsPpsFromSeqHeader(msg.Payload) nazalog.Assert(nil, err) } r.doAnalyze() return } if msg.IsAacSeqHeader() { r.asc = msg.Clone().Payload[2:] r.doAnalyze() return } r.msgCache = append(r.msgCache, msg.Clone()) r.doAnalyze() return } // 正常阶段 // 音视频头已通过sdp回调,rtp数据中不再包含音视频头 if msg.IsAvcKeySeqHeader() || msg.IsHevcKeySeqHeader() || msg.IsAacSeqHeader() { return } r.remux(msg) } func (r *Rtmp2RtspRemuxer) doAnalyze() { nazalog.Assert(false, r.analyzeDone) if r.isAnalyzeEnough() { if r.sps != nil && r.pps != nil { if r.vps != nil { r.videoPt = base.AvPacketPtHevc } else { r.videoPt = base.AvPacketPtAvc } } if r.asc != nil { r.audioPt = base.AvPacketPtAac } // 回调sdp ctx, err := sdp.Pack(r.vps, r.sps, r.pps, r.asc) nazalog.Assert(nil, err) r.onSdp(ctx) // 分析阶段缓存的数据 for i := range r.msgCache { r.remux(r.msgCache[i]) } r.msgCache = nil r.analyzeDone = true } } // 是否应该退出Analyze阶段 func (r *Rtmp2RtspRemuxer) isAnalyzeEnough() bool { // 音视频头都收集好了 if r.sps != nil && r.pps != nil && r.asc != nil { return true } // 达到分析包数阈值了 if len(r.msgCache) >= maxAnalyzeAvMsgSize { return true } return false } func (r *Rtmp2RtspRemuxer) remux(msg base.RtmpMsg) { var rtppkts []rtprtcp.RtpPacket switch msg.Header.MsgTypeId { case base.RtmpTypeIdAudio: rtppkts = r.getAudioPacker().Pack(base.AvPacket{ Timestamp: msg.Header.TimestampAbs, PayloadType: r.audioPt, Payload: msg.Payload[2:], }) case base.RtmpTypeIdVideo: rtppkts = r.getVideoPacker().Pack(base.AvPacket{ Timestamp: msg.Header.TimestampAbs, PayloadType: r.videoPt, Payload: msg.Payload[5:], }) } for i := range rtppkts { r.onRtpPacket(rtppkts[i]) } } func (r *Rtmp2RtspRemuxer) getAudioPacker() *rtprtcp.RtpPacker { if r.audioPacker == nil { // TODO(chef): ssrc随机产生,并且整个lal没有在setup信令中传递ssrc r.audioSsrc = rand.Uint32() // TODO(chef): 如果rtmp不是以音视频头开始,也可能收到了帧数据,但是头不存在,目前该remux没有做过多容错判断,后续要加上,或者在输入层保证 ascCtx, err := aac.NewAscContext(r.asc) if err != nil { nazalog.Errorf("parse asc failed. err=%+v", err) } clockRate, err := ascCtx.GetSamplingFrequency() if err != nil { nazalog.Errorf("get sampling frequency failed. err=%+v", err) } pp := rtprtcp.NewRtpPackerPayloadAac() r.audioPacker = rtprtcp.NewRtpPacker(pp, clockRate, r.audioSsrc) } return r.audioPacker } func (r *Rtmp2RtspRemuxer) getVideoPacker() *rtprtcp.RtpPacker { if r.videoPacker == nil { r.videoSsrc = rand.Uint32() pp := rtprtcp.NewRtpPackerPayloadAvcHevc(r.videoPt, func(option *rtprtcp.RtpPackerPayloadAvcHevcOption) { option.Typ = rtprtcp.RtpPackerPayloadAvcHevcTypeAvcc }) r.videoPacker = rtprtcp.NewRtpPacker(pp, 90000, r.videoSsrc) } return r.videoPacker } func init() { rand.Seed(time.Now().UnixNano()) }