Skip to content

Commit

Permalink
feat: fix register expiry, add DTMF decoder encoder, RTP and RTCP deb…
Browse files Browse the repository at this point in the history
…ug flags for media
  • Loading branch information
emiago committed Mar 15, 2024
1 parent 068df78 commit 7237578
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ Features:
- [x] Hangup control on caller
- [x] Timeouts handling
- [x] Digest auth
- [x] DTMF encoder, decoder via RFC4733
- [ ] Transfers on answer, dial
- [ ] SDP codec fields manipulating
- [ ] SDP negotiation fail
- [ ] DTMF passing


Checkout `echome` example to see more.
Expand Down
120 changes: 119 additions & 1 deletion media.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sipgox

import (
"encoding/binary"
"fmt"
"io"
"net"
Expand All @@ -11,13 +12,18 @@ import (
"github.com/emiago/sipgox/sdp"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)

var (
// RTPPortStart and RTPPortEnd allows defining rtp port range for media
RTPPortStart = 0
RTPPortEnd = 0
rtpPortOffset = atomic.Int32{}

RTPDebug = false
RTCPDebug = false
)

type MediaSession struct {
Expand All @@ -34,6 +40,8 @@ type MediaSession struct {
// Depending of negotiation this can change
// Not thread safe
Formats sdp.Formats

log zerolog.Logger
}

func NewMediaSession(laddr *net.UDPAddr) (s *MediaSession, e error) {
Expand All @@ -42,6 +50,7 @@ func NewMediaSession(laddr *net.UDPAddr) (s *MediaSession, e error) {
sdp.FORMAT_TYPE_ULAW, sdp.FORMAT_TYPE_ALAW,
},
Laddr: laddr,
log: log.With().Str("caller", "media").Logger(),
}

// Try to listen on this ports
Expand Down Expand Up @@ -248,7 +257,14 @@ func (m *MediaSession) ReadRTP() (rtp.Packet, error) {
return p, err
}

return p, p.Unmarshal(buf[:n])
if err := p.Unmarshal(buf[:n]); err != nil {
return p, err
}

if RTPDebug {
m.log.Debug().Msgf("Recv RTP\n%s", p.String())
}
return p, err
}

func (m *MediaSession) ReadRTPWithDeadline(t time.Time) (rtp.Packet, error) {
Expand Down Expand Up @@ -291,6 +307,10 @@ func (m *MediaSession) ReadRTCPRaw(buf []byte) (int, error) {
}

func (m *MediaSession) WriteRTP(p *rtp.Packet) error {
if RTPDebug {
m.log.Debug().Msgf("RTP write:\n%s", p.String())
}

data, err := p.Marshal()
if err != nil {
return err
Expand All @@ -314,6 +334,12 @@ func (m *MediaSession) WriteRTP(p *rtp.Packet) error {
}

func (m *MediaSession) WriteRTCP(p rtcp.Packet) error {
if RTCPDebug {
if sr, ok := p.(fmt.Stringer); ok {
m.log.Debug().Msgf("RTCP write: \n%s", sr.String())
}
}

data, err := p.Marshal()
if err != nil {
return err
Expand Down Expand Up @@ -369,3 +395,95 @@ func selectFormats(sendCodecs []string, recvCodecs []string) []int {
}
return formats
}

// DTMF event mapping (RFC 4733)
var dtmfEventMapping = map[rune]byte{
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'*': 10,
'#': 11,
'A': 12,
'B': 13,
'C': 14,
'D': 15,
}

func RTPDTMFEncode(char rune) []DTMFEvent {
event := dtmfEventMapping[char]

events := make([]DTMFEvent, 7)

for i := 0; i < 4; i++ {
d := DTMFEvent{
Event: event,
EndOfEvent: false,
Volume: 10,
Duration: 160 * (uint16(i) + 1),
}
events[i] = d
}

// End events. Took this from linphone example, but not clear why sending this many
for i := 4; i < 7; i++ {
d := DTMFEvent{
Event: event,
EndOfEvent: true,
Volume: 10,
Duration: 160 * 5, // Must not be increased for end event
}
events[i] = d
}

return events
}

// DTMFEvent represents a DTMF event
type DTMFEvent struct {
Event uint8
EndOfEvent bool
Volume uint8
Duration uint16
}

func (ev *DTMFEvent) String() string {
out := "RTP DTMF Event:\n"
out += fmt.Sprintf("\tEvent: %d\n", ev.Event)
out += fmt.Sprintf("\tEndOfEvent: %v\n", ev.EndOfEvent)
out += fmt.Sprintf("\tVolume: %d\n", ev.Volume)
out += fmt.Sprintf("\tDuration: %d\n", ev.Duration)
return out
}

// DecodeRTPPayload decodes an RTP payload into a DTMF event
func DTMFDecode(payload []byte, d *DTMFEvent) error {
if len(payload) < 4 {
return fmt.Errorf("payload too short")
}

d.Event = payload[0]
d.EndOfEvent = payload[1]&0x80 != 0
d.Volume = payload[1] & 0x7F
d.Duration = binary.BigEndian.Uint16(payload[2:4])
// d.Duration = uint16(payload[2])<<8 | uint16(payload[3])
return nil
}

func DTMFEncode(d DTMFEvent) []byte {
header := make([]byte, 4)
header[0] = d.Event

if d.EndOfEvent {
header[1] = 0x80
}
header[1] |= d.Volume & 0x3F
binary.BigEndian.PutUint16(header[2:4], d.Duration)
return header
}
34 changes: 34 additions & 0 deletions media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,37 @@ func TestMediaPortRange(t *testing.T) {
}

}

func TestDTMFEncodeDecode(t *testing.T) {
// Example payload for DTMF digit '1' with volume 10 and duration 1000
// Event: 0x01 (DTMF digit '1')
// E bit: 0x80 (End of Event)
// Volume: 0x0A (Volume 10)
// Duration: 0x03E8 (Duration 1000)
payload := []byte{0x01, 0x8A, 0x03, 0xE8}

event := DTMFEvent{}
err := DTMFDecode(payload, &event)
if err != nil {
t.Fatalf("Error decoding payload: %v", err)
}

if event.Event != 0x01 {
t.Errorf("Unexpected Event. got: %v, want: %v", event.Event, 0x01)
}

if event.EndOfEvent != true {
t.Errorf("Unexpected EndOfEvent. got: %v, want: %v", event.EndOfEvent, true)
}

if event.Volume != 0x0A {
t.Errorf("Unexpected Volume. got: %v, want: %v", event.Volume, 0x0A)
}

if event.Duration != 0x03E8 {
t.Errorf("Unexpected Duration. got: %v, want: %v", event.Duration, 0x03E8)
}

encoded := DTMFEncode(event)
require.Equal(t, payload, encoded)
}
4 changes: 4 additions & 0 deletions phone.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ func (p *Phone) createServerListener(s *sipgo.Server, a ListenAddr) (*Listener,
return nil, fmt.Errorf("listen udp error. err=%w", err)
}

// Port can be dynamic
a.Addr = udpConn.LocalAddr().String()

return &Listener{
a,
udpConn,
Expand All @@ -217,6 +220,7 @@ func (p *Phone) createServerListener(s *sipgo.Server, a ListenAddr) (*Listener,
return nil, fmt.Errorf("listen tcp error. err=%w", err)
}

a.Addr = conn.Addr().String()
// and uses listener to buffer
if network == "ws" {
return &Listener{
Expand Down
6 changes: 4 additions & 2 deletions register_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ func NewRegisterTransaction(log zerolog.Logger, client *sipgo.Client, recipient
// log := p.getLoggerCtx(ctx, "Register")
req := sip.NewRequest(sip.REGISTER, recipient)
req.AppendHeader(&contact)
expires := sip.ExpiresHeader(expiry)
req.AppendHeader(&expires)
if expiry > 0 {
expires := sip.ExpiresHeader(expiry)
req.AppendHeader(&expires)
}
if allowHDRS != nil {
req.AppendHeader(sip.NewHeader("Allow", strings.Join(allowHDRS, ", ")))
}
Expand Down
7 changes: 4 additions & 3 deletions sdp_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ func SDPGenerateForAudio(originIP net.IP, connectionIP net.IP, rtpPort int, mode
// "b=AS:84",
fmt.Sprintf("c=IN IP4 %s", connectionIP),
"t=0 0",
fmt.Sprintf("m=audio %d RTP/AVP %s", rtpPort, strings.Join(fmts, " ")),
fmt.Sprintf("m=audio %d RTP/AVP %s", rtpPort, strings.Join(fmts, " ")+" 101"),
"a=" + string(mode),
// "a=ssrc:111222 cname:[email protected]",
// "a=rtpmap:0 PCMU/8000",
// "a=rtpmap:8 PCMA/8000",
// "a=rtpmap:101 telephone-event/8000",
// "a=fmtp:101 0-16",
// THIS is FOR DTM
"a=rtpmap:101 telephone-event/8000",
"a=fmtp:101 0-16",
// "",
// "a=rtpmap:120 telephone-event/16000",
// "a=fmtp:120 0-16",
Expand Down

0 comments on commit 7237578

Please sign in to comment.