|
|
|
@ -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" |
|
|
|
@ -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) |
|
|
|
@ -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) |
|
|
|
} |
|
|
|
} |