mirror of https://github.com/q191201771/lal.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
3.7 KiB
Go
199 lines
3.7 KiB
Go
5 years ago
|
// Copyright 2020, Chef. All rights reserved.
|
||
|
// https://github.com/q191201771/lal
|
||
|
//
|
||
|
// Use of this source code is governed by a MIT-style license
|
||
|
// that can be found in the License file.
|
||
|
//
|
||
|
// Author: Chef (191201771@qq.com)
|
||
|
|
||
5 years ago
|
package sdp
|
||
5 years ago
|
|
||
|
import (
|
||
5 years ago
|
"encoding/base64"
|
||
5 years ago
|
"errors"
|
||
|
"strconv"
|
||
|
"strings"
|
||
5 years ago
|
|
||
|
"github.com/q191201771/lal/pkg/base"
|
||
5 years ago
|
)
|
||
|
|
||
|
var ErrSDP = errors.New("lal.sdp: fxxk")
|
||
|
|
||
5 years ago
|
type SDPContext struct {
|
||
5 years ago
|
ARTPMapList []ARTPMap
|
||
|
AFmtPBaseList []AFmtPBase
|
||
5 years ago
|
}
|
||
|
|
||
|
type ARTPMap struct {
|
||
|
PayloadType int
|
||
|
EncodingName string
|
||
5 years ago
|
ClockRate int
|
||
5 years ago
|
EncodingParameters string
|
||
|
}
|
||
|
|
||
5 years ago
|
type AFmtPBase struct {
|
||
5 years ago
|
Format int // same as PayloadType
|
||
|
Parameters map[string]string // name -> value
|
||
5 years ago
|
}
|
||
|
|
||
5 years ago
|
// 例子见单元测试
|
||
5 years ago
|
func ParseSDP(b []byte) (SDPContext, error) {
|
||
|
var sdpCtx SDPContext
|
||
5 years ago
|
|
||
5 years ago
|
s := string(b)
|
||
|
lines := strings.Split(s, "\r\n")
|
||
|
for _, line := range lines {
|
||
|
if strings.HasPrefix(line, "a=rtpmap") {
|
||
5 years ago
|
aRTPMap, err := ParseARTPMap(line)
|
||
5 years ago
|
if err != nil {
|
||
5 years ago
|
return sdpCtx, err
|
||
5 years ago
|
}
|
||
5 years ago
|
sdpCtx.ARTPMapList = append(sdpCtx.ARTPMapList, aRTPMap)
|
||
5 years ago
|
}
|
||
5 years ago
|
if strings.HasPrefix(line, "a=fmtp") {
|
||
5 years ago
|
aFmtPBase, err := ParseAFmtPBase(line)
|
||
|
if err != nil {
|
||
5 years ago
|
return sdpCtx, err
|
||
5 years ago
|
}
|
||
5 years ago
|
sdpCtx.AFmtPBaseList = append(sdpCtx.AFmtPBaseList, aFmtPBase)
|
||
5 years ago
|
}
|
||
5 years ago
|
}
|
||
|
|
||
5 years ago
|
return sdpCtx, nil
|
||
5 years ago
|
}
|
||
|
|
||
5 years ago
|
// 例子见单元测试
|
||
5 years ago
|
func ParseARTPMap(s string) (ret ARTPMap, err error) {
|
||
5 years ago
|
// rfc 3640 3.3.1. General
|
||
|
// rfc 3640 3.3.6. High Bit-rate AAC
|
||
|
//
|
||
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>]
|
||
|
//
|
||
|
|
||
5 years ago
|
items := strings.SplitN(s, ":", 2)
|
||
5 years ago
|
if len(items) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
5 years ago
|
items = strings.SplitN(items[1], " ", 2)
|
||
5 years ago
|
if len(items) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
ret.PayloadType, err = strconv.Atoi(items[0])
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
5 years ago
|
items = strings.SplitN(items[1], "/", 3)
|
||
5 years ago
|
switch len(items) {
|
||
|
case 3:
|
||
|
ret.EncodingParameters = items[2]
|
||
|
fallthrough
|
||
|
case 2:
|
||
|
ret.EncodingName = items[0]
|
||
5 years ago
|
ret.ClockRate, err = strconv.Atoi(items[1])
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
5 years ago
|
default:
|
||
|
err = ErrSDP
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
5 years ago
|
// 例子见单元测试
|
||
|
func ParseAFmtPBase(s string) (ret AFmtPBase, err error) {
|
||
5 years ago
|
// rfc 3640 4.4.1. The a=fmtp Keyword
|
||
|
//
|
||
|
// a=fmtp:<format> <parameter name>=<value>[; <parameter name>=<value>]
|
||
|
//
|
||
5 years ago
|
|
||
|
ret.Parameters = make(map[string]string)
|
||
|
|
||
|
items := strings.SplitN(s, ":", 2)
|
||
|
if len(items) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
|
||
|
items = strings.SplitN(items[1], " ", 2)
|
||
|
if len(items) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
|
||
|
ret.Format, err = strconv.Atoi(items[0])
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
items = strings.Split(items[1], ";")
|
||
|
for _, pp := range items {
|
||
|
pp = strings.TrimSpace(pp)
|
||
|
kv := strings.SplitN(pp, "=", 2)
|
||
|
if len(kv) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
ret.Parameters[kv[0]] = kv[1]
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
5 years ago
|
func ParseASC(a AFmtPBase) ([]byte, error) {
|
||
|
if a.Format != base.RTPPacketTypeAAC {
|
||
|
return nil, ErrSDP
|
||
|
}
|
||
|
|
||
|
v, ok := a.Parameters["config"]
|
||
|
if !ok {
|
||
|
return nil, ErrSDP
|
||
|
}
|
||
|
if len(v) != 4 {
|
||
|
return nil, ErrSDP
|
||
|
}
|
||
|
|
||
|
f, err := strconv.ParseInt(v[0:2], 16, 0)
|
||
|
if err != nil {
|
||
|
return nil, ErrSDP
|
||
|
}
|
||
|
s, err := strconv.ParseInt(v[2:], 16, 0)
|
||
|
if err != nil {
|
||
|
return nil, ErrSDP
|
||
|
}
|
||
|
r := make([]byte, 2)
|
||
|
r[0] = uint8(f)
|
||
|
r[1] = uint8(s)
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
5 years ago
|
// 例子见单元测试
|
||
|
func ParseSPSPPS(a AFmtPBase) (sps, pps []byte, err error) {
|
||
5 years ago
|
if a.Format != base.RTPPacketTypeAVC {
|
||
5 years ago
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
|
||
5 years ago
|
v, ok := a.Parameters["sprop-parameter-sets"]
|
||
5 years ago
|
if !ok {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
|
||
|
items := strings.SplitN(v, ",", 2)
|
||
|
if len(items) != 2 {
|
||
|
err = ErrSDP
|
||
|
return
|
||
|
}
|
||
|
|
||
|
sps, err = base64.StdEncoding.DecodeString(items[0])
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
pps, err = base64.StdEncoding.DecodeString(items[1])
|
||
5 years ago
|
|
||
|
return
|
||
|
}
|