Skip to content

Commit

Permalink
RTP Sessions.
Browse files Browse the repository at this point in the history
  • Loading branch information
jart committed Dec 23, 2014
1 parent af5a023 commit ec87bbe
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 96 deletions.
2 changes: 2 additions & 0 deletions dsp/dsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ func L16MixSat160(dst, src *int16)

// Compresses a PCM audio sample into a G.711 μ-Law sample. The BSR instruction
// is what makes this code fast.
//
// TODO(jart): How do I make assembly use proper types?
func LinearToUlaw(linear int64) (ulaw int64)

// Turns a μ-Law byte back into an audio sample.
Expand Down
204 changes: 115 additions & 89 deletions example/echo2/echo2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
package echo2_test

import (
"github.com/jart/gosip/dsp"
"github.com/jart/gosip/rtp"
"github.com/jart/gosip/sdp"
"github.com/jart/gosip/sip"
"github.com/jart/gosip/util"
"log"
"math/rand"
"net"
"testing"
"time"
Expand All @@ -24,48 +25,71 @@ func TestCallToEchoApp(t *testing.T) {
}
defer tp.Sock.Close()

// Used to notify main thread when subthreads die.
rtpDeath := make(chan bool, 2)

// Create an RTP session.
rtpsock, err := net.ListenPacket("udp", "108.61.60.146:0")
session, err := rtp.NewSession(from.Uri.Host)
if err != nil {
t.Fatal("rtp listen:", err)
}
defer rtpsock.Close()
rtpaddr := rtpsock.LocalAddr().(*net.UDPAddr)
rrtpaddrChan := make(chan *net.UDPAddr)
defer session.Sock.Close()
rtpaddr := session.Sock.LocalAddr().(*net.UDPAddr)
rtppeerChan := make(chan *net.UDPAddr, 1)
go func() {
var rrtpaddr *net.UDPAddr
frameout := make([]byte, rtp.HeaderSize+160)
rtpHeader := rtp.Header{
PT: sdp.ULAWCodec.PT,
Seq: 666,
TS: 0,
Ssrc: rand.Uint32(),
}
for n := 0; n < 160; n++ {
frameout[rtp.HeaderSize+n] = byte(n)
}
var frame rtp.Frame
awgn := dsp.NewAWGN(-25.0)
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
rtpHeader.Write(frameout)
rtpHeader.TS += 160
rtpHeader.Seq++
if rrtpaddr != nil {
_, err := rtpsock.WriteTo(frameout, rrtpaddr)
if err != nil {
t.Fatal("rtp write", err)
for n := 0; n < 160; n++ {
frame[n] = awgn.Get()
}
err := session.Send(frame)
if err != nil {
if !util.IsUseOfClosed(err) {
t.Error("rtp write", err)
}
rtpDeath <- true
return
}
case rrtpaddr = <-rrtpaddrChan:
if rrtpaddr == nil {
case session.Peer = <-rtppeerChan:
if session.Peer == nil {
return
}
}
}
}()
defer func() { rrtpaddrChan <- nil }()
defer func() { rtppeerChan <- nil }()

// Create an RTP message consumer.
go func() {
var frame rtp.Frame
for {
err := session.Recv(frame)
if err != nil {
if !util.IsUseOfClosed(err) {
t.Errorf("rtp read: %s %#v", err, err)
}
rtpDeath <- true
return
}
}
}()

// Create a SIP message consumer.
sipChan := make(chan *sip.Msg, 32)
go func() {
for {
msg := tp.Recv()
if msg.Error != nil && util.IsUseOfClosed(msg.Error) {
return
}
sipChan <- msg
}
}()

// Send an INVITE message with an SDP.
invite := sip.NewRequest(tp, "INVITE", to, from)
Expand All @@ -75,75 +99,77 @@ func TestCallToEchoApp(t *testing.T) {
t.Fatal(err)
}

// Consume provisional messages until we receive answer.
// Create a SIP dialog handler.
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var answered bool
var msg *sip.Msg
for {
tp.Sock.SetDeadline(time.Now().Add(time.Second))
msg = tp.Recv()
if msg.Error != nil {
t.Fatal(msg.Error)
}
if msg.Status < sip.StatusOK {
log.Printf("Provisional %d %s", msg.Status, msg.Phrase)
} else if msg.Status == sip.StatusOK {
log.Printf("Answered!")
}
if msg.Status >= sip.StatusOK {
err = tp.Send(sip.NewAck(invite, msg))
select {
case <-ticker.C:
if answered {
err = tp.Send(sip.NewBye(invite, msg))
} else {
err = tp.Send(sip.NewCancel(invite))
}
if err != nil {
t.Fatal(err)
t.Error(err)
}
case <-rtpDeath:
if answered {
err = tp.Send(sip.NewBye(invite, msg))
} else {
err = tp.Send(sip.NewCancel(invite))
}
}
if msg.Headers["Content-Type"] == "application/sdp" {
log.Printf("Establishing media session")
rsdp, err := sdp.Parse(msg.Payload)
if err != nil {
t.Fatal("failed to parse sdp", err)
t.Error(err)
}
case msg = <-sipChan:
if msg.Error != nil {
t.Fatal(msg.Error)
}
if msg.IsResponse {
if msg.Status >= sip.StatusOK && msg.CSeq == invite.CSeq {
err = tp.Send(sip.NewAck(invite, msg))
if err != nil {
t.Fatal(err)
}
}
if msg.Status < sip.StatusOK {
log.Printf("Provisional %d %s", msg.Status, msg.Phrase)
} else if msg.Status == sip.StatusOK {
if msg.CSeqMethod == "INVITE" {
log.Printf("Answered!")
answered = true
} else if msg.CSeqMethod == "BYE" {
log.Printf("Hungup!")
return
} else if msg.CSeqMethod == "CANCEL" {
log.Printf("Cancelled!")
return
}
} else if msg.Status > sip.StatusOK {
t.Errorf("Got %d %s", msg.Status, msg.Phrase)
return
}
if msg.Headers["Content-Type"] == "application/sdp" {
log.Printf("Establishing media session")
rsdp, err := sdp.Parse(msg.Payload)
if err != nil {
t.Fatal("failed to parse sdp", err)
}
rtppeerChan <- &net.UDPAddr{IP: net.ParseIP(rsdp.Addr), Port: int(rsdp.Audio.Port)}
}
} else {
if msg.Method == "BYE" {
log.Printf("Remote Hangup!")
err = tp.Send(sip.NewResponse(invite, sip.StatusOK))
if err != nil {
t.Fatal(err)
}
return
}
}
rrtpaddrChan <- &net.UDPAddr{IP: net.ParseIP(rsdp.Addr), Port: int(rsdp.Audio.Port)}
}
if msg.Status == sip.StatusOK {
break
} else if msg.Status > sip.StatusOK {
t.Fatalf("Got %d %s", msg.Status, msg.Phrase)
}
}

// We're talking to an echo application so they should send us back exactly
// the same audio.
memory := make([]byte, 2048)
rtpsock.SetDeadline(time.Now().Add(5 * time.Second))
amt, _, err := rtpsock.ReadFrom(memory)
if err != nil {
t.Fatal("rtp read", err)
}
if amt != rtp.HeaderSize+160 {
t.Fatal("rtp recv amt != 12+160")
}
var rtpHeader rtp.Header
err = rtpHeader.Read(memory)
if err != nil {
t.Fatal(err)
}
for n := 0; n < 160; n++ {
if memory[rtp.HeaderSize+n] != byte(n) {
t.Fatal("rtp response audio didnt match")
}
}

// Hangup (we'll be lazy and just change up the ack Msg)
err = tp.Send(sip.NewBye(invite, msg))
if err != nil {
t.Fatal(err)
}

// Wait for acknowledgment of hangup.
tp.Sock.SetDeadline(time.Now().Add(time.Second))
msg = tp.Recv()
if msg.Error != nil {
t.Fatal(msg.Error)
}
if msg.Status != 200 || msg.CSeqMethod != "BYE" {
t.Fatal("Wanted BYE 200:", msg)
}
}
12 changes: 6 additions & 6 deletions rtp/rtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ var (

// Header is encoded at the beginning of a UDP audio packet.
type Header struct {
Pad bool // the padding flag is used for secure rtp
Mark bool // the marker flag is used for rfc2833
PT uint8 // payload type you got from sdp
Seq uint16 // sequence id useful for reordering packets
TS uint32 // timestamp measured in samples
Ssrc uint32 // random id used to identify an rtp session
Pad bool // Padding flag is used for secure RTP.
Mark bool // Marker flag is used for RFC2833.
PT uint8 // Payload type you got from SDP.
Seq uint16 // Sequence id useful for reordering packets.
TS uint32 // Timestamp measured in samples.
Ssrc uint32 // Random ID used to identify an RTP session.
}

// EventHeader stores things like DTMF and is encoded after Header.
Expand Down
Loading

0 comments on commit ec87bbe

Please sign in to comment.