Browse Source

Reform SDP library.

pull/2/head
Justine Alexandra Roberts Tunney 11 years ago
parent
commit
5b787b4153
7 changed files with 146 additions and 167 deletions
  1. +11
    -14
      sdp/codec.go
  2. +27
    -38
      sdp/codecs.go
  3. +14
    -24
      sdp/media.go
  4. +29
    -22
      sdp/origin.go
  5. +59
    -59
      sdp/sdp.go
  6. +0
    -10
      sdp/sdp_test.go
  7. +6
    -0
      util/util.go

+ 11
- 14
sdp/codec.go View File

@ -2,7 +2,6 @@ package sdp
import (
"bytes"
"errors"
"strconv"
)
@ -17,25 +16,23 @@ type Codec struct {
Fmtp string // some extra info; i.e. dtmf might set as "0-16"
}
func (codec *Codec) Append(b *bytes.Buffer) error {
if codec.Name == "" {
return errors.New("Codec.Name is blank")
}
if codec.Rate < 8000 {
return errors.New("Codec.Rate < 8000")
}
func (codec *Codec) Append(b *bytes.Buffer) {
b.WriteString("a=rtpmap:")
b.WriteString(strconv.Itoa(int(codec.PT)) + " ")
b.WriteString(codec.Name + "/")
b.WriteString(strconv.FormatInt(int64(codec.PT), 10))
b.WriteString(" ")
b.WriteString(codec.Name)
b.WriteString("/")
b.WriteString(strconv.Itoa(codec.Rate))
if codec.Param != "" {
b.WriteString("/" + codec.Param)
b.WriteString("/")
b.WriteString(codec.Param)
}
b.WriteString("\r\n")
if codec.Fmtp != "" {
b.WriteString("a=fmtp:")
b.WriteString(strconv.Itoa(int(codec.PT)) + " ")
b.WriteString(codec.Fmtp + "\r\n")
b.WriteString(strconv.FormatInt(int64(codec.PT), 10))
b.WriteString(" ")
b.WriteString(codec.Fmtp)
b.WriteString("\r\n")
}
return nil
}

+ 27
- 38
sdp/codecs.go View File

@ -19,44 +19,33 @@
package sdp
var (
ULAWCodec = &Codec{PT: 0, Name: "PCMU", Rate: 8000}
DTMFCodec = &Codec{PT: 101, Name: "telephone-event", Rate: 8000, Fmtp: "0-16"}
ULAWCodec = Codec{PT: 0, Name: "PCMU", Rate: 8000}
DTMFCodec = Codec{PT: 101, Name: "telephone-event", Rate: 8000, Fmtp: "0-16"}
StandardCodecs = map[uint8]*Codec{
// G.711 μ-Law is the de-facto codec (SpanDSP g711.h)
0: ULAWCodec,
// Uncool codec asterisk ppl like (SpanDSP gsm0610.h)
3: &Codec{PT: 3, Name: "GSM", Rate: 8000},
4: &Codec{PT: 4, Name: "G723", Rate: 8000},
// adaptive pulse code modulation (SpanDSP ima_adpcm.h)
5: &Codec{PT: 5, Name: "DVI4", Rate: 8000},
6: &Codec{PT: 6, Name: "DVI4", Rate: 16000},
// chat with your friends ww2 field marshall style (SpanDSP lpc10.h)
7: &Codec{PT: 7, Name: "LPC", Rate: 8000},
// G.711 variant of μ-Law used in yurop (SpanDSP g711.h)
8: &Codec{PT: 8, Name: "PCMA", Rate: 8000},
// used for Polycom HD Voice; rate actually 16khz lol (SpanDSP g722.h)
9: &Codec{PT: 9, Name: "G722", Rate: 8000},
// 16-bit signed PCM stereo/mono (mind your MTU; adjust ptime)
10: &Codec{PT: 10, Name: "L16", Rate: 44100, Param: "2"},
11: &Codec{PT: 11, Name: "L16", Rate: 44100},
12: &Codec{PT: 12, Name: "QCELP", Rate: 8000},
// RFC3389 comfort noise
13: &Codec{PT: 13, Name: "CN", Rate: 8000},
14: &Codec{PT: 14, Name: "MPA", Rate: 90000},
15: &Codec{PT: 15, Name: "G728", Rate: 8000},
16: &Codec{PT: 16, Name: "DVI4", Rate: 11025},
17: &Codec{PT: 17, Name: "DVI4", Rate: 22050},
// best voice codec (if you got $10 bucks)
18: &Codec{PT: 18, Name: "G729", Rate: 8000},
25: &Codec{PT: 25, Name: "CelB", Rate: 90000},
26: &Codec{PT: 26, Name: "JPEG", Rate: 90000},
28: &Codec{PT: 28, Name: "nv", Rate: 90000},
// RFC4587 video
31: &Codec{PT: 31, Name: "H261", Rate: 90000},
32: &Codec{PT: 32, Name: "MPV", Rate: 90000},
33: &Codec{PT: 33, Name: "MP2T", Rate: 90000},
// $$$ video
34: &Codec{PT: 34, Name: "H263", Rate: 90000},
StandardCodecs = map[uint8]Codec{
0: ULAWCodec, // G.711 μ-Law is the de-facto codec (SpanDSP g711.h)
3: Codec{PT: 3, Name: "GSM", Rate: 8000}, // Uncool codec asterisk ppl like (SpanDSP gsm0610.h)
4: Codec{PT: 4, Name: "G723", Rate: 8000}, // Worthless.
5: Codec{PT: 5, Name: "DVI4", Rate: 8000}, // Adaptive pulse code modulation (SpanDSP ima_adpcm.h)
6: Codec{PT: 6, Name: "DVI4", Rate: 16000}, // Adaptive pulse code modulation 16khz (SpanDSP ima_adpcm.h)
7: Codec{PT: 7, Name: "LPC", Rate: 8000}, // Chat with your friends ww2 field marshall style (SpanDSP lpc10.h)
8: Codec{PT: 8, Name: "PCMA", Rate: 8000}, // G.711 variant of μ-Law used in yurop (SpanDSP g711.h)
9: Codec{PT: 9, Name: "G722", Rate: 8000}, // Used for Polycom HD Voice; Rate actually 16khz LOL (SpanDSP g722.h)
10: Codec{PT: 10, Name: "L16", Rate: 44100, Param: "2"}, // 16-bit signed PCM stereo/mono (mind your MTU; adjust ptime)
11: Codec{PT: 11, Name: "L16", Rate: 44100},
12: Codec{PT: 12, Name: "QCELP", Rate: 8000},
13: Codec{PT: 13, Name: "CN", Rate: 8000}, // RFC3389 comfort noise
14: Codec{PT: 14, Name: "MPA", Rate: 90000},
15: Codec{PT: 15, Name: "G728", Rate: 8000},
16: Codec{PT: 16, Name: "DVI4", Rate: 11025},
17: Codec{PT: 17, Name: "DVI4", Rate: 22050},
18: Codec{PT: 18, Name: "G729", Rate: 8000}, // Best telephone voice codec (if you got $10 bucks)
25: Codec{PT: 25, Name: "CelB", Rate: 90000},
26: Codec{PT: 26, Name: "JPEG", Rate: 90000},
28: Codec{PT: 28, Name: "nv", Rate: 90000},
31: Codec{PT: 31, Name: "H261", Rate: 90000}, // RFC4587 Video
32: Codec{PT: 32, Name: "MPV", Rate: 90000},
33: Codec{PT: 33, Name: "MP2T", Rate: 90000},
34: Codec{PT: 34, Name: "H263", Rate: 90000}, // $$$ video
}
)

+ 14
- 24
sdp/media.go View File

@ -2,44 +2,34 @@ package sdp
import (
"bytes"
"errors"
"strconv"
)
// Media is a high level representation of the c=/m=/a= lines for describing a
// specific type of media. Only "audio" and "video" are supported at this time.
type Media struct {
Type string // "audio" or "video"
Proto string // RTP, SRTP, UDP, UDPTL, TCP, TLS, etc.
Port int // port number (0 - 2^16-1)
Codecs []Codec // never nil with at least one codec
Port uint16 // Port number (0 - 2^16-1)
Codecs []Codec // Collection of codecs of a specific type.
}
func (media *Media) Append(b *bytes.Buffer) error {
if media.Type != "audio" && media.Type != "video" {
return errors.New("Media.Type not audio/video: " + media.Type)
}
if media.Codecs == nil || len(media.Codecs) == 0 {
return errors.New("Media.Codecs not set")
}
if media.Port == 0 {
return errors.New("Media.Port not set")
}
func (media *Media) Append(type_ string, b *bytes.Buffer) {
b.WriteString("m=")
b.WriteString(type_)
b.WriteString(" ")
b.WriteString(strconv.FormatUint(uint64(media.Port), 10))
b.WriteString(" ")
if media.Proto == "" {
media.Proto = "RTP/AVP"
b.WriteString("RTP/AVP")
} else {
b.WriteString(media.Proto)
}
b.WriteString("m=")
b.WriteString(media.Type + " ")
b.WriteString(strconv.Itoa(int(media.Port)) + " ")
b.WriteString(media.Proto)
for _, codec := range media.Codecs {
b.WriteString(" " + strconv.Itoa(int(codec.PT)))
b.WriteString(" ")
b.WriteString(strconv.FormatInt(int64(codec.PT), 10))
}
b.WriteString("\r\n")
for _, codec := range media.Codecs {
if err := codec.Append(b); err != nil {
return err
}
codec.Append(b)
}
return nil
}

+ 29
- 22
sdp/origin.go View File

@ -2,40 +2,47 @@ package sdp
import (
"bytes"
"errors"
"github.com/jart/gosip/util"
)
// Origin represents the session origin (o=) line of an SDP. Who knows what
// this is supposed to do.
type Origin struct {
User string // first value in o= line
ID string // second value in o= line
Version string // third value in o= line
Addr string // tracks ip of original user-agent
User string // First value in o= line
ID string // Second value in o= line
Version string // Third value in o= line
Addr string // Tracks IP of original user-agent
}
func (origin *Origin) Append(b *bytes.Buffer) error {
if origin.ID == "" {
return errors.New("sdp missing origin id")
}
if origin.Version == "" {
return errors.New("sdp missing origin version")
}
if origin.Addr == "" {
return errors.New("sdp missing origin address")
func (origin *Origin) Append(b *bytes.Buffer) {
id := origin.ID
if id == "" {
id = util.GenerateOriginID()
}
b.WriteString("o=")
if origin.User == "" {
origin.User = "-"
b.WriteString("-")
} else {
b.WriteString(origin.User)
}
b.WriteString(" ")
b.WriteString(id)
b.WriteString(" ")
if origin.Version == "" {
b.WriteString(id)
} else {
b.WriteString(origin.Version)
}
b.WriteString("o=")
b.WriteString(origin.User + " ")
b.WriteString(origin.ID + " ")
b.WriteString(origin.Version)
if util.IsIPv6(origin.Addr) {
b.WriteString(" IN IP6 " + origin.Addr + "\r\n")
b.WriteString(" IN IP6 ")
} else {
b.WriteString(" IN IP4 ")
}
if origin.Addr == "" {
// In case of bugs, keep calm and DDOS NASA.
b.WriteString("69.28.157.198")
} else {
b.WriteString(" IN IP4 " + origin.Addr + "\r\n")
b.WriteString(origin.Addr)
}
return nil
b.WriteString("\r\n")
}

+ 59
- 59
sdp/sdp.go View File

@ -57,7 +57,6 @@ import (
"errors"
"github.com/jart/gosip/util"
"log"
"math/rand"
"net"
"strconv"
"strings"
@ -70,32 +69,31 @@ const (
// SDP represents a Session Description Protocol SIP payload.
type SDP struct {
SendOnly bool // true if 'a=sendonly' was specified in sdp
RecvOnly bool // true if 'a=recvonly' was specified in sdp
Ptime int // transmit every X millisecs (0 means not set)
Addr string // connect to this ip; never blank (from c=)
Audio *Media // non nil if we can establish audio
Video *Media // non nil if we can establish video
Origin Origin // this must always be present
Session string // s= Session Name (defaults to "-")
Time string // t= Active Time (defaults to "0 0")
Attrs [][2]string // a= lines we don't recognize (never nil)
Origin Origin // This must always be present
Addr string // Connect to this IP; never blank (from c=)
Audio *Media // Non-nil if we can establish audio
Video *Media // Non-nil if we can establish video
Session string // s= Session Name (default "-")
Time string // t= Active Time (default "0 0")
Ptime int // Transmit frame every N milliseconds (default 20)
SendOnly bool // True if 'a=sendonly' was specified in SDP
RecvOnly bool // True if 'a=recvonly' was specified in SDP
Attrs [][2]string // a= lines we don't recognize
}
// Easy way to create a basic, everyday SDP for VoIP.
func New(addr *net.UDPAddr, codecs ...*Codec) *SDP {
func New(addr *net.UDPAddr, codecs ...Codec) *SDP {
sdp := new(SDP)
sdp.Addr = addr.IP.String()
sdp.Origin.ID = strconv.FormatInt(int64(rand.Uint32()), 10)
sdp.Origin.ID = util.GenerateOriginID()
sdp.Origin.Version = sdp.Origin.ID
sdp.Origin.Addr = sdp.Addr
sdp.Audio = new(Media)
sdp.Audio.Type = "audio"
sdp.Audio.Proto = "RTP/AVP"
sdp.Audio.Port = addr.Port
sdp.Audio.Port = uint16(addr.Port)
sdp.Audio.Codecs = make([]Codec, len(codecs))
for i := 0; i < len(codecs); i++ {
sdp.Audio.Codecs[i] = *codecs[i]
sdp.Audio.Codecs[i] = codecs[i]
}
sdp.Attrs = make([][2]string, 0, 8)
return sdp
@ -216,7 +214,6 @@ func Parse(s string) (sdp *SDP, err error) {
if audioinfo != "" {
sdp.Audio = new(Media)
sdp.Audio.Type = "audio"
sdp.Audio.Port, sdp.Audio.Proto, pts, err = parseMediaInfo(audioinfo)
if err != nil {
return nil, err
@ -231,7 +228,6 @@ func Parse(s string) (sdp *SDP, err error) {
if videoinfo != "" {
sdp.Video = new(Media)
sdp.Video.Type = "video"
sdp.Video.Port, sdp.Video.Proto, pts, err = parseMediaInfo(videoinfo)
if err != nil {
return nil, err
@ -256,58 +252,62 @@ func (sdp *SDP) String() string {
return ""
}
var b bytes.Buffer
if err := sdp.Append(&b); err != nil {
log.Println("Bad SDP!", err)
return ""
}
sdp.Append(&b)
return b.String()
}
func (sdp *SDP) Append(b *bytes.Buffer) error {
if sdp.Audio == nil && sdp.Video == nil {
return errors.New("sdp lonely no media :[")
}
func (sdp *SDP) Append(b *bytes.Buffer) {
b.WriteString("v=0\r\n")
sdp.Origin.Append(b)
b.WriteString("s=")
if sdp.Session == "" {
sdp.Session = "my people call themselves dark angels"
b.WriteString("my people call themselves dark angels")
} else {
b.WriteString(sdp.Session)
}
if sdp.Time == "" {
sdp.Time = "0 0"
b.WriteString("\r\n")
if util.IsIPv6(sdp.Addr) {
b.WriteString("c=IN IP6 ")
} else {
b.WriteString("c=IN IP4 ")
}
b.WriteString("v=0\r\n")
if err := sdp.Origin.Append(b); err != nil {
return err
if sdp.Addr == "" {
// In case of bugs, keep calm and DDOS NASA.
b.WriteString("69.28.157.198")
} else {
b.WriteString(sdp.Addr)
}
b.WriteString("s=" + sdp.Session + "\r\n")
if util.IsIPv6(sdp.Addr) {
b.WriteString("c=IN IP6 " + sdp.Addr + "\r\n")
b.WriteString("\r\n")
b.WriteString("t=")
if sdp.Time == "" {
b.WriteString("0 0")
} else {
b.WriteString("c=IN IP4 " + sdp.Addr + "\r\n")
b.WriteString(sdp.Time)
}
b.WriteString("t=" + sdp.Time + "\r\n")
b.WriteString("\r\n")
if sdp.Audio != nil {
if err := sdp.Audio.Append(b); err != nil {
return err
}
sdp.Audio.Append("audio", b)
}
if sdp.Video != nil {
if err := sdp.Video.Append(b); err != nil {
return err
}
}
if sdp.Attrs != nil {
for _, attr := range sdp.Attrs {
if attr[0] == "" {
return errors.New("SDP.Attrs key empty!")
}
if attr[1] == "" {
b.WriteString("a=" + attr[0] + "\r\n")
} else {
b.WriteString("a=" + attr[0] + ":" + attr[1] + "\r\n")
}
sdp.Video.Append("video", b)
}
for _, attr := range sdp.Attrs {
if attr[1] == "" {
b.WriteString("a=")
b.WriteString(attr[0])
b.WriteString("\r\n")
} else {
b.WriteString("a=")
b.WriteString(attr[0])
b.WriteString(":")
b.WriteString(attr[1])
b.WriteString("\r\n")
}
}
if sdp.Ptime > 0 {
b.WriteString("a=ptime:" + strconv.Itoa(sdp.Ptime) + "\r\n")
b.WriteString("a=ptime:")
b.WriteString(strconv.Itoa(sdp.Ptime))
b.WriteString("\r\n")
}
if sdp.SendOnly {
b.WriteString("a=sendonly\r\n")
@ -316,7 +316,6 @@ func (sdp *SDP) Append(b *bytes.Buffer) error {
} else {
b.WriteString("a=sendrecv\r\n")
}
return nil
}
// Here we take the list of payload types from the m= line (e.g. 9 18 0 101)
@ -344,7 +343,7 @@ func populateCodecs(media *Media, pts []uint8, rtpmaps, fmtps []string) (err err
return errors.New("dynamic codec missing rtpmap")
} else {
if v, ok := StandardCodecs[pt]; ok {
*codec = *v
*codec = v
} else {
return errors.New("unknown iana codec id: " +
strconv.Itoa(int(pt)))
@ -386,7 +385,7 @@ func parseRtpmapInfo(codec *Codec, s string) (err error) {
}
// Give me the part of an "m=" line that looks like: "30126 RTP/AVP 0 101".
func parseMediaInfo(s string) (port int, proto string, pts []uint8, err error) {
func parseMediaInfo(s string) (port uint16, proto string, pts []uint8, err error) {
toks := strings.Split(s, " ")
if toks == nil || len(toks) < 3 {
return 0, "", nil, errors.New("invalid m= line")
@ -400,10 +399,11 @@ func parseMediaInfo(s string) (port int, proto string, pts []uint8, err error) {
}
// Convert port to int and check range.
port, err = strconv.Atoi(portS)
portU, err := strconv.ParseUint(portS, 10, 16)
if err != nil || !(0 <= port && port <= 65535) {
return 0, "", nil, errors.New("invalid m= port")
}
port = uint16(portU)
// Is it rtp? srtp? udp? tcp? etc. (must be 3+ chars)
proto = toks[1]


+ 0
- 10
sdp/sdp_test.go View File

@ -41,7 +41,6 @@ var sdpTests = []sdpTest{
Time: "0 0",
Addr: "10.0.0.38",
Audio: &sdp.Media{
Type: "audio",
Proto: "RTP/AVP",
Port: 30126,
Codecs: []sdp.Codec{
@ -86,7 +85,6 @@ var sdpTests = []sdpTest{
Session: "pokémon",
Time: "0 0",
Audio: &sdp.Media{
Type: "audio",
Proto: "RTP/AVP",
Port: 32898,
Codecs: []sdp.Codec{
@ -94,7 +92,6 @@ var sdpTests = []sdpTest{
},
},
Video: &sdp.Media{
Type: "video",
Proto: "RTP/AVP",
Port: 32900,
Codecs: []sdp.Codec{
@ -138,7 +135,6 @@ var sdpTests = []sdpTest{
Time: "0 0",
Addr: "1.2.3.4",
Audio: &sdp.Media{
Type: "audio",
Proto: "RTP/AVP",
Port: 32898,
Codecs: []sdp.Codec{
@ -186,7 +182,6 @@ var sdpTests = []sdpTest{
Time: "0 0",
Addr: "dead:beef::666",
Audio: &sdp.Media{
Type: "audio",
Proto: "RTP/AVP",
Port: 32898,
Codecs: []sdp.Codec{
@ -235,7 +230,6 @@ var sdpTests = []sdpTest{
Time: "0 0",
Addr: "10.11.34.37",
Audio: &sdp.Media{
Type: "audio",
Proto: "RTP/AVP",
Port: 4000,
Codecs: []sdp.Codec{
@ -280,7 +274,6 @@ var sdpTests = []sdpTest{
Addr: "dead:beef::666",
SendOnly: true,
Audio: &sdp.Media{
Type: "audio",
Proto: "TCP/IP",
Port: 80,
Codecs: []sdp.Codec{
@ -356,9 +349,6 @@ func sdpCompareMedia(t *testing.T, name string, correct, media *sdp.Media) {
return
}
if correct.Type != media.Type {
t.Error(name, "Type", correct.Type, "!=", media.Type)
}
if correct.Proto != media.Proto {
t.Error(name, "Proto", correct.Proto, "!=", media.Proto)
}


+ 6
- 0
util/util.go View File

@ -4,6 +4,7 @@ import (
"encoding/hex"
"math/rand"
"net"
"strconv"
"strings"
"syscall"
)
@ -78,6 +79,11 @@ func GenerateCallID() string {
return uuid4
}
// Generates a random ID for an SDP.
func GenerateOriginID() string {
return strconv.FormatUint(uint64(rand.Uint32()), 10)
}
func randomBytes(l int) (b []byte) {
b = make([]byte, l)
for i := 0; i < l; i++ {


Loading…
Cancel
Save