mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
support generic tracks with multiple formats (https://github.com/aler9/rtsp-simple-server/issues/1103)
This commit is contained in:
@@ -247,9 +247,13 @@ func TestClientRead(t *testing.T) {
|
||||
|
||||
track := &TrackGeneric{
|
||||
Media: "application",
|
||||
Formats: []string{"97"},
|
||||
Payloads: []TrackGenericPayload{{
|
||||
Type: 97,
|
||||
RTPMap: "97 private/90000",
|
||||
}},
|
||||
}
|
||||
err = track.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := Tracks{track}
|
||||
tracks.setControls()
|
||||
|
96
track.go
96
track.go
@@ -28,76 +28,110 @@ type Track interface {
|
||||
url(*url.URL) (*url.URL, error)
|
||||
}
|
||||
|
||||
func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
|
||||
control := func() string {
|
||||
for _, attr := range md.Attributes {
|
||||
func getControlAttribute(attributes []psdp.Attribute) string {
|
||||
for _, attr := range attributes {
|
||||
if attr.Key == "control" {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
}
|
||||
|
||||
rtpmapPart1, payloadType := func() (string, uint8) {
|
||||
rtpmap, ok := md.Attribute("rtpmap")
|
||||
if !ok {
|
||||
return "", 0
|
||||
func getRtpmapAttribute(attributes []psdp.Attribute, payloadType uint8) string {
|
||||
for _, attr := range attributes {
|
||||
if attr.Key == "rtpmap" {
|
||||
v := strings.TrimSpace(attr.Value)
|
||||
if parts := strings.SplitN(v, " ", 2); len(parts) == 2 {
|
||||
if tmp, err := strconv.ParseInt(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
|
||||
return parts[1]
|
||||
}
|
||||
rtpmap = strings.TrimSpace(rtpmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
rtpmapParts := strings.Split(rtpmap, " ")
|
||||
if len(rtpmapParts) != 2 {
|
||||
return "", 0
|
||||
func getFmtpAttribute(attributes []psdp.Attribute, payloadType uint8) string {
|
||||
for _, attr := range attributes {
|
||||
if attr.Key == "fmtp" {
|
||||
if parts := strings.SplitN(attr.Value, " ", 2); len(parts) == 2 {
|
||||
if tmp, err := strconv.ParseInt(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getCodecAndClock(attributes []psdp.Attribute, payloadType uint8) (string, string) {
|
||||
rtpmap := getRtpmapAttribute(attributes, payloadType)
|
||||
if rtpmap == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64)
|
||||
parts2 := strings.SplitN(rtpmap, "/", 2)
|
||||
if len(parts2) != 2 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return parts2[0], parts2[1]
|
||||
}
|
||||
|
||||
func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
|
||||
if len(md.MediaName.Formats) == 0 {
|
||||
return nil, fmt.Errorf("no media formats found")
|
||||
}
|
||||
|
||||
control := getControlAttribute(md.Attributes)
|
||||
|
||||
if len(md.MediaName.Formats) == 1 {
|
||||
tmp, err := strconv.ParseInt(md.MediaName.Formats[0], 10, 8)
|
||||
if err != nil {
|
||||
return "", 0
|
||||
return nil, err
|
||||
}
|
||||
payloadType := uint8(tmp)
|
||||
|
||||
return rtpmapParts[1], payloadType
|
||||
}()
|
||||
codec, clock := getCodecAndClock(md.Attributes, payloadType)
|
||||
|
||||
if len(md.MediaName.Formats) == 1 {
|
||||
switch {
|
||||
case md.MediaName.Media == "video":
|
||||
switch {
|
||||
case md.MediaName.Formats[0] == "26":
|
||||
case payloadType == 26:
|
||||
return newTrackJPEGFromMediaDescription(control)
|
||||
|
||||
case md.MediaName.Formats[0] == "32":
|
||||
case payloadType == 32:
|
||||
return newTrackMPEG2VideoFromMediaDescription(control)
|
||||
|
||||
case rtpmapPart1 == "H264/90000":
|
||||
case codec == "H264" && clock == "90000":
|
||||
return newTrackH264FromMediaDescription(control, payloadType, md)
|
||||
|
||||
case rtpmapPart1 == "H265/90000":
|
||||
case codec == "H265" && clock == "90000":
|
||||
return newTrackH265FromMediaDescription(control, payloadType, md)
|
||||
|
||||
case rtpmapPart1 == "VP8/90000":
|
||||
case codec == "VP8" && clock == "90000":
|
||||
return newTrackVP8FromMediaDescription(control, payloadType, md)
|
||||
|
||||
case rtpmapPart1 == "VP9/90000":
|
||||
case codec == "VP9" && clock == "90000":
|
||||
return newTrackVP9FromMediaDescription(control, payloadType, md)
|
||||
}
|
||||
|
||||
case md.MediaName.Media == "audio":
|
||||
switch {
|
||||
case md.MediaName.Formats[0] == "0":
|
||||
return newTrackPCMUFromMediaDescription(control, rtpmapPart1)
|
||||
case payloadType == 0:
|
||||
return newTrackPCMUFromMediaDescription(control, clock)
|
||||
|
||||
case md.MediaName.Formats[0] == "8":
|
||||
return newTrackPCMAFromMediaDescription(control, rtpmapPart1)
|
||||
case payloadType == 8:
|
||||
return newTrackPCMAFromMediaDescription(control, clock)
|
||||
|
||||
case md.MediaName.Formats[0] == "14":
|
||||
case payloadType == 14:
|
||||
return newTrackMPEG2AudioFromMediaDescription(control)
|
||||
|
||||
case strings.HasPrefix(strings.ToLower(rtpmapPart1), "mpeg4-generic/"):
|
||||
case strings.ToLower(codec) == "mpeg4-generic":
|
||||
return newTrackMPEG4AudioFromMediaDescription(control, payloadType, md)
|
||||
|
||||
case strings.HasPrefix(rtpmapPart1, "opus/"):
|
||||
return newTrackOpusFromMediaDescription(control, payloadType, rtpmapPart1, md)
|
||||
case codec == "opus":
|
||||
return newTrackOpusFromMediaDescription(control, payloadType, clock, md)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
160
track_generic.go
160
track_generic.go
@@ -8,152 +8,166 @@ import (
|
||||
psdp "github.com/pion/sdp/v3"
|
||||
)
|
||||
|
||||
func trackGenericGetClockRate(formats []string, rtpmap string) (int, error) {
|
||||
if len(formats) < 1 {
|
||||
return 0, fmt.Errorf("no formats provided")
|
||||
}
|
||||
func findClockRate(track *TrackGeneric) (int, error) {
|
||||
// RFC 4566
|
||||
// When a list of
|
||||
// payload type numbers is given, this implies that all of these
|
||||
// payload formats MAY be used in the session, but the first of these
|
||||
// formats SHOULD be used as the default format for the session
|
||||
payload := track.Payloads[0]
|
||||
|
||||
// get clock rate from payload type
|
||||
// https://en.wikipedia.org/wiki/RTP_payload_formats
|
||||
switch formats[0] {
|
||||
case "0", "1", "2", "3", "4", "5", "7", "8", "9", "12", "13", "15", "18":
|
||||
switch payload.Type {
|
||||
case 0, 1, 2, 3, 4, 5, 7, 8, 9, 12, 13, 15, 18:
|
||||
return 8000, nil
|
||||
|
||||
case "6":
|
||||
case 6:
|
||||
return 16000, nil
|
||||
|
||||
case "10", "11":
|
||||
case 10, 11:
|
||||
return 44100, nil
|
||||
|
||||
case "14", "25", "26", "28", "31", "32", "33", "34":
|
||||
case 14, 25, 26, 28, 31, 32, 33, 34:
|
||||
return 90000, nil
|
||||
|
||||
case "16":
|
||||
case 16:
|
||||
return 11025, nil
|
||||
|
||||
case "17":
|
||||
case 17:
|
||||
return 22050, nil
|
||||
}
|
||||
|
||||
// get clock rate from rtpmap
|
||||
// https://tools.ietf.org/html/rfc4566
|
||||
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
|
||||
if rtpmap == "" {
|
||||
if payload.RTPMap == "" {
|
||||
return 0, fmt.Errorf("attribute 'rtpmap' not found")
|
||||
}
|
||||
|
||||
tmp := strings.Split(rtpmap, " ")
|
||||
if len(tmp) < 2 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", rtpmap)
|
||||
}
|
||||
|
||||
tmp = strings.Split(tmp[1], "/")
|
||||
tmp := strings.Split(payload.RTPMap, "/")
|
||||
if len(tmp) != 2 && len(tmp) != 3 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", rtpmap)
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", payload.RTPMap)
|
||||
}
|
||||
|
||||
v, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
// TrackGenericPayload is a payload of a TrackGeneric.
|
||||
type TrackGenericPayload struct {
|
||||
Type uint8
|
||||
RTPMap string
|
||||
FMTP string
|
||||
}
|
||||
|
||||
// TrackGeneric is a generic track.
|
||||
type TrackGeneric struct {
|
||||
Media string
|
||||
Formats []string
|
||||
RTPMap string
|
||||
FMTP string
|
||||
Payloads []TrackGenericPayload
|
||||
|
||||
trackBase
|
||||
|
||||
clockRate int
|
||||
}
|
||||
|
||||
func newTrackGenericFromMediaDescription(
|
||||
control string,
|
||||
md *psdp.MediaDescription,
|
||||
) (*TrackGeneric, error) {
|
||||
rtpmap := func() string {
|
||||
for _, attr := range md.Attributes {
|
||||
if attr.Key == "rtpmap" {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
|
||||
_, err := trackGenericGetClockRate(md.MediaName.Formats, rtpmap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get clock rate: %s", err)
|
||||
}
|
||||
|
||||
fmtp := func() string {
|
||||
for _, attr := range md.Attributes {
|
||||
if attr.Key == "fmtp" {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
|
||||
return &TrackGeneric{
|
||||
t := &TrackGeneric{
|
||||
Media: md.MediaName.Media,
|
||||
Formats: md.MediaName.Formats,
|
||||
RTPMap: rtpmap,
|
||||
FMTP: fmtp,
|
||||
trackBase: trackBase{
|
||||
control: control,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
for _, format := range md.MediaName.Formats {
|
||||
tmp, err := strconv.ParseInt(format, 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payloadType := uint8(tmp)
|
||||
|
||||
t.Payloads = append(t.Payloads, TrackGenericPayload{
|
||||
Type: payloadType,
|
||||
RTPMap: getRtpmapAttribute(md.Attributes, payloadType),
|
||||
FMTP: getFmtpAttribute(md.Attributes, payloadType),
|
||||
})
|
||||
}
|
||||
|
||||
err := t.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Init initializes a TrackGeneric
|
||||
func (t *TrackGeneric) Init() error {
|
||||
var err error
|
||||
t.clockRate, err = findClockRate(t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get clock rate: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClockRate returns the track clock rate.
|
||||
func (t *TrackGeneric) ClockRate() int {
|
||||
clockRate, _ := trackGenericGetClockRate(t.Formats, t.RTPMap)
|
||||
return clockRate
|
||||
return t.clockRate
|
||||
}
|
||||
|
||||
func (t *TrackGeneric) clone() Track {
|
||||
return &TrackGeneric{
|
||||
Media: t.Media,
|
||||
Formats: t.Formats,
|
||||
RTPMap: t.RTPMap,
|
||||
FMTP: t.FMTP,
|
||||
Payloads: append([]TrackGenericPayload(nil), t.Payloads...),
|
||||
trackBase: t.trackBase,
|
||||
clockRate: t.clockRate,
|
||||
}
|
||||
}
|
||||
|
||||
// MediaDescription returns the track media description in SDP format.
|
||||
func (t *TrackGeneric) MediaDescription() *psdp.MediaDescription {
|
||||
return &psdp.MediaDescription{
|
||||
MediaName: psdp.MediaName{
|
||||
Media: t.Media,
|
||||
Protos: []string{"RTP", "AVP"},
|
||||
Formats: t.Formats,
|
||||
},
|
||||
Attributes: func() []psdp.Attribute {
|
||||
var ret []psdp.Attribute
|
||||
formats := make([]string, len(t.Payloads))
|
||||
for i, pl := range t.Payloads {
|
||||
formats[i] = strconv.FormatInt(int64(pl.Type), 10)
|
||||
}
|
||||
|
||||
if t.RTPMap != "" {
|
||||
ret = append(ret, psdp.Attribute{
|
||||
var attributes []psdp.Attribute
|
||||
|
||||
for _, pl := range t.Payloads {
|
||||
if pl.RTPMap != "" {
|
||||
attributes = append(attributes, psdp.Attribute{
|
||||
Key: "rtpmap",
|
||||
Value: t.RTPMap,
|
||||
Value: strconv.FormatInt(int64(pl.Type), 10) + " " + pl.RTPMap,
|
||||
})
|
||||
}
|
||||
|
||||
if t.FMTP != "" {
|
||||
ret = append(ret, psdp.Attribute{
|
||||
if pl.FMTP != "" {
|
||||
attributes = append(attributes, psdp.Attribute{
|
||||
Key: "fmtp",
|
||||
Value: t.FMTP,
|
||||
Value: strconv.FormatInt(int64(pl.Type), 10) + " " + pl.FMTP,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, psdp.Attribute{
|
||||
attributes = append(attributes, psdp.Attribute{
|
||||
Key: "control",
|
||||
Value: t.control,
|
||||
})
|
||||
|
||||
return ret
|
||||
}(),
|
||||
return &psdp.MediaDescription{
|
||||
MediaName: psdp.MediaName{
|
||||
Media: t.Media,
|
||||
Protos: []string{"RTP", "AVP"},
|
||||
Formats: formats,
|
||||
},
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
|
@@ -10,11 +10,20 @@ import (
|
||||
func TestTrackGenericClone(t *testing.T) {
|
||||
track := &TrackGeneric{
|
||||
Media: "video",
|
||||
Formats: []string{"100", "101"},
|
||||
RTPMap: "98 H265/90000",
|
||||
FMTP: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
Payloads: []TrackGenericPayload{
|
||||
{
|
||||
Type: 98,
|
||||
RTPMap: "H265/90000",
|
||||
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
||||
},
|
||||
{
|
||||
Type: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
err := track.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
clone := track.clone()
|
||||
require.NotSame(t, track, clone)
|
||||
@@ -24,16 +33,26 @@ func TestTrackGenericClone(t *testing.T) {
|
||||
func TestTrackGenericMediaDescription(t *testing.T) {
|
||||
track := &TrackGeneric{
|
||||
Media: "video",
|
||||
Formats: []string{"100", "101"},
|
||||
RTPMap: "98 H265/90000",
|
||||
FMTP: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
Payloads: []TrackGenericPayload{
|
||||
{
|
||||
Type: 98,
|
||||
RTPMap: "H265/90000",
|
||||
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
||||
},
|
||||
{
|
||||
Type: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
err := track.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &psdp.MediaDescription{
|
||||
MediaName: psdp.MediaName{
|
||||
Media: "video",
|
||||
Protos: []string{"RTP", "AVP"},
|
||||
Formats: []string{"100", "101"},
|
||||
Formats: []string{"98", "100"},
|
||||
},
|
||||
Attributes: []psdp.Attribute{
|
||||
{
|
||||
|
@@ -20,20 +20,20 @@ type TrackOpus struct {
|
||||
func newTrackOpusFromMediaDescription(
|
||||
control string,
|
||||
payloadType uint8,
|
||||
rtpmapPart1 string,
|
||||
clock string,
|
||||
md *psdp.MediaDescription,
|
||||
) (*TrackOpus, error) {
|
||||
tmp := strings.SplitN(rtpmapPart1, "/", 3)
|
||||
if len(tmp) != 3 {
|
||||
return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1)
|
||||
tmp := strings.SplitN(clock, "/", 32)
|
||||
if len(tmp) != 2 {
|
||||
return nil, fmt.Errorf("invalid clock (%v)", clock)
|
||||
}
|
||||
|
||||
sampleRate, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||
sampleRate, err := strconv.ParseInt(tmp[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelCount, err := strconv.ParseInt(tmp[2], 10, 64)
|
||||
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -17,8 +17,8 @@ func newTrackPCMAFromMediaDescription(
|
||||
rtpmapPart1 string) (*TrackPCMA, error,
|
||||
) {
|
||||
tmp := strings.Split(rtpmapPart1, "/")
|
||||
if len(tmp) >= 3 && tmp[2] != "1" {
|
||||
return nil, fmt.Errorf("PCMA tracks must have only one channel")
|
||||
if len(tmp) == 2 && tmp[1] != "1" {
|
||||
return nil, fmt.Errorf("PCMU tracks can have only one channel")
|
||||
}
|
||||
|
||||
return &TrackPCMA{
|
||||
|
@@ -14,11 +14,11 @@ type TrackPCMU struct {
|
||||
|
||||
func newTrackPCMUFromMediaDescription(
|
||||
control string,
|
||||
rtpmapPart1 string) (*TrackPCMU, error,
|
||||
clock string) (*TrackPCMU, error,
|
||||
) {
|
||||
tmp := strings.Split(rtpmapPart1, "/")
|
||||
if len(tmp) >= 3 && tmp[2] != "1" {
|
||||
return nil, fmt.Errorf("PCMU tracks must have only one channel")
|
||||
tmp := strings.SplitN(clock, "/", 2)
|
||||
if len(tmp) == 2 && tmp[1] != "1" {
|
||||
return nil, fmt.Errorf("PCMU tracks can have only one channel")
|
||||
}
|
||||
|
||||
return &TrackPCMU{
|
||||
|
@@ -457,26 +457,41 @@ func TestTrackNewFromMediaDescription(t *testing.T) {
|
||||
MediaName: psdp.MediaName{
|
||||
Media: "video",
|
||||
Protos: []string{"RTP", "AVP"},
|
||||
Formats: []string{"98", "96"},
|
||||
Formats: []string{"96", "98"},
|
||||
},
|
||||
Attributes: []psdp.Attribute{
|
||||
{
|
||||
Key: "rtpmap",
|
||||
Value: "98 H265/90000",
|
||||
Value: "96 H264/90000",
|
||||
},
|
||||
{
|
||||
Key: "rtpmap",
|
||||
Value: "98 MetaData",
|
||||
},
|
||||
{
|
||||
Key: "rtcp-mux",
|
||||
},
|
||||
{
|
||||
Key: "fmtp",
|
||||
Value: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
||||
Value: "96 packetization-mode=1;profile-level-id=4d002a;" +
|
||||
"sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==",
|
||||
},
|
||||
},
|
||||
},
|
||||
&TrackGeneric{
|
||||
Media: "video",
|
||||
Formats: []string{"98", "96"},
|
||||
RTPMap: "98 H265/90000",
|
||||
FMTP: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
||||
Payloads: []TrackGenericPayload{
|
||||
{
|
||||
Type: 96,
|
||||
RTPMap: "H264/90000",
|
||||
FMTP: "packetization-mode=1;profile-level-id=4d002a;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==",
|
||||
},
|
||||
{
|
||||
Type: 98,
|
||||
RTPMap: "MetaData",
|
||||
},
|
||||
},
|
||||
clockRate: 90000,
|
||||
},
|
||||
},
|
||||
} {
|
||||
@@ -503,7 +518,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
|
||||
Formats: []string{},
|
||||
},
|
||||
},
|
||||
"unable to get clock rate: no formats provided",
|
||||
"no media formats found",
|
||||
},
|
||||
{
|
||||
"no rtpmap",
|
||||
@@ -527,11 +542,11 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
|
||||
Attributes: []psdp.Attribute{
|
||||
{
|
||||
Key: "rtpmap",
|
||||
Value: "96",
|
||||
Value: "97 mpeg4-generic/48000/2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"unable to get clock rate: invalid rtpmap (96)",
|
||||
"unable to get clock rate: attribute 'rtpmap' not found",
|
||||
},
|
||||
{
|
||||
"invalid clockrate 2",
|
||||
@@ -548,7 +563,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"unable to get clock rate: invalid rtpmap (96 mpeg4-generic)",
|
||||
"unable to get clock rate: invalid rtpmap (mpeg4-generic)",
|
||||
},
|
||||
{
|
||||
"invalid clockrate 3",
|
||||
@@ -725,7 +740,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid rtpmap (opus/48000)",
|
||||
"invalid clock (48000)",
|
||||
},
|
||||
{
|
||||
"opus invalid 2",
|
||||
|
Reference in New Issue
Block a user