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

346 lines
9.3 KiB
Go

// 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 (
"encoding/hex"
"math/rand"
"time"
"github.com/q191201771/lal/pkg/h2645"
"github.com/q191201771/lal/pkg/rtmp"
"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"
)
// TODO(chef): refactor 将analyze部分独立出来作为一个filter
// TODO(chef): fix 如果前面来的音频和视频数据没有seq header都是gop中间的数据那么analyze分析的结果可能是音频和视频都没有
var (
// config
// TODO(chef): 提供option另外还有ssrc和pt都支持自定义
maxAnalyzeAvMsgSize = 16
)
// Rtmp2RtspRemuxer 提供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
audioSampleRate int
audioSsrc uint32
videoSsrc uint32
audioPacker *rtprtcp.RtpPacker
videoPacker *rtprtcp.RtpPacker
}
type OnSdp func(sdpCtx sdp.LogicContext)
type OnRtpPacket func(pkt rtprtcp.RtpPacket)
// NewRtmp2RtspRemuxer @param onSdp: 每次回调为独立的内存块,回调结束后,内部不再使用该内存块
// @param onRtpPacket: 每次回调为独立的内存块,回调结束后,内部不再使用该内存块
func NewRtmp2RtspRemuxer(onSdp OnSdp, onRtpPacket OnRtpPacket) *Rtmp2RtspRemuxer {
return &Rtmp2RtspRemuxer{
onSdp: onSdp,
onRtpPacket: onRtpPacket,
audioPt: base.AvPacketPtUnknown,
videoPt: base.AvPacketPtUnknown,
audioSampleRate: -1,
}
}
// FeedRtmpMsg @param msg: 函数调用结束后,内部不持有`msg`内存块
func (r *Rtmp2RtspRemuxer) FeedRtmpMsg(msg base.RtmpMsg) {
var err error
switch msg.Header.MsgTypeId {
case base.RtmpTypeIdMetadata:
if meta, err := rtmp.ParseMetadata(msg.Payload); err == nil {
if audioCodecId, ok := meta.Find("audiocodecid").(float64); ok {
switch uint8(audioCodecId) {
case base.RtmpSoundFormatG711U:
r.audioPt = base.AvPacketPtG711U
case base.RtmpSoundFormatG711A:
r.audioPt = base.AvPacketPtG711A
10 months ago
case base.RtmpSoundFormatOpus:
r.audioPt = base.AvPacketPtOpus
}
}
if samplerate, ok := meta.Find("audiosamplerate").(float64); ok {
r.audioSampleRate = int(samplerate)
}
}
return
case base.RtmpTypeIdAudio:
3 years ago
if len(msg.Payload) <= 2 {
Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload))
return
}
if r.audioPt == base.AvPacketPtUnknown {
switch msg.AudioCodecId() {
case base.RtmpSoundFormatG711U:
r.audioPt = base.AvPacketPtG711U
if r.audioSampleRate < 0 {
r.audioSampleRate = pcmDefaultSampleRate
}
case base.RtmpSoundFormatG711A:
r.audioPt = base.AvPacketPtG711A
if r.audioSampleRate < 0 {
r.audioSampleRate = pcmDefaultSampleRate
}
10 months ago
case base.RtmpSoundFormatOpus:
r.audioPt = base.AvPacketPtOpus
if r.audioSampleRate < 0 {
r.audioSampleRate = opusDefaultSampleRate
}
}
}
case base.RtmpTypeIdVideo:
3 years ago
if len(msg.Payload) <= 5 {
Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload))
return
}
}
// 我们需要先接收一部分rtmp数据得到音频头、视频头
// 并且考虑,流中只有音频或只有视频的情况
// 我们把前面这个阶段叫做Analyze分析阶段
if !r.analyzeDone {
if msg.IsAvcKeySeqHeader() || msg.IsHevcKeySeqHeader() {
if msg.IsAvcKeySeqHeader() {
r.sps, r.pps, err = avc.ParseSpsPpsFromSeqHeader(msg.Payload)
Log.Assert(nil, err)
} else if msg.IsHevcKeySeqHeader() {
if msg.IsEnhanced() {
r.vps, r.sps, r.pps, err = hevc.ParseVpsSpsPpsFromEnhancedSeqHeader(msg.Payload)
} else {
r.vps, r.sps, r.pps, err = hevc.ParseVpsSpsPpsFromSeqHeader(msg.Payload)
}
Log.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数据中不再包含音视频头
// TODO(chef): [opt] RtspRemuxerAddSpsPps2KeyFrameFlag 开启时考虑更新sps 202207
if msg.IsAvcKeySeqHeader() || msg.IsHevcKeySeqHeader() || msg.IsAacSeqHeader() {
return
}
r.remux(msg)
}
func (r *Rtmp2RtspRemuxer) doAnalyze() {
Log.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
var ascCtx *aac.AscContext
ascCtx, err := aac.NewAscContext(r.asc)
if err != nil {
r.asc = nil
2 years ago
Log.Warnf("invalid asc. asc=%+v, err=%+v", ascCtx, err)
return
}
// aac的采样率以asc为准
r.audioSampleRate, err = ascCtx.GetSamplingFrequency()
if err != nil {
r.asc = nil
2 years ago
Log.Warnf("invalid asc. asc=%+v, err=%+v", ascCtx, err)
return
}
}
// 回调sdp
videoInfo := sdp.VideoInfo{
VideoPt: r.videoPt,
Vps: r.vps,
Sps: r.sps,
Pps: r.pps,
}
audioInfo := sdp.AudioInfo{
AudioPt: r.audioPt,
Asc: r.asc,
SamplingFrequency: r.audioSampleRate,
}
ctx, err := sdp.Pack(videoInfo, audioInfo)
Log.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 {
// 音视频头都收集好了
// 注意这里故意只判断sps和pps从而同时支持h264和2h65的情况
if r.sps != nil && r.pps != nil && (r.asc != nil || r.audioPt != base.AvPacketPtUnknown) {
return true
}
// 达到分析包数阈值了
if len(r.msgCache) >= maxAnalyzeAvMsgSize {
return true
}
return false
}
func (r *Rtmp2RtspRemuxer) remux(msg base.RtmpMsg) {
var packer *rtprtcp.RtpPacker
var rtppkts []rtprtcp.RtpPacket
switch msg.Header.MsgTypeId {
case base.RtmpTypeIdAudio:
packer = r.getAudioPacker()
if packer != nil {
10 months ago
if msg.AudioCodecId() == base.RtmpSoundFormatG711A || msg.AudioCodecId() == base.RtmpSoundFormatG711U || msg.AudioCodecId() == base.RtmpSoundFormatOpus {
rtppkts = packer.Pack(base.AvPacket{
Timestamp: int64(msg.Header.TimestampAbs),
PayloadType: r.audioPt,
Payload: msg.Payload[1:],
})
} else {
rtppkts = packer.Pack(base.AvPacket{
Timestamp: int64(msg.Header.TimestampAbs),
PayloadType: r.audioPt,
Payload: msg.Payload[2:],
})
}
}
case base.RtmpTypeIdVideo:
packer = r.getVideoPacker()
if packer != nil {
var payload []byte
if msg.VideoCodecId() == base.RtmpCodecIdHevc && msg.IsEnchanedHevcNalu() {
index := msg.GetEnchanedHevcNaluIndex()
payload = msg.Payload[index:]
} else {
payload = msg.Payload[5:]
}
if RtspRemuxerAddSpsPps2KeyFrameFlag {
if msg.IsAvcKeyNalu() && r.sps != nil && r.pps != nil {
payload = h2645.JoinNaluAvcc(r.sps, r.pps, msg.Payload[9:])
}
if msg.IsHevcKeyNalu() && r.vps != nil && r.sps != nil && r.pps != nil {
payload = h2645.JoinNaluAvcc(r.vps, r.sps, r.pps, msg.Payload[9:])
}
}
rtppkts = r.getVideoPacker().Pack(base.AvPacket{
Timestamp: int64(msg.Header.TimestampAbs),
PayloadType: r.videoPt,
Payload: payload,
})
}
}
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()
switch r.audioPt {
case base.AvPacketPtG711A:
fallthrough
case base.AvPacketPtG711U:
pp := rtprtcp.NewRtpPackerPayloadPcm()
r.audioPacker = rtprtcp.NewRtpPacker(pp, r.audioSampleRate, r.audioSsrc)
10 months ago
case base.AvPacketPtOpus:
pp := rtprtcp.NewRtpPackerPayloadOpus()
r.audioPacker = rtprtcp.NewRtpPacker(pp, r.audioSampleRate, r.audioSsrc)
case base.AvPacketPtAac:
if r.asc == nil {
return nil
}
ascCtx, err := aac.NewAscContext(r.asc)
if err != nil {
Log.Errorf("parse asc failed. err=%+v", err)
return nil
}
clockRate, err := ascCtx.GetSamplingFrequency()
if err != nil {
Log.Errorf("get sampling frequency failed. err=%+v, asc=%s", err, hex.Dump(r.asc))
}
pp := rtprtcp.NewRtpPackerPayloadAac()
r.audioPacker = rtprtcp.NewRtpPacker(pp, clockRate, r.audioSsrc)
}
}
return r.audioPacker
}
func (r *Rtmp2RtspRemuxer) getVideoPacker() *rtprtcp.RtpPacker {
if r.sps == nil {
return nil
}
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())
}