rtmp2rtsp support G711U

pull/265/head
Jae-Sung Lee 2 years ago
parent c3da6ba1ab
commit adc930fb0e

@ -21,6 +21,7 @@ type AvPacketPt int
const ( const (
AvPacketPtUnknown AvPacketPt = -1 AvPacketPtUnknown AvPacketPt = -1
AvPacketPtG711U AvPacketPt = 0 // g711u
AvPacketPtG711A AvPacketPt = 8 // g711a AvPacketPtG711A AvPacketPt = 8 // g711a
AvPacketPtAvc AvPacketPt = 96 // h264 AvPacketPtAvc AvPacketPt = 96 // h264
AvPacketPtHevc AvPacketPt = 98 // h265 AvPacketPtHevc AvPacketPt = 98 // h265

@ -15,7 +15,8 @@ package base
const ( const (
// AudioCodecAac StatGroup.AudioCodec // AudioCodecAac StatGroup.AudioCodec
AudioCodecAac = "AAC" AudioCodecAac = "AAC"
AudioCodecG711U = "PCMU"
// VideoCodecAvc StatGroup.VideoCodec // VideoCodecAvc StatGroup.VideoCodec
VideoCodecAvc = "H264" VideoCodecAvc = "H264"

@ -11,6 +11,7 @@ package base
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/q191201771/naza/pkg/bele" "github.com/q191201771/naza/pkg/bele"
"github.com/q191201771/naza/pkg/nazabytes" "github.com/q191201771/naza/pkg/nazabytes"
) )
@ -86,9 +87,12 @@ const (
// AACAUDIODATA // AACAUDIODATA
// AACPacketType UI8 // AACPacketType UI8
// Data UI8[n] // Data UI8[n]
RtmpSoundFormatAac uint8 = 10 // 注意视频的CodecId是后4位音频是前4位 // 注意视频的CodecId是后4位音频是前4位
RtmpAacPacketTypeSeqHeader = 0 RtmpSoundFormatG711U uint8 = 8
RtmpAacPacketTypeRaw = 1 RtmpSoundFormatAac uint8 = 10
RtmpAacPacketTypeSeqHeader = 0
RtmpAacPacketTypeRaw = 1
) )
type RtmpHeader struct { type RtmpHeader struct {
@ -129,13 +133,17 @@ func (msg RtmpMsg) IsVideoKeyNalu() bool {
} }
func (msg RtmpMsg) IsAacSeqHeader() bool { func (msg RtmpMsg) IsAacSeqHeader() bool {
return msg.Header.MsgTypeId == RtmpTypeIdAudio && (msg.Payload[0]>>4) == RtmpSoundFormatAac && msg.Payload[1] == RtmpAacPacketTypeSeqHeader return msg.Header.MsgTypeId == RtmpTypeIdAudio && msg.AudioCodecId() == RtmpSoundFormatAac && msg.Payload[1] == RtmpAacPacketTypeSeqHeader
} }
func (msg RtmpMsg) VideoCodecId() uint8 { func (msg RtmpMsg) VideoCodecId() uint8 {
return msg.Payload[0] & 0xF return msg.Payload[0] & 0xF
} }
func (msg RtmpMsg) AudioCodecId() uint8 {
return msg.Payload[0] >> 4
}
func (msg RtmpMsg) Clone() (ret RtmpMsg) { func (msg RtmpMsg) Clone() (ret RtmpMsg) {
ret.Header = msg.Header ret.Header = msg.Header
ret.Payload = make([]byte, len(msg.Payload)) ret.Payload = make([]byte, len(msg.Payload))

@ -394,8 +394,15 @@ func (group *Group) broadcastByRtmpMsg(msg base.RtmpMsg) {
// # 记录stat // # 记录stat
if group.stat.AudioCodec == "" { if group.stat.AudioCodec == "" {
if msg.IsAacSeqHeader() { if msg.Header.MsgTypeId == base.RtmpTypeIdAudio {
group.stat.AudioCodec = base.AudioCodecAac switch msg.AudioCodecId() {
case base.RtmpSoundFormatAac:
if msg.IsAacSeqHeader() {
group.stat.AudioCodec = base.AudioCodecAac
}
case base.RtmpSoundFormatG711U:
group.stat.AudioCodec = base.AudioCodecG711U
}
} }
} }
if group.stat.VideoCodec == "" { if group.stat.VideoCodec == "" {

@ -10,10 +10,11 @@ package remux
import ( import (
"encoding/hex" "encoding/hex"
"github.com/q191201771/lal/pkg/h2645"
"math/rand" "math/rand"
"time" "time"
"github.com/q191201771/lal/pkg/h2645"
"github.com/q191201771/lal/pkg/aac" "github.com/q191201771/lal/pkg/aac"
"github.com/q191201771/lal/pkg/avc" "github.com/q191201771/lal/pkg/avc"
"github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/base"
@ -74,6 +75,12 @@ func (r *Rtmp2RtspRemuxer) FeedRtmpMsg(msg base.RtmpMsg) {
Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload)) Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload))
return return
} }
if r.audioPt == base.AvPacketPtUnknown {
switch msg.AudioCodecId() {
case base.RtmpSoundFormatG711U:
r.audioPt = base.AvPacketPtG711U
}
}
case base.RtmpTypeIdVideo: case base.RtmpTypeIdVideo:
if len(msg.Payload) <= 5 { if len(msg.Payload) <= 5 {
Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload)) Log.Warnf("rtmp msg too short, ignore. header=%+v, payload=%s", msg.Header, hex.Dump(msg.Payload))
@ -136,7 +143,7 @@ func (r *Rtmp2RtspRemuxer) doAnalyze() {
} }
// 回调sdp // 回调sdp
ctx, err := sdp.Pack(r.vps, r.sps, r.pps, r.asc) ctx, err := sdp.Pack(r.vps, r.sps, r.pps, r.asc, r.audioPt)
Log.Assert(nil, err) Log.Assert(nil, err)
r.onSdp(ctx) r.onSdp(ctx)
@ -206,26 +213,32 @@ func (r *Rtmp2RtspRemuxer) remux(msg base.RtmpMsg) {
} }
func (r *Rtmp2RtspRemuxer) getAudioPacker() *rtprtcp.RtpPacker { func (r *Rtmp2RtspRemuxer) getAudioPacker() *rtprtcp.RtpPacker {
if r.asc == nil {
return nil
}
if r.audioPacker == nil { if r.audioPacker == nil {
// TODO(chef): ssrc随机产生并且整个lal没有在setup信令中传递ssrc // TODO(chef): ssrc随机产生并且整个lal没有在setup信令中传递ssrc
r.audioSsrc = rand.Uint32() r.audioSsrc = rand.Uint32()
ascCtx, err := aac.NewAscContext(r.asc) switch r.audioPt {
if err != nil { case base.AvPacketPtG711U:
Log.Errorf("parse asc failed. err=%+v", err) pp := rtprtcp.NewRtpPackerPayloadPcm()
return nil r.audioPacker = rtprtcp.NewRtpPacker(pp, 8000, r.audioSsrc)
} case base.AvPacketPtAac:
clockRate, err := ascCtx.GetSamplingFrequency() if r.asc == nil {
if err != nil { return nil
Log.Errorf("get sampling frequency failed. err=%+v, asc=%s", err, hex.Dump(r.asc)) }
}
pp := rtprtcp.NewRtpPackerPayloadAac() ascCtx, err := aac.NewAscContext(r.asc)
r.audioPacker = rtprtcp.NewRtpPacker(pp, clockRate, r.audioSsrc) 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 return r.audioPacker
} }

@ -0,0 +1,31 @@
// 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 rtprtcp
type RtpPackerPayloadPcm struct {
}
func NewRtpPackerPayloadPcm() *RtpPackerPayloadPcm {
return &RtpPackerPayloadPcm{}
}
func (r *RtpPackerPayloadPcm) Pack(in []byte, maxSize int) (out [][]byte) {
if in == nil || maxSize <= 0 {
return
}
if len(in) > maxSize {
Log.Warnf("frame size bigger than rtp payload size while packing. len(in)=%d, maxSize=%d", len(in), maxSize)
}
item := make([]byte, len(in))
copy(item, in)
out = append(out, item)
return
}

@ -21,17 +21,38 @@ import (
"github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/base"
) )
func Pack(vps, sps, pps, asc []byte) (ctx LogicContext, err error) { func Pack(vps, sps, pps, asc []byte, audioPt base.AvPacketPt) (ctx LogicContext, err error) {
// 判断音频、视频是否存在以及视频是H264还是H265 // 判断音频、视频是否存在以及视频是H264还是H265
var hasAudio, hasVideo, isHevc bool var hasAudio, hasVideo, isHevc, isAac bool
if sps != nil && pps != nil { if sps != nil && pps != nil {
hasVideo = true hasVideo = true
if vps != nil { if vps != nil {
isHevc = true isHevc = true
} }
} }
if asc != nil {
hasAudio = true var samplingFrequency int
if audioPt != base.AvPacketPtUnknown {
switch audioPt {
case base.AvPacketPtG711U:
hasAudio = true
samplingFrequency = 8000
case base.AvPacketPtAac:
if asc != nil {
isAac = true
hasAudio = true
// 判断AAC的采样率
var ascCtx *aac.AscContext
ascCtx, err = aac.NewAscContext(asc)
if err != nil {
return
}
samplingFrequency, err = ascCtx.GetSamplingFrequency()
if err != nil {
return
}
}
}
} }
if !hasAudio && !hasVideo { if !hasAudio && !hasVideo {
@ -39,20 +60,6 @@ func Pack(vps, sps, pps, asc []byte) (ctx LogicContext, err error) {
return return
} }
// 判断AAC的采样率
var samplingFrequency int
if asc != nil {
var ascCtx *aac.AscContext
ascCtx, err = aac.NewAscContext(asc)
if err != nil {
return
}
samplingFrequency, err = ascCtx.GetSamplingFrequency()
if err != nil {
return
}
}
sdpStr := fmt.Sprintf(`v=0 sdpStr := fmt.Sprintf(`v=0
o=- 0 0 IN IP4 127.0.0.1 o=- 0 0 IN IP4 127.0.0.1
s=No Name s=No Name
@ -84,13 +91,21 @@ a=control:streamid=%d
} }
if hasAudio { if hasAudio {
tmpl := `m=audio 0 RTP/AVP 97 if isAac {
tmpl := `m=audio 0 RTP/AVP 97
b=AS:128 b=AS:128
a=rtpmap:97 MPEG4-GENERIC/%d/2 a=rtpmap:97 MPEG4-GENERIC/%d/2
a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=%s a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=%s
a=control:streamid=%d a=control:streamid=%d
` `
sdpStr += fmt.Sprintf(tmpl, samplingFrequency, hex.EncodeToString(asc), streamid) sdpStr += fmt.Sprintf(tmpl, samplingFrequency, hex.EncodeToString(asc), streamid)
} else {
tmpl := `m=audio 0 RTP/AVP 0
a=rtpmap:0 PCMU/%d
a=control:streamid=%d
`
sdpStr += fmt.Sprintf(tmpl, samplingFrequency, streamid)
}
} }
raw := []byte(strings.ReplaceAll(sdpStr, "\n", "\r\n")) raw := []byte(strings.ReplaceAll(sdpStr, "\n", "\r\n"))

@ -129,6 +129,8 @@ func ParseSdp2LogicContext(b []byte) (LogicContext, error) {
// 例子:a=rtpmap:8 PCMA/8000/1 // 例子:a=rtpmap:8 PCMA/8000/1
// rtmpmap中有PCMA字段表示G711A // rtmpmap中有PCMA字段表示G711A
ret.audioPayloadTypeBase = base.AvPacketPtG711A ret.audioPayloadTypeBase = base.AvPacketPtG711A
} else if strings.EqualFold(md.ARtpMap.EncodingName, ARtpMapEncodingNameG711U) {
ret.audioPayloadTypeBase = base.AvPacketPtG711U
} else { } else {
if md.M.PT == 8 { if md.M.PT == 8 {
// ffmpeg推流情况下不会填充rtpmap字段,m中pt值为8也可以表示是PCMA,采样率默认为8000Hz // ffmpeg推流情况下不会填充rtpmap字段,m中pt值为8也可以表示是PCMA,采样率默认为8000Hz

@ -15,4 +15,5 @@ const (
ARtpMapEncodingNameH264 = "H264" ARtpMapEncodingNameH264 = "H264"
ARtpMapEncodingNameAac = "MPEG4-GENERIC" ARtpMapEncodingNameAac = "MPEG4-GENERIC"
ARtpMapEncodingNameG711A = "PCMA" ARtpMapEncodingNameG711A = "PCMA"
ARtpMapEncodingNameG711U = "PCMU"
) )

Loading…
Cancel
Save