// 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) package sdp import ( "encoding/base64" "errors" "strconv" "strings" "github.com/q191201771/lal/pkg/base" ) // rfc4566 var ErrSDP = errors.New("lal.sdp: fxxk") const ( ARTPMapEncodingNameH265 = "H265" ARTPMapEncodingNameH264 = "H264" ARTPMapEncodingNameAAC = "MPEG4-GENERIC" ) type LogicContext struct { HasAudio bool HasVideo bool AudioClockRate int VideoClockRate int AudioPayloadTypeOrigin int VideoPayloadTypeOrigin int AudioPayloadType base.AVPacketPT VideoPayloadType base.AVPacketPT AudioAControl string VideoAControl string ASC []byte VPS []byte SPS []byte PPS []byte } type MediaDesc struct { M M ARTPMap ARTPMap AFmtBase AFmtPBase AControl AControl } type RawContext struct { MediaDescList []MediaDesc } type M struct { Media string } type ARTPMap struct { PayloadType int EncodingName string ClockRate int EncodingParameters string } type AFmtPBase struct { Format int // same as PayloadType Parameters map[string]string // name -> value } type AControl struct { Value string } func ParseSDP2LogicContext(b []byte) (LogicContext, error) { var ret LogicContext c, err := ParseSDP2RawContext(b) if err != nil { return ret, err } for _, md := range c.MediaDescList { switch md.M.Media { case "audio": ret.HasAudio = true ret.AudioClockRate = md.ARTPMap.ClockRate ret.AudioAControl = md.AControl.Value ret.AudioPayloadTypeOrigin = md.ARTPMap.PayloadType if md.ARTPMap.EncodingName == ARTPMapEncodingNameAAC { ret.AudioPayloadType = base.AVPacketPTAAC ret.ASC, err = ParseASC(md.AFmtBase) if err != nil { return ret, err } } else { ret.AudioPayloadType = base.AVPacketPTUnknown } case "video": ret.HasVideo = true ret.VideoClockRate = md.ARTPMap.ClockRate ret.VideoAControl = md.AControl.Value ret.VideoPayloadTypeOrigin = md.ARTPMap.PayloadType switch md.ARTPMap.EncodingName { case ARTPMapEncodingNameH264: ret.VideoPayloadType = base.AVPacketPTAVC ret.SPS, ret.PPS, err = ParseSPSPPS(md.AFmtBase) if err != nil { return ret, err } case ARTPMapEncodingNameH265: ret.VideoPayloadType = base.AVPacketPTHEVC ret.VPS, ret.SPS, ret.PPS, err = ParseVPSSPSPPS(md.AFmtBase) if err != nil { return ret, err } default: ret.VideoPayloadType = base.AVPacketPTUnknown } } } return ret, nil } // 例子见单元测试 func ParseSDP2RawContext(b []byte) (RawContext, error) { var ( sdpCtx RawContext md *MediaDesc ) s := string(b) lines := strings.Split(s, "\r\n") for _, line := range lines { if strings.HasPrefix(line, "m=") { m, err := ParseM(line) if err != nil { return sdpCtx, err } if md != nil { sdpCtx.MediaDescList = append(sdpCtx.MediaDescList, *md) } md = &MediaDesc{ M: m, } } if strings.HasPrefix(line, "a=rtpmap") { aRTPMap, err := ParseARTPMap(line) if err != nil { return sdpCtx, err } if md == nil { continue } md.ARTPMap = aRTPMap } if strings.HasPrefix(line, "a=fmtp") { aFmtPBase, err := ParseAFmtPBase(line) if err != nil { return sdpCtx, err } if md == nil { continue } md.AFmtBase = aFmtPBase } if strings.HasPrefix(line, "a=control") { aControl, err := ParseAControl(line) if err != nil { return sdpCtx, err } if md == nil { continue } md.AControl = aControl } } if md != nil { sdpCtx.MediaDescList = append(sdpCtx.MediaDescList, *md) } return sdpCtx, nil } func ParseM(s string) (ret M, err error) { ss := strings.TrimPrefix(s, "m=") items := strings.Split(ss, " ") if len(items) < 1 { return ret, ErrSDP } ret.Media = items[0] return } // 例子见单元测试 func ParseARTPMap(s string) (ret ARTPMap, err error) { // rfc 3640 3.3.1. General // rfc 3640 3.3.6. High Bit-rate AAC // // a=rtpmap: /[/] // 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.PayloadType, err = strconv.Atoi(items[0]) if err != nil { return } items = strings.SplitN(items[1], "/", 3) switch len(items) { case 3: ret.EncodingParameters = items[2] fallthrough case 2: ret.EncodingName = items[0] ret.ClockRate, err = strconv.Atoi(items[1]) if err != nil { return } default: err = ErrSDP } return } // 例子见单元测试 func ParseAFmtPBase(s string) (ret AFmtPBase, err error) { // rfc 3640 4.4.1. The a=fmtp Keyword // // a=fmtp: =[; =] // 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 } func ParseAControl(s string) (ret AControl, err error) { if !strings.HasPrefix(s, "a=control:") { err = ErrSDP return } ret.Value = strings.TrimPrefix(s, "a=control:") return } 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 || (len(v)%2) != 0 { return nil, ErrSDP } l := len(v) / 2 r := make([]byte, l) for i := 0; i < l; i++ { b, err := strconv.ParseInt(v[i*2:i*2+2], 16, 0) if err != nil { return nil, ErrSDP } r[i] = uint8(b) } return r, nil } func ParseVPSSPSPPS(a AFmtPBase) (vps, sps, pps []byte, err error) { v, ok := a.Parameters["sprop-vps"] if !ok { return nil, nil, nil, ErrSDP } if vps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err } v, ok = a.Parameters["sprop-sps"] if !ok { return nil, nil, nil, ErrSDP } if sps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err } v, ok = a.Parameters["sprop-pps"] if !ok { return nil, nil, nil, ErrSDP } if pps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err } return } // 解析AVC/H264的sps,pps // 例子见单元测试 func ParseSPSPPS(a AFmtPBase) (sps, pps []byte, err error) { v, ok := a.Parameters["sprop-parameter-sets"] if !ok { return nil, nil, ErrSDP } items := strings.SplitN(v, ",", 2) if len(items) != 2 { return nil, nil, ErrSDP } sps, err = base64.StdEncoding.DecodeString(items[0]) if err != nil { return nil, nil, ErrSDP } pps, err = base64.StdEncoding.DecodeString(items[1]) return }