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