client: support publishing with opus

This commit is contained in:
aler9
2021-10-30 15:45:13 +02:00
parent e9044bc6a5
commit cab3fe270e
14 changed files with 1208 additions and 855 deletions

234
track.go
View File

@@ -1,15 +1,12 @@
package gortsplib
import (
"encoding/base64"
"encoding/hex"
"fmt"
"strconv"
"strings"
psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/aac"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/pkg/sdp"
)
@@ -20,20 +17,6 @@ type Track struct {
Media *psdp.MediaDescription
}
// TrackConfigH264 is the configuration of an H264 track.
type TrackConfigH264 struct {
SPS []byte
PPS []byte
}
// TrackConfigAAC is the configuration of an AAC track.
type TrackConfigAAC struct {
Type int
SampleRate int
ChannelCount int
AOTSpecificConfig []byte
}
func (t *Track) hasControlAttribute() bool {
for _, attr := range t.Media.Attributes {
if attr.Key == "control" {
@@ -140,223 +123,6 @@ func (t *Track) ClockRate() (int, error) {
return 0, fmt.Errorf("attribute 'rtpmap' not found")
}
// NewTrackH264 initializes an H264 track.
func NewTrackH264(payloadType uint8, conf *TrackConfigH264) (*Track, error) {
if len(conf.SPS) < 4 {
return nil, fmt.Errorf("invalid SPS")
}
spropParameterSets := base64.StdEncoding.EncodeToString(conf.SPS) +
"," + base64.StdEncoding.EncodeToString(conf.PPS)
profileLevelID := strings.ToUpper(hex.EncodeToString(conf.SPS[1:4]))
typ := strconv.FormatInt(int64(payloadType), 10)
return &Track{
Media: &psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "video",
Protos: []string{"RTP", "AVP"},
Formats: []string{typ},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: typ + " H264/90000",
},
{
Key: "fmtp",
Value: typ + " packetization-mode=1; " +
"sprop-parameter-sets=" + spropParameterSets + "; " +
"profile-level-id=" + profileLevelID,
},
},
},
}, nil
}
// IsH264 checks whether the track is an H264 track.
func (t *Track) IsH264() bool {
if t.Media.MediaName.Media != "video" {
return false
}
v, ok := t.Media.Attribute("rtpmap")
if !ok {
return false
}
v = strings.TrimSpace(v)
vals := strings.Split(v, " ")
if len(vals) != 2 {
return false
}
return vals[1] == "H264/90000"
}
// ExtractConfigH264 extracts the configuration of an H264 track.
func (t *Track) ExtractConfigH264() (*TrackConfigH264, error) {
v, ok := t.Media.Attribute("fmtp")
if !ok {
return nil, fmt.Errorf("fmtp attribute is missing")
}
tmp := strings.SplitN(v, " ", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid fmtp attribute (%v)", v)
}
for _, kv := range strings.Split(tmp[1], ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid fmtp attribute (%v)", v)
}
if tmp[0] == "sprop-parameter-sets" {
tmp := strings.SplitN(tmp[1], ",", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v)
}
sps, err := base64.StdEncoding.DecodeString(tmp[0])
if err != nil {
return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v)
}
pps, err := base64.StdEncoding.DecodeString(tmp[1])
if err != nil {
return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v)
}
conf := &TrackConfigH264{
SPS: sps,
PPS: pps,
}
return conf, nil
}
}
return nil, fmt.Errorf("sprop-parameter-sets is missing (%v)", v)
}
// NewTrackAAC initializes an AAC track.
func NewTrackAAC(payloadType uint8, conf *TrackConfigAAC) (*Track, error) {
mpegConf, err := aac.MPEG4AudioConfig{
Type: aac.MPEG4AudioType(conf.Type),
SampleRate: conf.SampleRate,
ChannelCount: conf.ChannelCount,
AOTSpecificConfig: conf.AOTSpecificConfig,
}.Encode()
if err != nil {
return nil, err
}
typ := strconv.FormatInt(int64(payloadType), 10)
return &Track{
Media: &psdp.MediaDescription{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{typ},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: typ + " mpeg4-generic/" + strconv.FormatInt(int64(conf.SampleRate), 10) +
"/" + strconv.FormatInt(int64(conf.ChannelCount), 10),
},
{
Key: "fmtp",
Value: typ + " profile-level-id=1; " +
"mode=AAC-hbr; " +
"sizelength=13; " +
"indexlength=3; " +
"indexdeltalength=3; " +
"config=" + hex.EncodeToString(mpegConf),
},
},
},
}, nil
}
// IsAAC checks whether the track is an AAC track.
func (t *Track) IsAAC() bool {
if t.Media.MediaName.Media != "audio" {
return false
}
v, ok := t.Media.Attribute("rtpmap")
if !ok {
return false
}
vals := strings.Split(v, " ")
if len(vals) != 2 {
return false
}
return strings.HasPrefix(strings.ToLower(vals[1]), "mpeg4-generic/")
}
// ExtractConfigAAC extracts the configuration of an AAC track.
func (t *Track) ExtractConfigAAC() (*TrackConfigAAC, error) {
v, ok := t.Media.Attribute("fmtp")
if !ok {
return nil, fmt.Errorf("fmtp attribute is missing")
}
tmp := strings.SplitN(v, " ", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid fmtp (%v)", v)
}
for _, kv := range strings.Split(tmp[1], ";") {
kv = strings.Trim(kv, " ")
if len(kv) == 0 {
continue
}
tmp := strings.SplitN(kv, "=", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("invalid fmtp (%v)", v)
}
if tmp[0] == "config" {
enc, err := hex.DecodeString(tmp[1])
if err != nil {
return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1])
}
var mpegConf aac.MPEG4AudioConfig
err = mpegConf.Decode(enc)
if err != nil {
return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1])
}
conf := &TrackConfigAAC{
Type: int(mpegConf.Type),
SampleRate: mpegConf.SampleRate,
ChannelCount: mpegConf.ChannelCount,
AOTSpecificConfig: mpegConf.AOTSpecificConfig,
}
return conf, nil
}
}
return nil, fmt.Errorf("config is missing (%v)", v)
}
// Tracks is a list of tracks.
type Tracks []*Track