mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
305 lines
6.0 KiB
Go
305 lines
6.0 KiB
Go
package rtsp
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/pion/sdp/v3"
|
|
)
|
|
|
|
const (
|
|
DirectionRecvonly = "recvonly"
|
|
DirectionSendonly = "sendonly"
|
|
DirectionSendRecv = "sendrecv"
|
|
|
|
KindVideo = "video"
|
|
KindAudio = "audio"
|
|
|
|
CodecH264 = "H264" // payloadType: 96
|
|
CodecH265 = "H265"
|
|
CodecVP8 = "VP8"
|
|
CodecVP9 = "VP9"
|
|
CodecAV1 = "AV1"
|
|
CodecJPEG = "JPEG" // payloadType: 26
|
|
CodecRAW = "RAW"
|
|
|
|
CodecPCMU = "PCMU" // payloadType: 0
|
|
CodecPCMA = "PCMA" // payloadType: 8
|
|
CodecAAC = "MPEG4-GENERIC"
|
|
CodecLATM = "MP4A-LATM"
|
|
CodecOpus = "OPUS" // payloadType: 111
|
|
CodecG722 = "G722"
|
|
CodecMP3 = "MPA" // payload: 14, aka MPEG-1 Layer III
|
|
CodecPCM = "L16" // Linear PCM (big endian)
|
|
|
|
CodecPCML = "PCML" // Linear PCM (little endian)
|
|
|
|
CodecELD = "ELD" // AAC-ELD
|
|
CodecFLAC = "FLAC"
|
|
|
|
CodecAll = "ALL"
|
|
CodecAny = "ANY"
|
|
)
|
|
|
|
const PayloadTypeRAW byte = 255
|
|
|
|
func Between(s, sub1, sub2 string) string {
|
|
i := strings.Index(s, sub1)
|
|
if i < 0 {
|
|
return ""
|
|
}
|
|
s = s[i+len(sub1):]
|
|
|
|
if i = strings.Index(s, sub2); i >= 0 {
|
|
return s[:i]
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func Atoi(s string) (i int) {
|
|
if s != "" {
|
|
i, _ = strconv.Atoi(s)
|
|
}
|
|
return
|
|
}
|
|
|
|
type Codec struct {
|
|
Name string // H264, PCMU, PCMA, opus...
|
|
ClockRate uint32 // 90000, 8000, 16000...
|
|
Channels uint16 // 0, 1, 2
|
|
FmtpLine string
|
|
PayloadType uint8
|
|
}
|
|
|
|
// MarshalJSON - return FFprobe compatible output
|
|
func (c *Codec) MarshalJSON() ([]byte, error) {
|
|
info := map[string]any{}
|
|
if name := FFmpegCodecName(c.Name); name != "" {
|
|
info["codec_name"] = name
|
|
info["codec_type"] = c.Kind()
|
|
}
|
|
if c.Name == CodecH264 {
|
|
profile, level := DecodeH264(c.FmtpLine)
|
|
if profile != "" {
|
|
info["profile"] = profile
|
|
info["level"] = level
|
|
}
|
|
}
|
|
if c.ClockRate != 0 && c.ClockRate != 90000 {
|
|
info["sample_rate"] = c.ClockRate
|
|
}
|
|
if c.Channels > 0 {
|
|
info["channels"] = c.Channels
|
|
}
|
|
return json.Marshal(info)
|
|
}
|
|
|
|
func FFmpegCodecName(name string) string {
|
|
switch name {
|
|
case CodecH264:
|
|
return "h264"
|
|
case CodecH265:
|
|
return "hevc"
|
|
case CodecJPEG:
|
|
return "mjpeg"
|
|
case CodecRAW:
|
|
return "rawvideo"
|
|
case CodecPCMA:
|
|
return "pcm_alaw"
|
|
case CodecPCMU:
|
|
return "pcm_mulaw"
|
|
case CodecPCM:
|
|
return "pcm_s16be"
|
|
case CodecPCML:
|
|
return "pcm_s16le"
|
|
case CodecAAC, CodecLATM:
|
|
return "aac"
|
|
case CodecOpus:
|
|
return "opus"
|
|
case CodecVP8:
|
|
return "vp8"
|
|
case CodecVP9:
|
|
return "vp9"
|
|
case CodecAV1:
|
|
return "av1"
|
|
case CodecELD:
|
|
return "aac/eld"
|
|
case CodecFLAC:
|
|
return "flac"
|
|
case CodecMP3:
|
|
return "mp3"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (c *Codec) String() (s string) {
|
|
s = c.Name
|
|
if c.ClockRate != 0 && c.ClockRate != 90000 {
|
|
s += fmt.Sprintf("/%d", c.ClockRate)
|
|
}
|
|
if c.Channels > 0 {
|
|
s += fmt.Sprintf("/%d", c.Channels)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Codec) IsRTP() bool {
|
|
return c.PayloadType != PayloadTypeRAW
|
|
}
|
|
|
|
func (c *Codec) IsVideo() bool {
|
|
return c.Kind() == KindVideo
|
|
}
|
|
|
|
func (c *Codec) IsAudio() bool {
|
|
return c.Kind() == KindAudio
|
|
}
|
|
|
|
func (c *Codec) Kind() string {
|
|
return GetKind(c.Name)
|
|
}
|
|
|
|
func (c *Codec) PrintName() string {
|
|
switch c.Name {
|
|
case CodecAAC:
|
|
return "AAC"
|
|
case CodecPCM:
|
|
return "S16B"
|
|
case CodecPCML:
|
|
return "S16L"
|
|
}
|
|
return c.Name
|
|
}
|
|
|
|
func (c *Codec) Clone() *Codec {
|
|
clone := *c
|
|
return &clone
|
|
}
|
|
|
|
func (c *Codec) Match(remote *Codec) bool {
|
|
switch remote.Name {
|
|
case CodecAll, CodecAny:
|
|
return true
|
|
}
|
|
|
|
return c.Name == remote.Name &&
|
|
(c.ClockRate == remote.ClockRate || remote.ClockRate == 0) &&
|
|
(c.Channels == remote.Channels || remote.Channels == 0)
|
|
}
|
|
|
|
func UnmarshalCodec(md *sdp.MediaDescription, payloadType string) *Codec {
|
|
c := &Codec{PayloadType: byte(Atoi(payloadType))}
|
|
|
|
for _, attr := range md.Attributes {
|
|
switch {
|
|
case c.Name == "" && attr.Key == "rtpmap" && strings.HasPrefix(attr.Value, payloadType):
|
|
i := strings.IndexByte(attr.Value, ' ')
|
|
ss := strings.Split(attr.Value[i+1:], "/")
|
|
|
|
c.Name = strings.ToUpper(ss[0])
|
|
// fix tailing space: `a=rtpmap:96 H264/90000 `
|
|
c.ClockRate = uint32(Atoi(strings.TrimRightFunc(ss[1], unicode.IsSpace)))
|
|
|
|
if len(ss) == 3 && ss[2] == "2" {
|
|
c.Channels = 2
|
|
}
|
|
case c.FmtpLine == "" && attr.Key == "fmtp" && strings.HasPrefix(attr.Value, payloadType):
|
|
if i := strings.IndexByte(attr.Value, ' '); i > 0 {
|
|
c.FmtpLine = attr.Value[i+1:]
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Name == "" {
|
|
// https://en.wikipedia.org/wiki/RTP_payload_formats
|
|
switch payloadType {
|
|
case "0":
|
|
c.Name = CodecPCMU
|
|
c.ClockRate = 8000
|
|
case "8":
|
|
c.Name = CodecPCMA
|
|
c.ClockRate = 8000
|
|
case "10":
|
|
c.Name = CodecPCM
|
|
c.ClockRate = 44100
|
|
c.Channels = 2
|
|
case "11":
|
|
c.Name = CodecPCM
|
|
c.ClockRate = 44100
|
|
case "14":
|
|
c.Name = CodecMP3
|
|
c.ClockRate = 90000 // it's not real sample rate
|
|
case "26":
|
|
c.Name = CodecJPEG
|
|
c.ClockRate = 90000
|
|
case "96", "97", "98":
|
|
if len(md.Bandwidth) == 0 {
|
|
c.Name = payloadType
|
|
break
|
|
}
|
|
|
|
// FFmpeg + RTSP + pcm_s16le = doesn't pass info about codec name and params
|
|
// so try to guess the codec based on bitrate
|
|
// https://github.com/AlexxIT/go2rtc/issues/523
|
|
switch md.Bandwidth[0].Bandwidth {
|
|
case 128:
|
|
c.ClockRate = 8000
|
|
case 256:
|
|
c.ClockRate = 16000
|
|
case 384:
|
|
c.ClockRate = 24000
|
|
case 512:
|
|
c.ClockRate = 32000
|
|
case 705:
|
|
c.ClockRate = 44100
|
|
case 768:
|
|
c.ClockRate = 48000
|
|
case 1411:
|
|
// default Windows DShow
|
|
c.ClockRate = 44100
|
|
c.Channels = 2
|
|
case 1536:
|
|
// default Linux ALSA
|
|
c.ClockRate = 48000
|
|
c.Channels = 2
|
|
default:
|
|
c.Name = payloadType
|
|
break
|
|
}
|
|
|
|
c.Name = CodecPCML
|
|
default:
|
|
c.Name = payloadType
|
|
}
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func DecodeH264(fmtp string) (profile string, level byte) {
|
|
if ps := Between(fmtp, "sprop-parameter-sets=", ","); ps != "" {
|
|
if sps, _ := base64.StdEncoding.DecodeString(ps); len(sps) >= 4 {
|
|
switch sps[1] {
|
|
case 0x42:
|
|
profile = "Baseline"
|
|
case 0x4D:
|
|
profile = "Main"
|
|
case 0x58:
|
|
profile = "Extended"
|
|
case 0x64:
|
|
profile = "High"
|
|
default:
|
|
profile = fmt.Sprintf("0x%02X", sps[1])
|
|
}
|
|
|
|
level = sps[3]
|
|
}
|
|
}
|
|
return
|
|
}
|