mirror of
https://github.com/aler9/gortsplib
synced 2025-10-07 08:01:14 +08:00
client: support publishing with opus
This commit is contained in:
234
track.go
234
track.go
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user