You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/remux/rtmp2rtsp.go

214 lines
5.4 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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())
}