mirror of
https://github.com/aler9/gortsplib
synced 2025-10-06 15:46:51 +08:00
simplify FMTP decoding and encoding (#205)
This commit is contained in:
@@ -32,6 +32,31 @@ func getCodecAndClock(rtpMap string) (string, string) {
|
|||||||
return parts2[0], parts2[1]
|
return parts2[0], parts2[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeFMTP(enc string) map[string]string {
|
||||||
|
if enc == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make(map[string]string)
|
||||||
|
|
||||||
|
for _, kv := range strings.Split(enc, ";") {
|
||||||
|
kv = strings.Trim(kv, " ")
|
||||||
|
|
||||||
|
if len(kv) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := strings.SplitN(kv, "=", 2)
|
||||||
|
if len(tmp) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[strings.ToLower(tmp[0])] = tmp[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Format is a format of a media.
|
// Format is a format of a media.
|
||||||
// It defines a codec and a payload type used to ship the media.
|
// It defines a codec and a payload type used to ship the media.
|
||||||
type Format interface {
|
type Format interface {
|
||||||
@@ -44,10 +69,10 @@ type Format interface {
|
|||||||
// PayloadType returns the payload type.
|
// PayloadType returns the payload type.
|
||||||
PayloadType() uint8
|
PayloadType() uint8
|
||||||
|
|
||||||
unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error
|
unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error
|
||||||
|
|
||||||
// Marshal encodes the format in SDP format.
|
// Marshal encodes the format in SDP format.
|
||||||
Marshal() (string, string)
|
Marshal() (string, map[string]string)
|
||||||
|
|
||||||
// PTSEqualsDTS checks whether PTS is equal to DTS in RTP packets.
|
// PTSEqualsDTS checks whether PTS is equal to DTS in RTP packets.
|
||||||
PTSEqualsDTS(*rtp.Packet) bool
|
PTSEqualsDTS(*rtp.Packet) bool
|
||||||
@@ -74,7 +99,7 @@ func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error)
|
|||||||
rtpMap := getFormatAttribute(md.Attributes, payloadType, "rtpmap")
|
rtpMap := getFormatAttribute(md.Attributes, payloadType, "rtpmap")
|
||||||
codec, clock := getCodecAndClock(rtpMap)
|
codec, clock := getCodecAndClock(rtpMap)
|
||||||
codec = strings.ToLower(codec)
|
codec = strings.ToLower(codec)
|
||||||
fmtp := getFormatAttribute(md.Attributes, payloadType, "fmtp")
|
fmtp := decodeFMTP(getFormatAttribute(md.Attributes, payloadType, "fmtp"))
|
||||||
|
|
||||||
format := func() Format {
|
format := func() Format {
|
||||||
switch {
|
switch {
|
||||||
|
@@ -721,27 +721,6 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"strconv.ParseInt: parsing \"\": invalid syntax",
|
"strconv.ParseInt: parsing \"\": invalid syntax",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"audio aac fmtp without key",
|
|
||||||
&psdp.MediaDescription{
|
|
||||||
MediaName: psdp.MediaName{
|
|
||||||
Media: "audio",
|
|
||||||
Protos: []string{"RTP", "AVP"},
|
|
||||||
Formats: []string{"96"},
|
|
||||||
},
|
|
||||||
Attributes: []psdp.Attribute{
|
|
||||||
{
|
|
||||||
Key: "rtpmap",
|
|
||||||
Value: "96 mpeg4-generic/48000/2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "fmtp",
|
|
||||||
Value: "96 profile-level-id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"invalid fmtp (profile-level-id)",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"audio aac missing config",
|
"audio aac missing config",
|
||||||
&psdp.MediaDescription{
|
&psdp.MediaDescription{
|
||||||
@@ -761,7 +740,7 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"config is missing (profile-level-id=1)",
|
"config is missing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"audio aac invalid config 1",
|
"audio aac invalid config 1",
|
||||||
@@ -824,7 +803,7 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sizelength is missing (profile-level-id=1; config=1190)",
|
"sizelength is missing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"audio aac invalid sizelength",
|
"audio aac invalid sizelength",
|
||||||
@@ -908,7 +887,7 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"config is missing (aa=bb)",
|
"config is missing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"audio opus invalid 1",
|
"audio opus invalid 1",
|
||||||
@@ -961,27 +940,6 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"strconv.ParseInt: parsing \"aa\": invalid syntax",
|
"strconv.ParseInt: parsing \"aa\": invalid syntax",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"video h264 invalid fmtp",
|
|
||||||
&psdp.MediaDescription{
|
|
||||||
MediaName: psdp.MediaName{
|
|
||||||
Media: "video",
|
|
||||||
Protos: []string{"RTP", "AVP"},
|
|
||||||
Formats: []string{"96"},
|
|
||||||
},
|
|
||||||
Attributes: []psdp.Attribute{
|
|
||||||
{
|
|
||||||
Key: "rtpmap",
|
|
||||||
Value: "96 H264/90000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "fmtp",
|
|
||||||
Value: "96 aaa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"invalid fmtp attribute (aaa)",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"video h264 invalid sps",
|
"video h264 invalid sps",
|
||||||
&psdp.MediaDescription{
|
&psdp.MediaDescription{
|
||||||
|
@@ -30,17 +30,17 @@ func (t *G711) PayloadType() uint8 {
|
|||||||
return 8
|
return 8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *G711) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *G711) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.MULaw = (payloadType == 0)
|
t.MULaw = (payloadType == 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *G711) Marshal() (string, string) {
|
func (t *G711) Marshal() (string, map[string]string) {
|
||||||
if t.MULaw {
|
if t.MULaw {
|
||||||
return "PCMU/8000", ""
|
return "PCMU/8000", nil
|
||||||
}
|
}
|
||||||
return "PCMA/8000", ""
|
return "PCMA/8000", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -28,7 +28,7 @@ func TestG711MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "PCMA/8000", rtpmap)
|
require.Equal(t, "PCMA/8000", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("pcmu", func(t *testing.T) {
|
t.Run("pcmu", func(t *testing.T) {
|
||||||
@@ -38,7 +38,7 @@ func TestG711MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "PCMU/8000", rtpmap)
|
require.Equal(t, "PCMU/8000", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,13 +24,13 @@ func (t *G722) PayloadType() uint8 {
|
|||||||
return 9
|
return 9
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *G722) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *G722) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *G722) Marshal() (string, string) {
|
func (t *G722) Marshal() (string, map[string]string) {
|
||||||
return "G722/8000", ""
|
return "G722/8000", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -20,7 +20,7 @@ func TestG722MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "G722/8000", rtpmap)
|
require.Equal(t, "G722/8000", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestG722DecEncoder(t *testing.T) {
|
func TestG722DecEncoder(t *testing.T) {
|
||||||
|
@@ -55,7 +55,7 @@ func findClockRate(payloadType uint8, rtpMap string) (int, error) {
|
|||||||
type Generic struct {
|
type Generic struct {
|
||||||
PayloadTyp uint8
|
PayloadTyp uint8
|
||||||
RTPMap string
|
RTPMap string
|
||||||
FMTP string
|
FMTP map[string]string
|
||||||
|
|
||||||
// clock rate of the format. Filled automatically.
|
// clock rate of the format. Filled automatically.
|
||||||
ClockRat int
|
ClockRat int
|
||||||
@@ -82,7 +82,10 @@ func (t *Generic) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Generic) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *Generic) unmarshal(
|
||||||
|
payloadType uint8, clock string, codec string,
|
||||||
|
rtpmap string, fmtp map[string]string,
|
||||||
|
) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
t.RTPMap = rtpmap
|
t.RTPMap = rtpmap
|
||||||
t.FMTP = fmtp
|
t.FMTP = fmtp
|
||||||
@@ -91,7 +94,7 @@ func (t *Generic) unmarshal(payloadType uint8, clock string, codec string, rtpma
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *Generic) Marshal() (string, string) {
|
func (t *Generic) Marshal() (string, map[string]string) {
|
||||||
return t.RTPMap, t.FMTP
|
return t.RTPMap, t.FMTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,8 +11,12 @@ func TestGenericAttributes(t *testing.T) {
|
|||||||
format := &Generic{
|
format := &Generic{
|
||||||
PayloadTyp: 98,
|
PayloadTyp: 98,
|
||||||
RTPMap: "H265/90000",
|
RTPMap: "H265/90000",
|
||||||
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
FMTP: map[string]string{
|
||||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
"profile-id": "1",
|
||||||
|
"sprop-vps": "QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ",
|
||||||
|
"sprop-sps": "QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=",
|
||||||
|
"sprop-pps": "RAHgdrAwxmQ=",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := format.Init()
|
err := format.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -27,14 +31,22 @@ func TestGenericMediaDescription(t *testing.T) {
|
|||||||
format := &Generic{
|
format := &Generic{
|
||||||
PayloadTyp: 98,
|
PayloadTyp: 98,
|
||||||
RTPMap: "H265/90000",
|
RTPMap: "H265/90000",
|
||||||
FMTP: "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " +
|
FMTP: map[string]string{
|
||||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
|
"profile-id": "1",
|
||||||
|
"sprop-vps": "QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ",
|
||||||
|
"sprop-sps": "QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=",
|
||||||
|
"sprop-pps": "RAHgdrAwxmQ=",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := format.Init()
|
err := format.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "H265/90000", rtpmap)
|
require.Equal(t, "H265/90000", rtpmap)
|
||||||
require.Equal(t, "profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; "+
|
require.Equal(t, map[string]string{
|
||||||
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", fmtp)
|
"profile-id": "1",
|
||||||
|
"sprop-vps": "QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ",
|
||||||
|
"sprop-sps": "QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=",
|
||||||
|
"sprop-pps": "RAHgdrAwxmQ=",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
@@ -95,48 +95,35 @@ func (t *H264) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
switch key {
|
||||||
kv = strings.Trim(kv, " ")
|
case "sprop-parameter-sets":
|
||||||
|
tmp2 := strings.Split(val, ",")
|
||||||
if len(kv) == 0 {
|
if len(tmp2) >= 2 {
|
||||||
continue
|
sps, err := base64.StdEncoding.DecodeString(tmp2[0])
|
||||||
}
|
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
|
||||||
if len(tmp) != 2 {
|
|
||||||
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tmp[0] {
|
|
||||||
case "sprop-parameter-sets":
|
|
||||||
tmp2 := strings.Split(tmp[1], ",")
|
|
||||||
if len(tmp2) >= 2 {
|
|
||||||
sps, err := base64.StdEncoding.DecodeString(tmp2[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid sprop-parameter-sets (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
pps, err := base64.StdEncoding.DecodeString(tmp2[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid sprop-parameter-sets (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
t.SPS = sps
|
|
||||||
t.PPS = pps
|
|
||||||
}
|
|
||||||
|
|
||||||
case "packetization-mode":
|
|
||||||
tmp2, err := strconv.ParseInt(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid packetization-mode (%v)", tmp[1])
|
return fmt.Errorf("invalid sprop-parameter-sets (%v)", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.PacketizationMode = int(tmp2)
|
pps, err := base64.StdEncoding.DecodeString(tmp2[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid sprop-parameter-sets (%v)", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.SPS = sps
|
||||||
|
t.PPS = pps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "packetization-mode":
|
||||||
|
tmp2, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid packetization-mode (%v)", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.PacketizationMode = int(tmp2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +131,16 @@ func (t *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *H264) Marshal() (string, string) {
|
func (t *H264) Marshal() (string, map[string]string) {
|
||||||
t.mutex.RLock()
|
t.mutex.RLock()
|
||||||
defer t.mutex.RUnlock()
|
defer t.mutex.RUnlock()
|
||||||
|
|
||||||
var tmp []string
|
fmtp := make(map[string]string)
|
||||||
|
|
||||||
if t.PacketizationMode != 0 {
|
if t.PacketizationMode != 0 {
|
||||||
tmp = append(tmp, "packetization-mode="+strconv.FormatInt(int64(t.PacketizationMode), 10))
|
fmtp["packetization-mode"] = strconv.FormatInt(int64(t.PacketizationMode), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp2 []string
|
var tmp2 []string
|
||||||
if t.SPS != nil {
|
if t.SPS != nil {
|
||||||
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.SPS))
|
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.SPS))
|
||||||
@@ -160,14 +149,10 @@ func (t *H264) Marshal() (string, string) {
|
|||||||
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.PPS))
|
tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(t.PPS))
|
||||||
}
|
}
|
||||||
if tmp2 != nil {
|
if tmp2 != nil {
|
||||||
tmp = append(tmp, "sprop-parameter-sets="+strings.Join(tmp2, ","))
|
fmtp["sprop-parameter-sets"] = strings.Join(tmp2, ",")
|
||||||
}
|
}
|
||||||
if len(t.SPS) >= 4 {
|
if len(t.SPS) >= 4 {
|
||||||
tmp = append(tmp, "profile-level-id="+strings.ToUpper(hex.EncodeToString(t.SPS[1:4])))
|
fmtp["profile-level-id"] = strings.ToUpper(hex.EncodeToString(t.SPS[1:4]))
|
||||||
}
|
|
||||||
var fmtp string
|
|
||||||
if tmp != nil {
|
|
||||||
fmtp = strings.Join(tmp, "; ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "H264/90000", fmtp
|
return "H264/90000", fmtp
|
||||||
|
@@ -59,8 +59,11 @@ func TestH264MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "H264/90000", rtpmap)
|
require.Equal(t, "H264/90000", rtpmap)
|
||||||
require.Equal(t, "packetization-mode=1; "+
|
require.Equal(t, map[string]string{
|
||||||
"sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==; profile-level-id=64000C", fmtp)
|
"packetization-mode": "1",
|
||||||
|
"sprop-parameter-sets": "Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==",
|
||||||
|
"profile-level-id": "64000C",
|
||||||
|
}, fmtp)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no sps/pps", func(t *testing.T) {
|
t.Run("no sps/pps", func(t *testing.T) {
|
||||||
@@ -71,7 +74,9 @@ func TestH264MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "H264/90000", rtpmap)
|
require.Equal(t, "H264/90000", rtpmap)
|
||||||
require.Equal(t, "packetization-mode=1", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"packetization-mode": "1",
|
||||||
|
}, fmtp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
@@ -38,51 +37,38 @@ func (t *H265) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *H265) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *H265) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
switch key {
|
||||||
kv = strings.Trim(kv, " ")
|
case "sprop-vps":
|
||||||
|
var err error
|
||||||
if len(kv) == 0 {
|
t.VPS, err = base64.StdEncoding.DecodeString(val)
|
||||||
continue
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid sprop-vps (%v)", fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
case "sprop-sps":
|
||||||
if len(tmp) != 2 {
|
var err error
|
||||||
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
|
t.SPS, err = base64.StdEncoding.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid sprop-sps (%v)", fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tmp[0] {
|
case "sprop-pps":
|
||||||
case "sprop-vps":
|
var err error
|
||||||
var err error
|
t.PPS, err = base64.StdEncoding.DecodeString(val)
|
||||||
t.VPS, err = base64.StdEncoding.DecodeString(tmp[1])
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("invalid sprop-pps (%v)", fmtp)
|
||||||
return fmt.Errorf("invalid sprop-vps (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sprop-sps":
|
|
||||||
var err error
|
|
||||||
t.SPS, err = base64.StdEncoding.DecodeString(tmp[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid sprop-sps (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sprop-pps":
|
|
||||||
var err error
|
|
||||||
t.PPS, err = base64.StdEncoding.DecodeString(tmp[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid sprop-pps (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sprop-max-don-diff":
|
|
||||||
tmp, err := strconv.ParseInt(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid sprop-max-don-diff (%v)", fmtp)
|
|
||||||
}
|
|
||||||
t.MaxDONDiff = int(tmp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "sprop-max-don-diff":
|
||||||
|
tmp, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid sprop-max-don-diff (%v)", fmtp)
|
||||||
|
}
|
||||||
|
t.MaxDONDiff = int(tmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,26 +76,22 @@ func (t *H265) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *H265) Marshal() (string, string) {
|
func (t *H265) Marshal() (string, map[string]string) {
|
||||||
t.mutex.RLock()
|
t.mutex.RLock()
|
||||||
defer t.mutex.RUnlock()
|
defer t.mutex.RUnlock()
|
||||||
|
|
||||||
var tmp []string
|
fmtp := make(map[string]string)
|
||||||
if t.VPS != nil {
|
if t.VPS != nil {
|
||||||
tmp = append(tmp, "sprop-vps="+base64.StdEncoding.EncodeToString(t.VPS))
|
fmtp["sprop-vps"] = base64.StdEncoding.EncodeToString(t.VPS)
|
||||||
}
|
}
|
||||||
if t.SPS != nil {
|
if t.SPS != nil {
|
||||||
tmp = append(tmp, "sprop-sps="+base64.StdEncoding.EncodeToString(t.SPS))
|
fmtp["sprop-sps"] = base64.StdEncoding.EncodeToString(t.SPS)
|
||||||
}
|
}
|
||||||
if t.PPS != nil {
|
if t.PPS != nil {
|
||||||
tmp = append(tmp, "sprop-pps="+base64.StdEncoding.EncodeToString(t.PPS))
|
fmtp["sprop-pps"] = base64.StdEncoding.EncodeToString(t.PPS)
|
||||||
}
|
}
|
||||||
if t.MaxDONDiff != 0 {
|
if t.MaxDONDiff != 0 {
|
||||||
tmp = append(tmp, "sprop-max-don-diff="+strconv.FormatInt(int64(t.MaxDONDiff), 10))
|
fmtp["sprop-max-don-diff"] = strconv.FormatInt(int64(t.MaxDONDiff), 10)
|
||||||
}
|
|
||||||
var fmtp string
|
|
||||||
if tmp != nil {
|
|
||||||
fmtp = strings.Join(tmp, "; ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "H265/90000", fmtp
|
return "H265/90000", fmtp
|
||||||
|
@@ -40,7 +40,11 @@ func TestH265MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "H265/90000", rtpmap)
|
require.Equal(t, "H265/90000", rtpmap)
|
||||||
require.Equal(t, "sprop-vps=AQI=; sprop-sps=AwQ=; sprop-pps=BQY=", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"sprop-vps": "AQI=",
|
||||||
|
"sprop-sps": "AwQ=",
|
||||||
|
"sprop-pps": "BQY=",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestH265DecEncoder(t *testing.T) {
|
func TestH265DecEncoder(t *testing.T) {
|
||||||
|
@@ -32,7 +32,7 @@ func (t *LPCM) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *LPCM) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *LPCM) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
switch codec {
|
switch codec {
|
||||||
@@ -68,7 +68,7 @@ func (t *LPCM) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *LPCM) Marshal() (string, string) {
|
func (t *LPCM) Marshal() (string, map[string]string) {
|
||||||
var codec string
|
var codec string
|
||||||
switch t.BitDepth {
|
switch t.BitDepth {
|
||||||
case 8:
|
case 8:
|
||||||
@@ -82,7 +82,7 @@ func (t *LPCM) Marshal() (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return codec + "/" + strconv.FormatInt(int64(t.SampleRate), 10) +
|
return codec + "/" + strconv.FormatInt(int64(t.SampleRate), 10) +
|
||||||
"/" + strconv.FormatInt(int64(t.ChannelCount), 10), ""
|
"/" + strconv.FormatInt(int64(t.ChannelCount), 10), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -34,7 +34,7 @@ func TestLPCMMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, fmt.Sprintf("L%d/96000/2", ca), rtpmap)
|
require.Equal(t, fmt.Sprintf("L%d/96000/2", ca), rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,13 +24,13 @@ func (t *MJPEG) PayloadType() uint8 {
|
|||||||
return 26
|
return 26
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MJPEG) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *MJPEG) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *MJPEG) Marshal() (string, string) {
|
func (t *MJPEG) Marshal() (string, map[string]string) {
|
||||||
return "JPEG/90000", ""
|
return "JPEG/90000", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -20,7 +20,7 @@ func TestMJPEGMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "JPEG/90000", rtpmap)
|
require.Equal(t, "JPEG/90000", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMJPEGDecEncoder(t *testing.T) {
|
func TestMJPEGDecEncoder(t *testing.T) {
|
||||||
|
@@ -22,13 +22,16 @@ func (t *MPEG2Audio) PayloadType() uint8 {
|
|||||||
return 14
|
return 14
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MPEG2Audio) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *MPEG2Audio) unmarshal(
|
||||||
|
payloadType uint8, clock string, codec string,
|
||||||
|
rtpmap string, fmtp map[string]string,
|
||||||
|
) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *MPEG2Audio) Marshal() (string, string) {
|
func (t *MPEG2Audio) Marshal() (string, map[string]string) {
|
||||||
return "", ""
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -20,5 +20,5 @@ func TestMPEG2AudioMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "", rtpmap)
|
require.Equal(t, "", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
}
|
}
|
||||||
|
@@ -22,13 +22,16 @@ func (t *MPEG2Video) PayloadType() uint8 {
|
|||||||
return 32
|
return 32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MPEG2Video) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *MPEG2Video) unmarshal(
|
||||||
|
payloadType uint8, clock string, codec string,
|
||||||
|
rtpmap string, fmtp map[string]string,
|
||||||
|
) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *MPEG2Video) Marshal() (string, string) {
|
func (t *MPEG2Video) Marshal() (string, map[string]string) {
|
||||||
return "", ""
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -20,5 +20,5 @@ func TestMPEG2VideoMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "", rtpmap)
|
require.Equal(t, "", rtpmap)
|
||||||
require.Equal(t, "", fmtp)
|
require.Equal(t, map[string]string(nil), fmtp)
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -36,75 +35,65 @@ func (t *MPEG4Audio) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MPEG4Audio) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *MPEG4Audio) unmarshal(
|
||||||
|
payloadType uint8, clock string, codec string,
|
||||||
|
rtpmap string, fmtp map[string]string,
|
||||||
|
) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
switch key {
|
||||||
kv = strings.Trim(kv, " ")
|
case "config":
|
||||||
|
enc, err := hex.DecodeString(val)
|
||||||
if len(kv) == 0 {
|
if err != nil {
|
||||||
continue
|
return fmt.Errorf("invalid AAC config (%v)", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
t.Config = &mpeg4audio.Config{}
|
||||||
if len(tmp) != 2 {
|
err = t.Config.Unmarshal(enc)
|
||||||
return fmt.Errorf("invalid fmtp (%v)", fmtp)
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid AAC config (%v)", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(tmp[0]) {
|
case "sizelength":
|
||||||
case "config":
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
enc, err := hex.DecodeString(tmp[1])
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("invalid AAC SizeLength (%v)", val)
|
||||||
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Config = &mpeg4audio.Config{}
|
|
||||||
err = t.Config.Unmarshal(enc)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sizelength":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid AAC SizeLength (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
t.SizeLength = int(val)
|
|
||||||
|
|
||||||
case "indexlength":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid AAC IndexLength (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
t.IndexLength = int(val)
|
|
||||||
|
|
||||||
case "indexdeltalength":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid AAC IndexDeltaLength (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
t.IndexDeltaLength = int(val)
|
|
||||||
}
|
}
|
||||||
|
t.SizeLength = int(n)
|
||||||
|
|
||||||
|
case "indexlength":
|
||||||
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid AAC IndexLength (%v)", val)
|
||||||
|
}
|
||||||
|
t.IndexLength = int(n)
|
||||||
|
|
||||||
|
case "indexdeltalength":
|
||||||
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid AAC IndexDeltaLength (%v)", val)
|
||||||
|
}
|
||||||
|
t.IndexDeltaLength = int(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Config == nil {
|
if t.Config == nil {
|
||||||
return fmt.Errorf("config is missing (%v)", fmtp)
|
return fmt.Errorf("config is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.SizeLength == 0 {
|
if t.SizeLength == 0 {
|
||||||
return fmt.Errorf("sizelength is missing (%v)", fmtp)
|
return fmt.Errorf("sizelength is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *MPEG4Audio) Marshal() (string, string) {
|
func (t *MPEG4Audio) Marshal() (string, map[string]string) {
|
||||||
enc, err := t.Config.Marshal()
|
enc, err := t.Config.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ""
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleRate := t.Config.SampleRate
|
sampleRate := t.Config.SampleRate
|
||||||
@@ -112,27 +101,20 @@ func (t *MPEG4Audio) Marshal() (string, string) {
|
|||||||
sampleRate = t.Config.ExtensionSampleRate
|
sampleRate = t.Config.ExtensionSampleRate
|
||||||
}
|
}
|
||||||
|
|
||||||
fmtp := "profile-level-id=1; " +
|
fmtp := make(map[string]string)
|
||||||
"mode=AAC-hbr; " +
|
|
||||||
func() string {
|
fmtp["profile-level-id"] = "1"
|
||||||
if t.SizeLength > 0 {
|
fmtp["mode"] = "AAC-hbr"
|
||||||
return fmt.Sprintf("sizelength=%d; ", t.SizeLength)
|
if t.SizeLength > 0 {
|
||||||
}
|
fmtp["sizelength"] = strconv.FormatInt(int64(t.SizeLength), 10)
|
||||||
return ""
|
}
|
||||||
}() +
|
if t.IndexLength > 0 {
|
||||||
func() string {
|
fmtp["indexlength"] = strconv.FormatInt(int64(t.IndexLength), 10)
|
||||||
if t.IndexLength > 0 {
|
}
|
||||||
return fmt.Sprintf("indexlength=%d; ", t.IndexLength)
|
if t.IndexDeltaLength > 0 {
|
||||||
}
|
fmtp["indexdeltalength"] = strconv.FormatInt(int64(t.IndexDeltaLength), 10)
|
||||||
return ""
|
}
|
||||||
}() +
|
fmtp["config"] = hex.EncodeToString(enc)
|
||||||
func() string {
|
|
||||||
if t.IndexDeltaLength > 0 {
|
|
||||||
return fmt.Sprintf("indexdeltalength=%d; ", t.IndexDeltaLength)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}() +
|
|
||||||
"config=" + hex.EncodeToString(enc)
|
|
||||||
|
|
||||||
return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) +
|
return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) +
|
||||||
"/" + strconv.FormatInt(int64(t.Config.ChannelCount), 10), fmtp
|
"/" + strconv.FormatInt(int64(t.Config.ChannelCount), 10), fmtp
|
||||||
|
@@ -42,8 +42,14 @@ func TestMPEG4AudioMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "mpeg4-generic/48000/2", rtpmap)
|
require.Equal(t, "mpeg4-generic/48000/2", rtpmap)
|
||||||
require.Equal(t, "profile-level-id=1; mode=AAC-hbr; sizelength=13;"+
|
require.Equal(t, map[string]string{
|
||||||
" indexlength=3; indexdeltalength=3; config=1190", fmtp)
|
"profile-level-id": "1",
|
||||||
|
"mode": "AAC-hbr",
|
||||||
|
"sizelength": "13",
|
||||||
|
"indexlength": "3",
|
||||||
|
"indexdeltalength": "3",
|
||||||
|
"config": "1190",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMPEG4AudioDecEncoder(t *testing.T) {
|
func TestMPEG4AudioDecEncoder(t *testing.T) {
|
||||||
|
@@ -33,7 +33,7 @@ func (t *Opus) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
tmp := strings.SplitN(clock, "/", 2)
|
tmp := strings.SplitN(clock, "/", 2)
|
||||||
@@ -57,22 +57,9 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
return fmt.Errorf("invalid channel count: %d", channelCount)
|
return fmt.Errorf("invalid channel count: %d", channelCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
if key == "sprop-stereo" {
|
||||||
kv = strings.Trim(kv, " ")
|
t.IsStereo = (val == "1")
|
||||||
|
|
||||||
if len(kv) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
|
||||||
if len(tmp) != 2 {
|
|
||||||
return fmt.Errorf("invalid fmtp (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ToLower(tmp[0]) == "sprop-stereo" {
|
|
||||||
t.IsStereo = (tmp[1] == "1")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,13 +67,15 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *Opus) Marshal() (string, string) {
|
func (t *Opus) Marshal() (string, map[string]string) {
|
||||||
fmtp := "sprop-stereo=" + func() string {
|
fmtp := map[string]string{
|
||||||
if t.IsStereo {
|
"sprop-stereo": func() string {
|
||||||
return "1"
|
if t.IsStereo {
|
||||||
}
|
return "1"
|
||||||
return "0"
|
}
|
||||||
}()
|
return "0"
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
|
||||||
// RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the
|
// RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the
|
||||||
// number of channels MUST be 2.
|
// number of channels MUST be 2.
|
||||||
|
@@ -26,7 +26,9 @@ func TestOpusMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "opus/48000/2", rtpmap)
|
require.Equal(t, "opus/48000/2", rtpmap)
|
||||||
require.Equal(t, "sprop-stereo=1", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"sprop-stereo": "1",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpusDecEncoder(t *testing.T) {
|
func TestOpusDecEncoder(t *testing.T) {
|
||||||
|
@@ -32,7 +32,7 @@ func (t *Vorbis) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Vorbis) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *Vorbis) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
tmp := strings.SplitN(clock, "/", 2)
|
tmp := strings.SplitN(clock, "/", 2)
|
||||||
@@ -52,42 +52,33 @@ func (t *Vorbis) unmarshal(payloadType uint8, clock string, codec string, rtpmap
|
|||||||
}
|
}
|
||||||
t.ChannelCount = int(channelCount)
|
t.ChannelCount = int(channelCount)
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
if key == "configuration" {
|
||||||
kv = strings.Trim(kv, " ")
|
conf, err := base64.StdEncoding.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
if len(kv) == 0 {
|
return fmt.Errorf("invalid AAC config (%v)", val)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
t.Configuration = conf
|
||||||
if len(tmp) != 2 {
|
|
||||||
return fmt.Errorf("invalid fmtp (%v)", fmtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmp[0] == "configuration" {
|
|
||||||
conf, err := base64.StdEncoding.DecodeString(tmp[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid AAC config (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Configuration = conf
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Configuration == nil {
|
if t.Configuration == nil {
|
||||||
return fmt.Errorf("config is missing (%v)", fmtp)
|
return fmt.Errorf("config is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *Vorbis) Marshal() (string, string) {
|
func (t *Vorbis) Marshal() (string, map[string]string) {
|
||||||
|
fmtp := map[string]string{
|
||||||
|
"configuration": base64.StdEncoding.EncodeToString(t.Configuration),
|
||||||
|
}
|
||||||
|
|
||||||
return "VORBIS/" + strconv.FormatInt(int64(t.SampleRate), 10) +
|
return "VORBIS/" + strconv.FormatInt(int64(t.SampleRate), 10) +
|
||||||
"/" + strconv.FormatInt(int64(t.ChannelCount), 10),
|
"/" + strconv.FormatInt(int64(t.ChannelCount), 10),
|
||||||
"configuration=" + base64.StdEncoding.EncodeToString(t.Configuration)
|
fmtp
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// PTSEqualsDTS implements Format.
|
||||||
|
@@ -30,5 +30,7 @@ func TestVorbisMediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "VORBIS/48000/2", rtpmap)
|
require.Equal(t, "VORBIS/48000/2", rtpmap)
|
||||||
require.Equal(t, "configuration=AQIDBA==", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"configuration": "AQIDBA==",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package format
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -32,39 +31,26 @@ func (t *VP8) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *VP8) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *VP8) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
switch key {
|
||||||
kv = strings.Trim(kv, " ")
|
case "max-fr":
|
||||||
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
if len(kv) == 0 {
|
if err != nil {
|
||||||
continue
|
return fmt.Errorf("invalid max-fr (%v)", val)
|
||||||
}
|
}
|
||||||
|
v2 := int(n)
|
||||||
|
t.MaxFR = &v2
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
case "max-fs":
|
||||||
if len(tmp) != 2 {
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("invalid max-fs (%v)", val)
|
||||||
|
|
||||||
switch tmp[0] {
|
|
||||||
case "max-fr":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid max-fr (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
v2 := int(val)
|
|
||||||
t.MaxFR = &v2
|
|
||||||
|
|
||||||
case "max-fs":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid max-fs (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
v2 := int(val)
|
|
||||||
t.MaxFS = &v2
|
|
||||||
}
|
}
|
||||||
|
v2 := int(n)
|
||||||
|
t.MaxFS = &v2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,17 +58,13 @@ func (t *VP8) unmarshal(payloadType uint8, clock string, codec string, rtpmap st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *VP8) Marshal() (string, string) {
|
func (t *VP8) Marshal() (string, map[string]string) {
|
||||||
var tmp []string
|
fmtp := make(map[string]string)
|
||||||
if t.MaxFR != nil {
|
if t.MaxFR != nil {
|
||||||
tmp = append(tmp, "max-fr="+strconv.FormatInt(int64(*t.MaxFR), 10))
|
fmtp["max-fr"] = strconv.FormatInt(int64(*t.MaxFR), 10)
|
||||||
}
|
}
|
||||||
if t.MaxFS != nil {
|
if t.MaxFS != nil {
|
||||||
tmp = append(tmp, "max-fs="+strconv.FormatInt(int64(*t.MaxFS), 10))
|
fmtp["max-fs"] = strconv.FormatInt(int64(*t.MaxFS), 10)
|
||||||
}
|
|
||||||
var fmtp string
|
|
||||||
if tmp != nil {
|
|
||||||
fmtp = strings.Join(tmp, ";")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "VP8/90000", fmtp
|
return "VP8/90000", fmtp
|
||||||
|
@@ -28,7 +28,10 @@ func TestVP8MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "VP8/90000", rtpmap)
|
require.Equal(t, "VP8/90000", rtpmap)
|
||||||
require.Equal(t, "max-fr=123;max-fs=456", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"max-fr": "123",
|
||||||
|
"max-fs": "456",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVP8DecEncoder(t *testing.T) {
|
func TestVP8DecEncoder(t *testing.T) {
|
||||||
|
@@ -3,7 +3,6 @@ package format
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -33,47 +32,34 @@ func (t *VP9) PayloadType() uint8 {
|
|||||||
return t.PayloadTyp
|
return t.PayloadTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *VP9) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
|
func (t *VP9) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error {
|
||||||
t.PayloadTyp = payloadType
|
t.PayloadTyp = payloadType
|
||||||
|
|
||||||
if fmtp != "" {
|
for key, val := range fmtp {
|
||||||
for _, kv := range strings.Split(fmtp, ";") {
|
switch key {
|
||||||
kv = strings.Trim(kv, " ")
|
case "max-fr":
|
||||||
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
if len(kv) == 0 {
|
if err != nil {
|
||||||
continue
|
return fmt.Errorf("invalid max-fr (%v)", val)
|
||||||
}
|
}
|
||||||
|
v2 := int(n)
|
||||||
|
t.MaxFR = &v2
|
||||||
|
|
||||||
tmp := strings.SplitN(kv, "=", 2)
|
case "max-fs":
|
||||||
if len(tmp) != 2 {
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
return fmt.Errorf("invalid fmtp attribute (%v)", fmtp)
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid max-fs (%v)", val)
|
||||||
}
|
}
|
||||||
|
v2 := int(n)
|
||||||
|
t.MaxFS = &v2
|
||||||
|
|
||||||
switch tmp[0] {
|
case "profile-id":
|
||||||
case "max-fr":
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("invalid profile-id (%v)", val)
|
||||||
return fmt.Errorf("invalid max-fr (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
v2 := int(val)
|
|
||||||
t.MaxFR = &v2
|
|
||||||
|
|
||||||
case "max-fs":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid max-fs (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
v2 := int(val)
|
|
||||||
t.MaxFS = &v2
|
|
||||||
|
|
||||||
case "profile-id":
|
|
||||||
val, err := strconv.ParseUint(tmp[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid profile-id (%v)", tmp[1])
|
|
||||||
}
|
|
||||||
v2 := int(val)
|
|
||||||
t.ProfileID = &v2
|
|
||||||
}
|
}
|
||||||
|
v2 := int(n)
|
||||||
|
t.ProfileID = &v2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,20 +67,16 @@ func (t *VP9) unmarshal(payloadType uint8, clock string, codec string, rtpmap st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *VP9) Marshal() (string, string) {
|
func (t *VP9) Marshal() (string, map[string]string) {
|
||||||
var tmp []string
|
fmtp := make(map[string]string)
|
||||||
if t.MaxFR != nil {
|
if t.MaxFR != nil {
|
||||||
tmp = append(tmp, "max-fr="+strconv.FormatInt(int64(*t.MaxFR), 10))
|
fmtp["max-fr"] = strconv.FormatInt(int64(*t.MaxFR), 10)
|
||||||
}
|
}
|
||||||
if t.MaxFS != nil {
|
if t.MaxFS != nil {
|
||||||
tmp = append(tmp, "max-fs="+strconv.FormatInt(int64(*t.MaxFS), 10))
|
fmtp["max-fs"] = strconv.FormatInt(int64(*t.MaxFS), 10)
|
||||||
}
|
}
|
||||||
if t.ProfileID != nil {
|
if t.ProfileID != nil {
|
||||||
tmp = append(tmp, "profile-id="+strconv.FormatInt(int64(*t.ProfileID), 10))
|
fmtp["profile-id"] = strconv.FormatInt(int64(*t.ProfileID), 10)
|
||||||
}
|
|
||||||
var fmtp string
|
|
||||||
if tmp != nil {
|
|
||||||
fmtp = strings.Join(tmp, ";")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "VP9/90000", fmtp
|
return "VP9/90000", fmtp
|
||||||
|
@@ -30,7 +30,11 @@ func TestVP9MediaDescription(t *testing.T) {
|
|||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
require.Equal(t, "VP9/90000", rtpmap)
|
require.Equal(t, "VP9/90000", rtpmap)
|
||||||
require.Equal(t, "max-fr=123;max-fs=456;profile-id=789", fmtp)
|
require.Equal(t, map[string]string{
|
||||||
|
"max-fr": "123",
|
||||||
|
"max-fs": "456",
|
||||||
|
"profile-id": "789",
|
||||||
|
}, fmtp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVP9DecEncoder(t *testing.T) {
|
func TestVP9DecEncoder(t *testing.T) {
|
||||||
|
@@ -4,6 +4,7 @@ package media
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -130,10 +131,23 @@ func (m Media) Marshal() *psdp.MediaDescription {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if fmtp != "" {
|
if len(fmtp) != 0 {
|
||||||
|
keys := make([]string, len(fmtp))
|
||||||
|
i := 0
|
||||||
|
for key := range fmtp {
|
||||||
|
keys[i] = key
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
tmp := make([]string, len(fmtp))
|
||||||
|
for i, key := range keys {
|
||||||
|
tmp[i] = key + "=" + fmtp[key]
|
||||||
|
}
|
||||||
|
|
||||||
md.Attributes = append(md.Attributes, psdp.Attribute{
|
md.Attributes = append(md.Attributes, psdp.Attribute{
|
||||||
Key: "fmtp",
|
Key: "fmtp",
|
||||||
Value: typ + " " + fmtp,
|
Value: typ + " " + strings.Join(tmp, "; "),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ var casesMedias = []struct {
|
|||||||
"m=video 0 RTP/AVP 97\r\n" +
|
"m=video 0 RTP/AVP 97\r\n" +
|
||||||
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=v\r\n" +
|
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=v\r\n" +
|
||||||
"a=rtpmap:97 H264/90000\r\n" +
|
"a=rtpmap:97 H264/90000\r\n" +
|
||||||
"a=fmtp:97 packetization-mode=1; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=; profile-level-id=640028\r\n" +
|
"a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" +
|
||||||
"m=audio 0 RTP/AVP 0\r\n" +
|
"m=audio 0 RTP/AVP 0\r\n" +
|
||||||
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=a\r\n" +
|
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=a\r\n" +
|
||||||
"a=recvonly\r\n" +
|
"a=recvonly\r\n" +
|
||||||
@@ -302,8 +302,10 @@ var casesMedias = []struct {
|
|||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 97,
|
PayloadTyp: 97,
|
||||||
RTPMap: "rtx/90000",
|
RTPMap: "rtx/90000",
|
||||||
FMTP: "apt=96",
|
FMTP: map[string]string{
|
||||||
ClockRat: 90000,
|
"apt": "96",
|
||||||
|
},
|
||||||
|
ClockRat: 90000,
|
||||||
},
|
},
|
||||||
&format.VP9{
|
&format.VP9{
|
||||||
PayloadTyp: 98,
|
PayloadTyp: 98,
|
||||||
@@ -311,8 +313,10 @@ var casesMedias = []struct {
|
|||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 99,
|
PayloadTyp: 99,
|
||||||
RTPMap: "rtx/90000",
|
RTPMap: "rtx/90000",
|
||||||
FMTP: "apt=98",
|
FMTP: map[string]string{
|
||||||
ClockRat: 90000,
|
"apt": "98",
|
||||||
|
},
|
||||||
|
ClockRat: 90000,
|
||||||
},
|
},
|
||||||
&format.H264{
|
&format.H264{
|
||||||
PayloadTyp: 100,
|
PayloadTyp: 100,
|
||||||
@@ -321,8 +325,10 @@ var casesMedias = []struct {
|
|||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 101,
|
PayloadTyp: 101,
|
||||||
RTPMap: "rtx/90000",
|
RTPMap: "rtx/90000",
|
||||||
FMTP: "apt=100",
|
FMTP: map[string]string{
|
||||||
ClockRat: 90000,
|
"apt": "100",
|
||||||
|
},
|
||||||
|
ClockRat: 90000,
|
||||||
},
|
},
|
||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 127,
|
PayloadTyp: 127,
|
||||||
@@ -332,8 +338,10 @@ var casesMedias = []struct {
|
|||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 124,
|
PayloadTyp: 124,
|
||||||
RTPMap: "rtx/90000",
|
RTPMap: "rtx/90000",
|
||||||
FMTP: "apt=127",
|
FMTP: map[string]string{
|
||||||
ClockRat: 90000,
|
"apt": "127",
|
||||||
|
},
|
||||||
|
ClockRat: 90000,
|
||||||
},
|
},
|
||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 125,
|
PayloadTyp: 125,
|
||||||
@@ -364,8 +372,8 @@ var casesMedias = []struct {
|
|||||||
"m=video 0 RTP/AVP 96 98\r\n" +
|
"m=video 0 RTP/AVP 96 98\r\n" +
|
||||||
"a=control\r\n" +
|
"a=control\r\n" +
|
||||||
"a=rtpmap:96 H264/90000\r\n" +
|
"a=rtpmap:96 H264/90000\r\n" +
|
||||||
"a=fmtp:96 packetization-mode=1;" +
|
"a=fmtp:96 packetization-mode=1; profile-level-id=4D002A; " +
|
||||||
" sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==; profile-level-id=4D002A\r\n" +
|
"sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==\r\n" +
|
||||||
"a=rtpmap:98 MetaData\r\n",
|
"a=rtpmap:98 MetaData\r\n",
|
||||||
Medias{
|
Medias{
|
||||||
{
|
{
|
||||||
@@ -511,8 +519,10 @@ func TestMediasFindFormat(t *testing.T) {
|
|||||||
tr := &format.Generic{
|
tr := &format.Generic{
|
||||||
PayloadTyp: 97,
|
PayloadTyp: 97,
|
||||||
RTPMap: "rtx/90000",
|
RTPMap: "rtx/90000",
|
||||||
FMTP: "apt=96",
|
FMTP: map[string]string{
|
||||||
ClockRat: 90000,
|
"apt": "96",
|
||||||
|
},
|
||||||
|
ClockRat: 90000,
|
||||||
}
|
}
|
||||||
|
|
||||||
md := &Media{
|
md := &Media{
|
||||||
|
Reference in New Issue
Block a user