diff --git a/src/net/rtp/ctrlpacket_test.go b/src/net/rtp/ctrlpacket_test.go index 12e2c1b..f49c049 100644 --- a/src/net/rtp/ctrlpacket_test.go +++ b/src/net/rtp/ctrlpacket_test.go @@ -1,141 +1,141 @@ -// Copyright (C) 2011 Werner Dittmann -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Authors: Werner Dittmann -// - -package rtp - -import ( - "fmt" -// "net" - "testing" -) - -// V=2, P=0, chunks=2; PT=SDES; length=5 32bit words (24 bytes) -var sdes_1 = []byte{0x81, 202, 0x00, 0x05, - 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 - 0x01, 0x01, 0x02, // CNAME, len=1, content=2 - 0x00, // END (chunk length: 8) - 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 - 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 - 0x00, 0x00, 0x00 } // END plus 2 padding (chunk length: 12) -// V=2, P=0, chunks=2; PT=SDES; length=8 32bit words (36 bytes) -var sdes_2 = []byte{0x81, 202, 0x00, 0x08, - // first chunk, two items: CNAME, NAME, END) - 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 - 0x01, 0x01, 0x02, // CNAME, len=1, content=2 (item length: 3) - 0x02, 0x07, // NAME, len=7 - 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, // content 5,6,7,7,6,5,4 (item length 9) - 0x00, 0x00, 0x00, 0x00, // END plus 3 padding (chunk length: 20) - // second chunk, one item (CNAME, END) - 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 - 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 - 0x00, 0x00, 0x00 } // END plus 2 padding (chunk length: 12) - -// V=2, P=0, chunks=2; PT=SDES; length=5 32bit words (24 bytes) -var sdes_1_wrong = []byte{0x81, 202, 0x00, 0x05, - 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 - 0x01, 0x03, 0x02, // CNAME, len=3 (wrong, should be 1), content=2 - 0x00, // END (chunk length: 8) - 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 - 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 - 0x00, 0x00, 0x00 } // END plus 2 padding (chunk length: 12) - -func sdesCheck(t *testing.T) (result bool) { - result = false - - rp := new(CtrlPacket) // allocate a new CTRL packet. - rp.buffer = sdes_1 - - cnt := int((rp.Length(0) + 1) * 4) // SDES Length incl. header word - if cnt != 24 { - t.Error(fmt.Sprintf("Basic first SDES length check failed. Expected: 24, got: %d\n", cnt)) - return - } - offset := 4 - // first SDES chunk starts ofter first header word - sdesChunk := rp.toSdesChunk(offset, cnt-4) - - // Get length of first chunk - must be 8 - chunkLen, _ := sdesChunk.chunkLen() - if chunkLen != 8 { - t.Error(fmt.Sprintf("Basic first SDES chunk length check failed. Expected: 8, got: %d\n", chunkLen)) - return - } - offset += chunkLen - cnt -= chunkLen - // second SDES chunk starts ofter first chunk :-) - sdesChunk = rp.toSdesChunk(offset, cnt-4) - - // Get length of second chunk - must be 12 - chunkLen, _ = sdesChunk.chunkLen() - if chunkLen != 12 { - t.Error(fmt.Sprintf("Basic first SDES chunk length check failed. Expected: 12, got: %d\n", chunkLen)) - return - } - - rp.buffer = sdes_2 - cnt = int((rp.Length(0) + 1) * 4) // SDES Length incl. header word - if cnt != 36 { - t.Error(fmt.Sprintf("Basic second SDES length check failed. Expected: 36, got: %d\n", cnt)) - return - } - offset = 4 - // first SDES chunk starts ofter first header word - sdesChunk = rp.toSdesChunk(offset, cnt-4) - - // Get length of first chunk - must be 20 - chunkLen, _ = sdesChunk.chunkLen() - if chunkLen != 20 { - t.Error(fmt.Sprintf("Basic second SDES chunk length check failed. Expected: 20, got: %d\n", chunkLen)) - return - } - offset += chunkLen - cnt -= chunkLen - // second SDES chunk starts ofter first chunk :-) - sdesChunk = rp.toSdesChunk(offset, cnt-4) - - // Get length of second chunk - must be 12 - chunkLen, _ = sdesChunk.chunkLen() - if chunkLen != 12 { - t.Error(fmt.Sprintf("Basic second SDES chunk length check failed. Expected: 12, got: %d\n", chunkLen)) - return - } - - rp.buffer = sdes_1_wrong - offset = 4 - // SDES chunk starts ofter first header word - sdesChunk = rp.toSdesChunk(offset, cnt-4) - - // Get length of chunk - must fail - chunkLen, ok := sdesChunk.chunkLen() - if ok { - t.Errorf("Chunk length error handling failed, expected: 0, false; got: %d, %v\n", chunkLen, ok) - return - } - - result = true - return -} - -func rtcpPacketBasic(t *testing.T) { - sdesCheck(t) -} - -func TestRtcpPacket(t *testing.T) { - parseFlags() - rtcpPacketBasic(t) -} +// Copyright (C) 2011 Werner Dittmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Authors: Werner Dittmann +// + +package rtp + +import ( + "fmt" + // "net" + "testing" +) + +// V=2, P=0, chunks=2; PT=SDES; length=5 32bit words (24 bytes) +var sdes_1 = []byte{0x81, 202, 0x00, 0x05, + 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 + 0x01, 0x01, 0x02, // CNAME, len=1, content=2 + 0x00, // END (chunk length: 8) + 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 + 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 + 0x00, 0x00, 0x00} // END plus 2 padding (chunk length: 12) +// V=2, P=0, chunks=2; PT=SDES; length=8 32bit words (36 bytes) +var sdes_2 = []byte{0x81, 202, 0x00, 0x08, + // first chunk, two items: CNAME, NAME, END) + 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 + 0x01, 0x01, 0x02, // CNAME, len=1, content=2 (item length: 3) + 0x02, 0x07, // NAME, len=7 + 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, // content 5,6,7,7,6,5,4 (item length 9) + 0x00, 0x00, 0x00, 0x00, // END plus 3 padding (chunk length: 20) + // second chunk, one item (CNAME, END) + 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 + 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 + 0x00, 0x00, 0x00} // END plus 2 padding (chunk length: 12) + +// V=2, P=0, chunks=2; PT=SDES; length=5 32bit words (24 bytes) +var sdes_1_wrong = []byte{0x81, 202, 0x00, 0x05, + 0x01, 0x02, 0x03, 0x04, // SSRC 0x01020304 + 0x01, 0x03, 0x02, // CNAME, len=3 (wrong, should be 1), content=2 + 0x00, // END (chunk length: 8) + 0x05, 0x06, 0x07, 0x08, // SSRC 0x05060707 + 0x01, 0x03, 0x05, 0x06, 0x07, // CNAME, len=3, content=5,6,7 + 0x00, 0x00, 0x00} // END plus 2 padding (chunk length: 12) + +func sdesCheck(t *testing.T) (result bool) { + result = false + + rp := new(CtrlPacket) // allocate a new CTRL packet. + rp.buffer = sdes_1 + + cnt := int((rp.Length(0) + 1) * 4) // SDES Length incl. header word + if cnt != 24 { + t.Error(fmt.Sprintf("Basic first SDES length check failed. Expected: 24, got: %d\n", cnt)) + return + } + offset := 4 + // first SDES chunk starts ofter first header word + sdesChunk := rp.toSdesChunk(offset, cnt-4) + + // Get length of first chunk - must be 8 + chunkLen, _ := sdesChunk.chunkLen() + if chunkLen != 8 { + t.Error(fmt.Sprintf("Basic first SDES chunk length check failed. Expected: 8, got: %d\n", chunkLen)) + return + } + offset += chunkLen + cnt -= chunkLen + // second SDES chunk starts ofter first chunk :-) + sdesChunk = rp.toSdesChunk(offset, cnt-4) + + // Get length of second chunk - must be 12 + chunkLen, _ = sdesChunk.chunkLen() + if chunkLen != 12 { + t.Error(fmt.Sprintf("Basic first SDES chunk length check failed. Expected: 12, got: %d\n", chunkLen)) + return + } + + rp.buffer = sdes_2 + cnt = int((rp.Length(0) + 1) * 4) // SDES Length incl. header word + if cnt != 36 { + t.Error(fmt.Sprintf("Basic second SDES length check failed. Expected: 36, got: %d\n", cnt)) + return + } + offset = 4 + // first SDES chunk starts ofter first header word + sdesChunk = rp.toSdesChunk(offset, cnt-4) + + // Get length of first chunk - must be 20 + chunkLen, _ = sdesChunk.chunkLen() + if chunkLen != 20 { + t.Error(fmt.Sprintf("Basic second SDES chunk length check failed. Expected: 20, got: %d\n", chunkLen)) + return + } + offset += chunkLen + cnt -= chunkLen + // second SDES chunk starts ofter first chunk :-) + sdesChunk = rp.toSdesChunk(offset, cnt-4) + + // Get length of second chunk - must be 12 + chunkLen, _ = sdesChunk.chunkLen() + if chunkLen != 12 { + t.Error(fmt.Sprintf("Basic second SDES chunk length check failed. Expected: 12, got: %d\n", chunkLen)) + return + } + + rp.buffer = sdes_1_wrong + offset = 4 + // SDES chunk starts ofter first header word + sdesChunk = rp.toSdesChunk(offset, cnt-4) + + // Get length of chunk - must fail + chunkLen, ok := sdesChunk.chunkLen() + if ok { + t.Errorf("Chunk length error handling failed, expected: 0, false; got: %d, %v\n", chunkLen, ok) + return + } + + result = true + return +} + +func rtcpPacketBasic(t *testing.T) { + sdesCheck(t) +} + +func TestRtcpPacket(t *testing.T) { + parseFlags() + rtcpPacketBasic(t) +} diff --git a/src/net/rtp/datapacket_test.go b/src/net/rtp/datapacket_test.go index e0fcbf8..aef60f5 100644 --- a/src/net/rtp/datapacket_test.go +++ b/src/net/rtp/datapacket_test.go @@ -1,350 +1,350 @@ -// Copyright (C) 2011 Werner Dittmann -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Authors: Werner Dittmann -// - -package rtp - -import ( - "fmt" - "net" - "testing" - "flag" - "time" -) - -var verbose *bool = flag.Bool("verbose", false, "Verbose output during tests") -var parsed bool - -var payload = []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20} -var payloadNull = []byte{} - -var initialPacket = []byte{0x80, 0x03, 0x47, 0x11, 0xf0, 0xe0, 0xd0, 0xc0, 0x01, 0x02, 0x03, 0x04, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20} - -var csrc_1 = []uint32{0x21435465, 0x65544322} -var csrc_2 = []uint32{0x23445566, 0x66554423, 0x87766554} -var csrc_3 = []uint32{} - -// profile ID length -var ext_1 = []byte{0x77, 0x88, 0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x04, 0x03, 0x02, 0x01} // len: 12 -var ext_2 = []byte{0x77, 0x89, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x04, 0x03, 0x02, 0x01, 0x11, 0x22, 0x33, 0x44} // len: 16 -var ext_3 = []byte{0x77, 0x8a, 0x00, 0x00} // len: 4 -var ext_4 = []byte{} - -func headerCheck(rp *DataPacket, t *testing.T) (result bool) { - result = false - - ssrc := rp.Ssrc() - if ssrc != 0x01020304 { - t.Error(fmt.Sprintf("SSRC check failed. Expected: 0x1020304, got: %x\n", ssrc)) - return - } - seq := rp.Sequence() - if seq != 0x4711 { - t.Error(fmt.Sprintf("Sequence check failed. Expected: 0x4711, got: %x\n", seq)) - return - } - ts := rp.Timestamp() - if ts != 0xF0E0D0C0 { - t.Error(fmt.Sprintf("Timestamp check failed. Expected: 0xF0E0D0C0, got: %x\n", ts)) - return - } - - result = true - return -} - -func csrcTest(rp *DataPacket, t *testing.T, csrc []uint32, run int) (result bool) { - result = false - rp.SetCsrcList(csrc) - - if !headerCheck(rp, t) { - return - } - ssrcLen := rp.CsrcCount() - if int(ssrcLen) != len(csrc) { - t.Error(fmt.Sprintf("CSRC-%d length check failed. Expected: %d, got: %d\n", run, len(csrc), ssrcLen)) - return - } - csrcTmp := rp.CsrcList() - for i, v := range csrcTmp { - if v != csrc[i] { - t.Error(fmt.Sprintf("CSRC-%d check failed at %i. Expected: %x, got: %x\n", run, i, csrc[i], csrcTmp[i])) - return - } - } - use := rp.InUse() - expected := rtpHeaderLength + len(payload) + len(csrc)*4 + rp.ExtensionLength() - if use != expected { - t.Error(fmt.Sprintf("Payload-CSRC-%d length check failed. Expected: %d, got: %d\n", run, expected, use)) - return - } - pay := rp.Payload() - for i, v := range payload { - if v != pay[i] { - t.Error(fmt.Sprintf("Payload-CSRC-%d check failed at %i. Expected: %x, got: %x\n", run, i, payload[i], pay[i])) - return - } - } - if *verbose { - rp.Print(fmt.Sprintf("CSRC-%d check", run)) - } - result = true - return -} - -func extTest(rp *DataPacket, t *testing.T, ext []byte, run int) (result bool) { - result = false - rp.SetExtension(ext) - - if !headerCheck(rp, t) { - return - } - extLen := rp.ExtensionLength() - if extLen != len(ext) { - t.Error(fmt.Sprintf("EXT-%d length check failed. Expected: %d, got: %d\n", run, len(ext), extLen)) - return - } - extTmp := rp.Extension() - for i, v := range extTmp { - if v != ext[i] { - t.Error(fmt.Sprintf("EXT-%d check failed at %i. Expected: %x, got: %x\n", run, i, ext[i], extTmp[i])) - return - } - } - use := rp.InUse() - expected := rtpHeaderLength + len(payload) + int(rp.CsrcCount()*4) + len(ext) - if use != expected { - t.Error(fmt.Sprintf("Payload-EXT-%d length check failed. Expected: %d, got: %d\n", run, expected, use)) - return - } - pay := rp.Payload() - for i, v := range payload { - if v != pay[i] { - t.Error(fmt.Sprintf("Payload-EXT-%d check failed at %i. Expected: %x, got: %x\n", run, i, payload[i], pay[i])) - return - } - } - if *verbose { - rp.Print(fmt.Sprintf("EXT-%d check", run)) - } - result = true - return -} - -func rtpPacket(t *testing.T) { - - // Prepare some data to create a RP session, RTP stream and then RTP packets - port := 5220 - local, _ := net.ResolveIPAddr("ip", "127.0.0.1") - - // Create a UDP transport with "local" address and use this for a "local" RTP session - // The RTP session uses the transport to receive and send RTP packets to the remote peers. - tpLocal, _ := NewTransportUDP(local, port) - - // TransportUDP implements RtpTransportWrite and RtpTransportRecv interfaces thus - // set it in the RtpSession for both interfaces - rsLocal := NewSession(tpLocal, tpLocal) - - // Create a media stream. - // The SSRC identifies the stream. Each stream has its own sequence number and other - // context. A RTP session can have several RTP stream for example to send several - // streams of the same media. - // - strIdx, _ := rsLocal.NewSsrcStreamOut(&Address{local.IP, port, port+1}, 0x01020304, 0x4711) - rsLocal.SsrcStreamOutForIndex(strIdx).SetPayloadType(3) - - // Create a RTP packet suitable for standard stream (index 0) with a payload length of 160 bytes - // The method initializes the RTP packet with SSRC, sequence number, and RTP version number. - // If the payload type was set with the RTP stream then the payload type is also set in - // the RTP packet - rp := rsLocal.NewDataPacket(160) - rp.SetTimestamp(0xF0E0D0C0) - - if !headerCheck(rp, t) { - return - } - use := rp.InUse() - if use != rtpHeaderLength { - t.Error(fmt.Sprintf("RTP header length check failed. Expected: 12, got: %d\n", use)) - return - } - - // Check basic payload handling - rp.SetPayload(payload) - - if *verbose { - rp.Print("Basic payload") - } - use = rp.InUse() - if use != rtpHeaderLength+len(payload) { - t.Error(fmt.Sprintf("Packet length check failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) - return - } - pay := rp.Payload() - if len(pay) != len(payload) { - t.Error(fmt.Sprintf("Payload length check failed. Expected: %d, got: %d\n", len(payload), len(pay))) - return - } - for i, v := range payload { - if v != pay[i] { - t.Error(fmt.Sprintf("Payload check failed at %i. Expected: %x, got: %x\n", i, payload[i], pay[i])) - return - } - } - buf := rp.Buffer() - for i, v := range buf[0:use] { - if v != initialPacket[i] { - t.Error(fmt.Sprintf("Basic header buffer check failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) - return - } - } - rp.SetMarker(true) - if buf[markerPtOffset] != 0x83 { - t.Error(fmt.Sprintf("Marker/PT check 1 failed. Expected: 0x83, got: %x\n", buf[markerPtOffset])) - return - } - pt := rp.PayloadType() - if pt != 3 { - t.Error(fmt.Sprintf("PT-after-Marker check 1 failed. Expected: 3, got: %x\n", pt)) - return - } - rp.SetMarker(false) - if buf[markerPtOffset] != 3 { - t.Error(fmt.Sprintf("Marker/PT check 2 failed. Expected: 3, got: %x\n", buf[markerPtOffset])) - return - } - pt = rp.PayloadType() - if pt != 3 { - t.Error(fmt.Sprintf("PT-after-Marker check 2 failed. Expected: 3, got: %x\n", pt)) - return - } - - // Delete payload, prepare for padding tests - rp.SetPayload(payloadNull) - - // Check padding - rp.SetPadding(true, 0) // zero defaults to multiple of int32 (4) - - // Check payload handling with padding; len(payload) + rtpHeaderLength is 22, thus 2 bytes padding - rp.SetPayload(payload) - use = rp.InUse() - if use != (rtpHeaderLength + len(payload) + 2) { - t.Error(fmt.Sprintf("Padding packet length check failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload)+2, use)) - return - } - pay = rp.Payload() - if len(pay) != len(payload) { - t.Error(fmt.Sprintf("Padding payload length check failed. Expected: %d, got: %d\n", len(payload), len(pay))) - return - } - for i, v := range payload { - if v != pay[i] { - t.Error(fmt.Sprintf("Payload check failed at %i. Expected: %x, got: %x\n", i, payload[i], pay[i])) - return - } - } - if *verbose { - rp.Print("Payload with padding") - } - - // delete padded payload and switch off padding - rp.SetPayload(payloadNull) - rp.SetPadding(false, 0) - - // set normal payload without padding to perform other tests - rp.SetPayload(payload) - - // Now check CSRC list handling. These modify InUse and shift the payload inside the packet buffer. - if !csrcTest(rp, t, csrc_1, 1) || !csrcTest(rp, t, csrc_2, 2) || !csrcTest(rp, t, csrc_3, 3) { - return - } - // After last CSCR test the packet shall be in initial state, check it. - use = rp.InUse() - if use != rtpHeaderLength+len(payload) { - t.Error(fmt.Sprintf("Packet length check afer CSRC failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) - return - } - buf = rp.Buffer() - for i, v := range buf[0:use] { - if v != initialPacket[i] { - t.Error(fmt.Sprintf("Basic header buffer check after CSRC failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) - return - } - } - if !extTest(rp, t, ext_1, 1) || !extTest(rp, t, ext_2, 2) || !extTest(rp, t, ext_3, 3) || !extTest(rp, t, ext_4, 4) { - return - } - // After last EXT test the packet shall be in initial state, check it. - use = rp.InUse() - if use != rtpHeaderLength+len(payload) { - t.Error(fmt.Sprintf("Packet length check afer EXT failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) - return - } - buf = rp.Buffer() - for i, v := range buf[0:use] { - if v != initialPacket[i] { - t.Error(fmt.Sprintf("Basic header buffer check after CSRC failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) - return - } - } - if !csrcTest(rp, t, csrc_1, 1) || !extTest(rp, t, ext_1, 1) { - return - } - if *verbose { - rp.Print("CSCR/EXT combined") - } - use = rp.InUse() - expected := rtpHeaderLength + len(payload) + len(csrc_1)*4 + len(ext_1) - if use != expected { - t.Error(fmt.Sprintf("Packet length check afer CSRC/EXT failed. Expected: %d, got: %d\n", expected, use)) - return - } -} - -func intervalCheck(t *testing.T) { -// members, senders, RTCP bandwidth, packet length, weSent, initial - tm, _ := rtcpInterval(1, 0, 3500.0, 80.0, false, true) - fmt.Printf("Interval: %d\n", tm) - tm, _ = rtcpInterval(100, 0, 3500.0, 160.0, false, true) - fmt.Printf("Interval: %d\n", tm) -} - -func ntpCheck(t *testing.T) { - tm := time.Now().UnixNano() - high, low := toNtpStamp(tm) - tm1 := fromNtp(high, low) - diff := tm - tm1 - if diff < -1 || diff > 1 { - t.Error(fmt.Sprintf("NTP time conversion check failed. Expected range: +/-1 got: %d\n", diff)) - return - } -} - -func parseFlags() { - if !parsed { - flag.Parse() - parsed = true - } -} - -func TestRtpPacket(t *testing.T) { - parseFlags() - rtpPacket(t) - ntpCheck(t) -// intervalCheck(t) -} +// Copyright (C) 2011 Werner Dittmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Authors: Werner Dittmann +// + +package rtp + +import ( + "flag" + "fmt" + "net" + "testing" + "time" +) + +var verbose *bool = flag.Bool("verbose", false, "Verbose output during tests") +var parsed bool + +var payload = []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20} +var payloadNull = []byte{} + +var initialPacket = []byte{0x80, 0x03, 0x47, 0x11, 0xf0, 0xe0, 0xd0, 0xc0, 0x01, 0x02, 0x03, 0x04, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20} + +var csrc_1 = []uint32{0x21435465, 0x65544322} +var csrc_2 = []uint32{0x23445566, 0x66554423, 0x87766554} +var csrc_3 = []uint32{} + +// profile ID length +var ext_1 = []byte{0x77, 0x88, 0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x04, 0x03, 0x02, 0x01} // len: 12 +var ext_2 = []byte{0x77, 0x89, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x04, 0x03, 0x02, 0x01, 0x11, 0x22, 0x33, 0x44} // len: 16 +var ext_3 = []byte{0x77, 0x8a, 0x00, 0x00} // len: 4 +var ext_4 = []byte{} + +func headerCheck(rp *DataPacket, t *testing.T) (result bool) { + result = false + + ssrc := rp.Ssrc() + if ssrc != 0x01020304 { + t.Error(fmt.Sprintf("SSRC check failed. Expected: 0x1020304, got: %x\n", ssrc)) + return + } + seq := rp.Sequence() + if seq != 0x4711 { + t.Error(fmt.Sprintf("Sequence check failed. Expected: 0x4711, got: %x\n", seq)) + return + } + ts := rp.Timestamp() + if ts != 0xF0E0D0C0 { + t.Error(fmt.Sprintf("Timestamp check failed. Expected: 0xF0E0D0C0, got: %x\n", ts)) + return + } + + result = true + return +} + +func csrcTest(rp *DataPacket, t *testing.T, csrc []uint32, run int) (result bool) { + result = false + rp.SetCsrcList(csrc) + + if !headerCheck(rp, t) { + return + } + ssrcLen := rp.CsrcCount() + if int(ssrcLen) != len(csrc) { + t.Error(fmt.Sprintf("CSRC-%d length check failed. Expected: %d, got: %d\n", run, len(csrc), ssrcLen)) + return + } + csrcTmp := rp.CsrcList() + for i, v := range csrcTmp { + if v != csrc[i] { + t.Error(fmt.Sprintf("CSRC-%d check failed at %i. Expected: %x, got: %x\n", run, i, csrc[i], csrcTmp[i])) + return + } + } + use := rp.InUse() + expected := rtpHeaderLength + len(payload) + len(csrc)*4 + rp.ExtensionLength() + if use != expected { + t.Error(fmt.Sprintf("Payload-CSRC-%d length check failed. Expected: %d, got: %d\n", run, expected, use)) + return + } + pay := rp.Payload() + for i, v := range payload { + if v != pay[i] { + t.Error(fmt.Sprintf("Payload-CSRC-%d check failed at %i. Expected: %x, got: %x\n", run, i, payload[i], pay[i])) + return + } + } + if *verbose { + rp.Print(fmt.Sprintf("CSRC-%d check", run)) + } + result = true + return +} + +func extTest(rp *DataPacket, t *testing.T, ext []byte, run int) (result bool) { + result = false + rp.SetExtension(ext) + + if !headerCheck(rp, t) { + return + } + extLen := rp.ExtensionLength() + if extLen != len(ext) { + t.Error(fmt.Sprintf("EXT-%d length check failed. Expected: %d, got: %d\n", run, len(ext), extLen)) + return + } + extTmp := rp.Extension() + for i, v := range extTmp { + if v != ext[i] { + t.Error(fmt.Sprintf("EXT-%d check failed at %i. Expected: %x, got: %x\n", run, i, ext[i], extTmp[i])) + return + } + } + use := rp.InUse() + expected := rtpHeaderLength + len(payload) + int(rp.CsrcCount()*4) + len(ext) + if use != expected { + t.Error(fmt.Sprintf("Payload-EXT-%d length check failed. Expected: %d, got: %d\n", run, expected, use)) + return + } + pay := rp.Payload() + for i, v := range payload { + if v != pay[i] { + t.Error(fmt.Sprintf("Payload-EXT-%d check failed at %i. Expected: %x, got: %x\n", run, i, payload[i], pay[i])) + return + } + } + if *verbose { + rp.Print(fmt.Sprintf("EXT-%d check", run)) + } + result = true + return +} + +func rtpPacket(t *testing.T) { + + // Prepare some data to create a RP session, RTP stream and then RTP packets + port := 5220 + local, _ := net.ResolveIPAddr("ip", "127.0.0.1") + + // Create a UDP transport with "local" address and use this for a "local" RTP session + // The RTP session uses the transport to receive and send RTP packets to the remote peers. + tpLocal, _ := NewTransportUDP(local, port) + + // TransportUDP implements RtpTransportWrite and RtpTransportRecv interfaces thus + // set it in the RtpSession for both interfaces + rsLocal := NewSession(tpLocal, tpLocal) + + // Create a media stream. + // The SSRC identifies the stream. Each stream has its own sequence number and other + // context. A RTP session can have several RTP stream for example to send several + // streams of the same media. + // + strIdx, _ := rsLocal.NewSsrcStreamOut(&Address{local.IP, port, port + 1}, 0x01020304, 0x4711) + rsLocal.SsrcStreamOutForIndex(strIdx).SetPayloadType(3) + + // Create a RTP packet suitable for standard stream (index 0) with a payload length of 160 bytes + // The method initializes the RTP packet with SSRC, sequence number, and RTP version number. + // If the payload type was set with the RTP stream then the payload type is also set in + // the RTP packet + rp := rsLocal.NewDataPacket(160) + rp.SetTimestamp(0xF0E0D0C0) + + if !headerCheck(rp, t) { + return + } + use := rp.InUse() + if use != rtpHeaderLength { + t.Error(fmt.Sprintf("RTP header length check failed. Expected: 12, got: %d\n", use)) + return + } + + // Check basic payload handling + rp.SetPayload(payload) + + if *verbose { + rp.Print("Basic payload") + } + use = rp.InUse() + if use != rtpHeaderLength+len(payload) { + t.Error(fmt.Sprintf("Packet length check failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) + return + } + pay := rp.Payload() + if len(pay) != len(payload) { + t.Error(fmt.Sprintf("Payload length check failed. Expected: %d, got: %d\n", len(payload), len(pay))) + return + } + for i, v := range payload { + if v != pay[i] { + t.Error(fmt.Sprintf("Payload check failed at %i. Expected: %x, got: %x\n", i, payload[i], pay[i])) + return + } + } + buf := rp.Buffer() + for i, v := range buf[0:use] { + if v != initialPacket[i] { + t.Error(fmt.Sprintf("Basic header buffer check failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) + return + } + } + rp.SetMarker(true) + if buf[markerPtOffset] != 0x83 { + t.Error(fmt.Sprintf("Marker/PT check 1 failed. Expected: 0x83, got: %x\n", buf[markerPtOffset])) + return + } + pt := rp.PayloadType() + if pt != 3 { + t.Error(fmt.Sprintf("PT-after-Marker check 1 failed. Expected: 3, got: %x\n", pt)) + return + } + rp.SetMarker(false) + if buf[markerPtOffset] != 3 { + t.Error(fmt.Sprintf("Marker/PT check 2 failed. Expected: 3, got: %x\n", buf[markerPtOffset])) + return + } + pt = rp.PayloadType() + if pt != 3 { + t.Error(fmt.Sprintf("PT-after-Marker check 2 failed. Expected: 3, got: %x\n", pt)) + return + } + + // Delete payload, prepare for padding tests + rp.SetPayload(payloadNull) + + // Check padding + rp.SetPadding(true, 0) // zero defaults to multiple of int32 (4) + + // Check payload handling with padding; len(payload) + rtpHeaderLength is 22, thus 2 bytes padding + rp.SetPayload(payload) + use = rp.InUse() + if use != (rtpHeaderLength + len(payload) + 2) { + t.Error(fmt.Sprintf("Padding packet length check failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload)+2, use)) + return + } + pay = rp.Payload() + if len(pay) != len(payload) { + t.Error(fmt.Sprintf("Padding payload length check failed. Expected: %d, got: %d\n", len(payload), len(pay))) + return + } + for i, v := range payload { + if v != pay[i] { + t.Error(fmt.Sprintf("Payload check failed at %i. Expected: %x, got: %x\n", i, payload[i], pay[i])) + return + } + } + if *verbose { + rp.Print("Payload with padding") + } + + // delete padded payload and switch off padding + rp.SetPayload(payloadNull) + rp.SetPadding(false, 0) + + // set normal payload without padding to perform other tests + rp.SetPayload(payload) + + // Now check CSRC list handling. These modify InUse and shift the payload inside the packet buffer. + if !csrcTest(rp, t, csrc_1, 1) || !csrcTest(rp, t, csrc_2, 2) || !csrcTest(rp, t, csrc_3, 3) { + return + } + // After last CSCR test the packet shall be in initial state, check it. + use = rp.InUse() + if use != rtpHeaderLength+len(payload) { + t.Error(fmt.Sprintf("Packet length check afer CSRC failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) + return + } + buf = rp.Buffer() + for i, v := range buf[0:use] { + if v != initialPacket[i] { + t.Error(fmt.Sprintf("Basic header buffer check after CSRC failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) + return + } + } + if !extTest(rp, t, ext_1, 1) || !extTest(rp, t, ext_2, 2) || !extTest(rp, t, ext_3, 3) || !extTest(rp, t, ext_4, 4) { + return + } + // After last EXT test the packet shall be in initial state, check it. + use = rp.InUse() + if use != rtpHeaderLength+len(payload) { + t.Error(fmt.Sprintf("Packet length check afer EXT failed. Expected: %d, got: %d\n", rtpHeaderLength+len(payload), use)) + return + } + buf = rp.Buffer() + for i, v := range buf[0:use] { + if v != initialPacket[i] { + t.Error(fmt.Sprintf("Basic header buffer check after CSRC failed at %d. Expected: %x, got: %x\n", i, initialPacket[i], buf[i])) + return + } + } + if !csrcTest(rp, t, csrc_1, 1) || !extTest(rp, t, ext_1, 1) { + return + } + if *verbose { + rp.Print("CSCR/EXT combined") + } + use = rp.InUse() + expected := rtpHeaderLength + len(payload) + len(csrc_1)*4 + len(ext_1) + if use != expected { + t.Error(fmt.Sprintf("Packet length check afer CSRC/EXT failed. Expected: %d, got: %d\n", expected, use)) + return + } +} + +func intervalCheck(t *testing.T) { + // members, senders, RTCP bandwidth, packet length, weSent, initial + tm, _ := rtcpInterval(1, 0, 3500.0, 80.0, false, true) + fmt.Printf("Interval: %d\n", tm) + tm, _ = rtcpInterval(100, 0, 3500.0, 160.0, false, true) + fmt.Printf("Interval: %d\n", tm) +} + +func ntpCheck(t *testing.T) { + tm := time.Now().UnixNano() + high, low := toNtpStamp(tm) + tm1 := fromNtp(high, low) + diff := tm - tm1 + if diff < -1 || diff > 1 { + t.Error(fmt.Sprintf("NTP time conversion check failed. Expected range: +/-1 got: %d\n", diff)) + return + } +} + +func parseFlags() { + if !parsed { + flag.Parse() + parsed = true + } +} + +func TestRtpPacket(t *testing.T) { + parseFlags() + rtpPacket(t) + ntpCheck(t) + // intervalCheck(t) +} diff --git a/src/net/rtp/doc.go b/src/net/rtp/doc.go index dabe8b3..03c2b36 100644 --- a/src/net/rtp/doc.go +++ b/src/net/rtp/doc.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -67,7 +67,7 @@ RTP data packets The Go RTP stack implementation supports the necessary methods to access and modify the contents and header fields of a RTP data packet. Most often application only deal with the payload and timestamp of RTP data -packets. The RTP stack maintains the other fields and keeps track of them. +packets. The RTP stack maintains the other fields and keeps track of them. The packet module implements a leaky buffer mechanism to reduce the number of dynamically allocated packets. While not an absolute requirement it is advised @@ -99,22 +99,22 @@ transport module. The following code snippet shows how this works: var localPort = 5220 var local, _ = net.ResolveIPAddr("ip", "127.0.0.1") - + var remotePort = 5222 var remote, _ = net.ResolveIPAddr("ip", "127.0.0.1") - + ... - + // Create a UDP transport with "local" address and use this for a "local" RTP session // The RTP session uses the transport to receive and send RTP packets to the remote peer. tpLocal, _ := rtp.NewTransportUDP(local, localPort) - + // TransportUDP implements TransportWrite and TransportRecv interfaces thus // use it as write and read modules for the Session. rsLocal = rtp.NewSession(tpLocal, tpLocal) - + ... - + You may have noticed that the code does not use a standard Go UDP address but separates the IP address and the port number. This separation makes it easier to implement several network transport, such as UDP or TCP. The network @@ -240,7 +240,7 @@ would do something like go receivePacketLocal() -to fire up the receiver function. +to fire up the receiver function. Start the Session and perform RTP processing @@ -288,11 +288,11 @@ Some noteable features * The current release V1.0.0 computes the RTCP intervals based on the length of RTCP compound packets and the bandwidth allocated to RTCP. The application may set the bandwidth, if no set GoRTP makes somes educated guesses. - + * The application may set the maximum number of output and input streams even while the RTP session is active. If the application des not set GoRTP sets the values to 5 and 30 respectively. - + * GoRTP produces SR and RR reports and the associated SDES for active streams only, thus it implements the activity check as defined in chapter 6.4 @@ -306,7 +306,7 @@ streams. This restriction is mainly due to MTU contraints of modern Ethernet or DSL based networks. The MTU is usually less than 1500 bytes, GoRTP limits the RTP/RTCP packet size to 1200 bytes. The length of an RR is 24 bytes, thus 31 RR already require 774 bytes. Adding some data for SR and SDES fills -the rest. +the rest. * An application may register to a control event channel and GoRTP delivers a nice set of control and error events. The events cover: @@ -315,14 +315,14 @@ nice set of control and error events. The events cover: - RTCP events to inform about RTCP packets and received reports - Error events -* Currently GoRTP supports only SR, RR, SDES, and BYE RTCP packets. Inside +* Currently GoRTP supports only SR, RR, SDES, and BYE RTCP packets. Inside SDES GoRTP does not support SDES Private and SDES H.323 items. Further documentation Beside the documentation of the global methods, functions, variables and constants I -also documented all internally used stuff. Thus if you need more information how it +also documented all internally used stuff. Thus if you need more information how it works or if you would like to enhance GoRTP please have a look in the sources. */ diff --git a/src/net/rtp/packets.go b/src/net/rtp/packets.go index 6cf77b6..5018ae4 100644 --- a/src/net/rtp/packets.go +++ b/src/net/rtp/packets.go @@ -1,573 +1,573 @@ -// Copyright (C) 2011 Werner Dittmann -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Authors: Werner Dittmann -// +// Copyright (C) 2011 Werner Dittmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Authors: Werner Dittmann +// package rtp import ( - "encoding/binary" - "encoding/hex" - "fmt" + "encoding/binary" + "encoding/hex" + "fmt" ) const ( - defaultBufferSize = 1200 - freeListLengthRtp = 10 - freeListLengthRtcp = 5 - rtpHeaderLength = 12 - rtcpHeaderLength = 4 - rtcpSsrcLength = 4 - padToMultipleOf = 4 + defaultBufferSize = 1200 + freeListLengthRtp = 10 + freeListLengthRtcp = 5 + rtpHeaderLength = 12 + rtcpHeaderLength = 4 + rtcpSsrcLength = 4 + padToMultipleOf = 4 ) const ( - markerPtOffset = 1 - packetTypeOffset = 1 - lengthOffset = 2 - sequenceOffset = 2 - timestampOffset = sequenceOffset + 2 - ssrcOffsetRtp = timestampOffset + 4 - ssrcOffsetRtcp = sequenceOffset + 2 + markerPtOffset = 1 + packetTypeOffset = 1 + lengthOffset = 2 + sequenceOffset = 2 + timestampOffset = sequenceOffset + 2 + ssrcOffsetRtp = timestampOffset + 4 + ssrcOffsetRtcp = sequenceOffset + 2 ) const ( - version2Bit = 0x80 - extensionBit = 0x10 - paddingBit = 0x20 - markerBit = 0x80 - ccMask = 0x0f - ptMask = 0x7f - countMask = 0x1f + version2Bit = 0x80 + extensionBit = 0x10 + paddingBit = 0x20 + markerBit = 0x80 + ccMask = 0x0f + ptMask = 0x7f + countMask = 0x1f ) -// For full reference of registered RTP parameters refer to: -// http://www.iana.org/assignments/rtp-parameters +// For full reference of registered RTP parameters refer to: +// http://www.iana.org/assignments/rtp-parameters -// RTCP packet types +// RTCP packet types const ( - RtcpSR = 200 // SR sender report [RFC3550] - RtcpRR = 201 // RR receiver report [RFC3550] - RtcpSdes = 202 // SDES source description [RFC3550] - RtcpBye = 203 // BYE goodbye [RFC3550] - RtcpApp = 204 // APP application-defined [RFC3550] - RtcpRtpfb = 205 // RTPFB Generic RTP Feedback [RFC4585] - RtcpPsfb = 206 // PSFB Payload-specific [RFC4585] - RtcpXr = 207 // XR extended report [RFC3611] + RtcpSR = 200 // SR sender report [RFC3550] + RtcpRR = 201 // RR receiver report [RFC3550] + RtcpSdes = 202 // SDES source description [RFC3550] + RtcpBye = 203 // BYE goodbye [RFC3550] + RtcpApp = 204 // APP application-defined [RFC3550] + RtcpRtpfb = 205 // RTPFB Generic RTP Feedback [RFC4585] + RtcpPsfb = 206 // PSFB Payload-specific [RFC4585] + RtcpXr = 207 // XR extended report [RFC3611] ) -// RTCP SDES item types +// RTCP SDES item types const ( - SdesEnd = iota // END end of SDES list [RFC3550] - SdesCname // CNAME canonical name [RFC3550] - SdesName // NAME user name [RFC3550] - SdesEmail // EMAIL user's electronic mail address [RFC3550] - SdesPhone // PHONE user's phone number [RFC3550] - SdesLoc // LOC geographic user location [RFC3550] - SdesTool // TOOL name of application or tool [RFC3550] - SdesNote // NOTE notice about the source [RFC3550] - SdesPriv // PRIV private extensions [RFC3550] - SdesH323Caddr // H323-CADDR H.323 callable address [Kumar] - sdesMax + SdesEnd = iota // END end of SDES list [RFC3550] + SdesCname // CNAME canonical name [RFC3550] + SdesName // NAME user name [RFC3550] + SdesEmail // EMAIL user's electronic mail address [RFC3550] + SdesPhone // PHONE user's phone number [RFC3550] + SdesLoc // LOC geographic user location [RFC3550] + SdesTool // TOOL name of application or tool [RFC3550] + SdesNote // NOTE notice about the source [RFC3550] + SdesPriv // PRIV private extensions [RFC3550] + SdesH323Caddr // H323-CADDR H.323 callable address [Kumar] + sdesMax ) -// Length of fixed report blocks in bytes +// Length of fixed report blocks in bytes const ( - senderInfoLen = 20 - reportBlockLen = 24 + senderInfoLen = 20 + reportBlockLen = 24 ) -// nullArray is what it's names says: a long array filled with zeros. -// used to clear (fill with zeros) arrays/slices inside a buffer by copying. +// nullArray is what it's names says: a long array filled with zeros. +// used to clear (fill with zeros) arrays/slices inside a buffer by copying. var nullArray [1200]byte type RawPacket struct { - inUse int - padTo int - isFree bool - fromAddr Address - buffer []byte -} - -// Buffer returns the internal buffer in raw format. -// Usually only other Transports use the buffer in raw format, for example to encrypt -// or decrypt the buffer. -// Always call Buffer() just before the the buffer is actually used because several packet -// handling functions may re-allocate buffers. + inUse int + padTo int + isFree bool + fromAddr Address + buffer []byte +} + +// Buffer returns the internal buffer in raw format. +// Usually only other Transports use the buffer in raw format, for example to encrypt +// or decrypt the buffer. +// Always call Buffer() just before the the buffer is actually used because several packet +// handling functions may re-allocate buffers. func (raw *RawPacket) Buffer() []byte { - return raw.buffer + return raw.buffer } -// InUse returns the number of valid bytes in the packet buffer. -// Several function modify the inUse variable, for example when copying payload or setting extensions -// in the RTP packet. Thus "buffer[0:inUse]" is the slice inside the buffer that will be sent or -// was received. +// InUse returns the number of valid bytes in the packet buffer. +// Several function modify the inUse variable, for example when copying payload or setting extensions +// in the RTP packet. Thus "buffer[0:inUse]" is the slice inside the buffer that will be sent or +// was received. func (rp *RawPacket) InUse() int { - return rp.inUse + return rp.inUse } -// *** RTP specific functions start here *** +// *** RTP specific functions start here *** -// RTP packet type to define RTP specific functions +// RTP packet type to define RTP specific functions type DataPacket struct { - RawPacket - payloadLength int16 + RawPacket + payloadLength int16 } var freeListRtp = make(chan *DataPacket, freeListLengthRtp) func newDataPacket() (rp *DataPacket) { - // Grab a packet if available; allocate if not. - select { - case rp = <-freeListRtp: // Got one; nothing more to do. - default: - rp = new(DataPacket) // None free, so allocate a new one. - rp.buffer = make([]byte, defaultBufferSize) - } - rp.buffer[0] = version2Bit // RTP: V = 2, P, X, CC = 0 - rp.inUse = rtpHeaderLength - rp.isFree = false - return -} - -// FreePacket returns the packet to the free RTP list. -// A packet marked as free is ignored, thus calling FreePacket multiple times for the same -// packet is possible. + // Grab a packet if available; allocate if not. + select { + case rp = <-freeListRtp: // Got one; nothing more to do. + default: + rp = new(DataPacket) // None free, so allocate a new one. + rp.buffer = make([]byte, defaultBufferSize) + } + rp.buffer[0] = version2Bit // RTP: V = 2, P, X, CC = 0 + rp.inUse = rtpHeaderLength + rp.isFree = false + return +} + +// FreePacket returns the packet to the free RTP list. +// A packet marked as free is ignored, thus calling FreePacket multiple times for the same +// packet is possible. func (rp *DataPacket) FreePacket() { - if rp.isFree { - return - } - rp.buffer[0] = 0 // invalidate RTP packet - rp.inUse = 0 - rp.padTo = 0 - rp.fromAddr.DataPort = 0 - rp.fromAddr.IpAddr = nil - rp.isFree = true - - select { - case freeListRtp <- rp: // Packet on free list; nothing more to do. - default: // Free list full, just carry on. - } -} - -// CsrcCount return the number of CSRC values in this packet + if rp.isFree { + return + } + rp.buffer[0] = 0 // invalidate RTP packet + rp.inUse = 0 + rp.padTo = 0 + rp.fromAddr.DataPort = 0 + rp.fromAddr.IpAddr = nil + rp.isFree = true + + select { + case freeListRtp <- rp: // Packet on free list; nothing more to do. + default: // Free list full, just carry on. + } +} + +// CsrcCount return the number of CSRC values in this packet func (rp *DataPacket) CsrcCount() uint8 { - return rp.buffer[0] & ccMask + return rp.buffer[0] & ccMask } -// SetCsrcList takes the CSRC in this list, converts from host to network order and sets into the RTP packet. -// The new CSRC list replaces an existing CSCR list. The list can have a maximum length of 16 CSCR values, -// if the list contains more values the method leaves the RTP packet untouched. +// SetCsrcList takes the CSRC in this list, converts from host to network order and sets into the RTP packet. +// The new CSRC list replaces an existing CSCR list. The list can have a maximum length of 16 CSCR values, +// if the list contains more values the method leaves the RTP packet untouched. func (rp *DataPacket) SetCsrcList(csrc []uint32) { - if len(csrc) > 16 { - return - } - // For this method: content is any data after an existing (or new) CSRC list. This - // includes RTP extension data and payload. - offsetOld := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to old content - offsetNew := len(csrc)*4 + rtpHeaderLength // offset to new content - - newInUse := offsetNew + rp.inUse - offsetOld - if newInUse > cap(rp.buffer) { - return - } - tmpRp := newDataPacket() // get a new packet first - newBuf := tmpRp.buffer // and get its buffer - - copy(newBuf, rp.buffer[0:rtpHeaderLength]) // copy fixed header - copy(newBuf[offsetNew:], rp.buffer[offsetOld:rp.inUse]) // copy over old content - - for i := 0; i < len(csrc); i++ { - binary.BigEndian.PutUint32(newBuf[rtpHeaderLength+i*4:], csrc[i]) // CSCR in network order - } - tmpRp.buffer = rp.buffer // switch buffers - rp.buffer = newBuf - tmpRp.FreePacket() // free temporary RTP packet - - rp.buffer[0] &^= ccMask // clear old length - rp.buffer[0] |= byte(len(csrc) & ccMask) - rp.inUse = newInUse -} - -// CsrcList returns the list of CSRC values as uint32 slice in host horder + if len(csrc) > 16 { + return + } + // For this method: content is any data after an existing (or new) CSRC list. This + // includes RTP extension data and payload. + offsetOld := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to old content + offsetNew := len(csrc)*4 + rtpHeaderLength // offset to new content + + newInUse := offsetNew + rp.inUse - offsetOld + if newInUse > cap(rp.buffer) { + return + } + tmpRp := newDataPacket() // get a new packet first + newBuf := tmpRp.buffer // and get its buffer + + copy(newBuf, rp.buffer[0:rtpHeaderLength]) // copy fixed header + copy(newBuf[offsetNew:], rp.buffer[offsetOld:rp.inUse]) // copy over old content + + for i := 0; i < len(csrc); i++ { + binary.BigEndian.PutUint32(newBuf[rtpHeaderLength+i*4:], csrc[i]) // CSCR in network order + } + tmpRp.buffer = rp.buffer // switch buffers + rp.buffer = newBuf + tmpRp.FreePacket() // free temporary RTP packet + + rp.buffer[0] &^= ccMask // clear old length + rp.buffer[0] |= byte(len(csrc) & ccMask) + rp.inUse = newInUse +} + +// CsrcList returns the list of CSRC values as uint32 slice in host horder func (rp *DataPacket) CsrcList() (list []uint32) { - list = make([]uint32, rp.CsrcCount()) - for i := 0; i < len(list); i++ { - list[i] = binary.BigEndian.Uint32(rp.buffer[rtpHeaderLength+i*4:]) - } - return + list = make([]uint32, rp.CsrcCount()) + for i := 0; i < len(list); i++ { + list[i] = binary.BigEndian.Uint32(rp.buffer[rtpHeaderLength+i*4:]) + } + return } -// SetExtension takes a byte slice and set it as extension into the RTP packet. -// The byte slice must conform to one of the formats specified in RFC 3550 or RFC 5258, thus -// the length must be a multiple of uint32 (4) and the length field must be in the 3rd and 4th -// byte (uint16) and its value must adhere to RFC 3550 / RFC 5258. +// SetExtension takes a byte slice and set it as extension into the RTP packet. +// The byte slice must conform to one of the formats specified in RFC 3550 or RFC 5258, thus +// the length must be a multiple of uint32 (4) and the length field must be in the 3rd and 4th +// byte (uint16) and its value must adhere to RFC 3550 / RFC 5258. func (rp *DataPacket) SetExtension(ext []byte) { - if (len(ext) % 4) != 0 { - return - } - l := 0 - if len(ext) > 0 { - l = int((binary.BigEndian.Uint16(ext[2:]) + 1) * 4) - } - if l != len(ext) { - return - } - // For this method: content is any data after an existing (or new) Extension area. This - // is the payload. - offsetExt := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to Extension - offsetOld := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to old content - if rp.ExtensionBit() { - offsetOld += rp.ExtensionLength() - } - offsetNew := offsetExt + l // offset to new content - - newInUse := rp.inUse + l - (offsetOld - offsetExt) - if newInUse > cap(rp.buffer) { - return - } - tmpRp := newDataPacket() // get a new packet first - newBuf := tmpRp.buffer // and get its buffer - - copy(newBuf, rp.buffer[0:rtpHeaderLength]) // copy fixed header - copy(newBuf[offsetExt:], ext) // copy new extension - copy(newBuf[offsetNew:], rp.buffer[offsetOld:rp.inUse]) // copy over old content - - tmpRp.buffer = rp.buffer // switch buffers - rp.buffer = newBuf - tmpRp.FreePacket() // free temporary RTP packet - if l == 0 { - rp.buffer[0] &^= extensionBit - } else { - rp.buffer[0] |= extensionBit - } - rp.inUse = newInUse -} - -// Extension returns the byte slice of the RTP packet extension part, if not extension available it returns nil. -// This is not a copy of the extension part but the slice points into the real RTP packet buffer. + if (len(ext) % 4) != 0 { + return + } + l := 0 + if len(ext) > 0 { + l = int((binary.BigEndian.Uint16(ext[2:]) + 1) * 4) + } + if l != len(ext) { + return + } + // For this method: content is any data after an existing (or new) Extension area. This + // is the payload. + offsetExt := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to Extension + offsetOld := int(rp.CsrcCount()*4 + rtpHeaderLength) // offset to old content + if rp.ExtensionBit() { + offsetOld += rp.ExtensionLength() + } + offsetNew := offsetExt + l // offset to new content + + newInUse := rp.inUse + l - (offsetOld - offsetExt) + if newInUse > cap(rp.buffer) { + return + } + tmpRp := newDataPacket() // get a new packet first + newBuf := tmpRp.buffer // and get its buffer + + copy(newBuf, rp.buffer[0:rtpHeaderLength]) // copy fixed header + copy(newBuf[offsetExt:], ext) // copy new extension + copy(newBuf[offsetNew:], rp.buffer[offsetOld:rp.inUse]) // copy over old content + + tmpRp.buffer = rp.buffer // switch buffers + rp.buffer = newBuf + tmpRp.FreePacket() // free temporary RTP packet + if l == 0 { + rp.buffer[0] &^= extensionBit + } else { + rp.buffer[0] |= extensionBit + } + rp.inUse = newInUse +} + +// Extension returns the byte slice of the RTP packet extension part, if not extension available it returns nil. +// This is not a copy of the extension part but the slice points into the real RTP packet buffer. func (rp *DataPacket) Extension() []byte { - if !rp.ExtensionBit() { - return nil - } - offset := int(rp.CsrcCount()*4 + rtpHeaderLength) - return rp.buffer[offset : offset+rp.ExtensionLength()] + if !rp.ExtensionBit() { + return nil + } + offset := int(rp.CsrcCount()*4 + rtpHeaderLength) + return rp.buffer[offset : offset+rp.ExtensionLength()] } -// Ssrc returns the SSRC as uint32 in host order. +// Ssrc returns the SSRC as uint32 in host order. func (rp *DataPacket) Ssrc() uint32 { - return binary.BigEndian.Uint32(rp.buffer[ssrcOffsetRtp:]) + return binary.BigEndian.Uint32(rp.buffer[ssrcOffsetRtp:]) } -// SetSsrc converts SSRC from host order into network order and stores it in the RTP packet. +// SetSsrc converts SSRC from host order into network order and stores it in the RTP packet. func (rp *DataPacket) SetSsrc(ssrc uint32) { - binary.BigEndian.PutUint32(rp.buffer[ssrcOffsetRtp:], ssrc) + binary.BigEndian.PutUint32(rp.buffer[ssrcOffsetRtp:], ssrc) } -// Timestamp returns the Timestamp as uint32 in host order. +// Timestamp returns the Timestamp as uint32 in host order. func (rp *DataPacket) Timestamp() uint32 { - return binary.BigEndian.Uint32(rp.buffer[timestampOffset:]) + return binary.BigEndian.Uint32(rp.buffer[timestampOffset:]) } -// SetTimestamp converts timestamp from host order into network order and stores it in the RTP packet. +// SetTimestamp converts timestamp from host order into network order and stores it in the RTP packet. func (rp *DataPacket) SetTimestamp(timestamp uint32) { - binary.BigEndian.PutUint32(rp.buffer[timestampOffset:], timestamp) + binary.BigEndian.PutUint32(rp.buffer[timestampOffset:], timestamp) } -// SetMarker set or resets the Marker bit. -// If the parameter m is true the methods sets the Marker bit, resets it otherweise. +// SetMarker set or resets the Marker bit. +// If the parameter m is true the methods sets the Marker bit, resets it otherweise. func (rp *DataPacket) SetMarker(m bool) { - if m { - rp.buffer[markerPtOffset] |= markerBit - } else { - rp.buffer[markerPtOffset] &^= markerBit - } + if m { + rp.buffer[markerPtOffset] |= markerBit + } else { + rp.buffer[markerPtOffset] &^= markerBit + } } -// Marker returns the state of the Marker bit. -// If the Marker bit is set the method return true, otherwise it returns false +// Marker returns the state of the Marker bit. +// If the Marker bit is set the method return true, otherwise it returns false func (rp *DataPacket) Marker() bool { - return (rp.buffer[markerPtOffset] & markerBit) == markerBit + return (rp.buffer[markerPtOffset] & markerBit) == markerBit } -// SetPadding set or resets the padding bit. -// If the parameter p is true the methods sets the Padding bit, resets it otherweise. -// If parameter p is true and padTo is zero, then this method sets pads the whole -// RTP packet to a multiple of 4, otherwise the given value is used which must be -// greater than 1. -// -// NOTE: padding is only done when adding payload to the packet, thus if an application -// required padding then seeting the payload should be the last step in RTP packet creation +// SetPadding set or resets the padding bit. +// If the parameter p is true the methods sets the Padding bit, resets it otherweise. +// If parameter p is true and padTo is zero, then this method sets pads the whole +// RTP packet to a multiple of 4, otherwise the given value is used which must be +// greater than 1. +// +// NOTE: padding is only done when adding payload to the packet, thus if an application +// required padding then seeting the payload should be the last step in RTP packet creation func (rp *DataPacket) SetPadding(p bool, padTo int) { - if padTo == 0 { - padTo = padToMultipleOf - } - if p { - rp.buffer[0] |= paddingBit - rp.padTo = padTo - } else { - rp.buffer[0] &^= paddingBit - rp.padTo = 0 - } -} - -// Padding returns the state of the Padding bit. -// If the Padding bit is set the method return true, otherwise it returns false + if padTo == 0 { + padTo = padToMultipleOf + } + if p { + rp.buffer[0] |= paddingBit + rp.padTo = padTo + } else { + rp.buffer[0] &^= paddingBit + rp.padTo = 0 + } +} + +// Padding returns the state of the Padding bit. +// If the Padding bit is set the method return true, otherwise it returns false func (rp *DataPacket) Padding() bool { - return (rp.buffer[0] & paddingBit) == paddingBit + return (rp.buffer[0] & paddingBit) == paddingBit } -// SetPayloadType sets a new payload type value in the RTP packet header. +// SetPayloadType sets a new payload type value in the RTP packet header. func (rp *DataPacket) SetPayloadType(pt byte) { - rp.buffer[markerPtOffset] &^= ptMask // first: clear old type - rp.buffer[markerPtOffset] |= (pt & ptMask) + rp.buffer[markerPtOffset] &^= ptMask // first: clear old type + rp.buffer[markerPtOffset] |= (pt & ptMask) } -// PayloadType return the payload type value from RTP packet header. +// PayloadType return the payload type value from RTP packet header. func (rp *DataPacket) PayloadType() byte { - return rp.buffer[markerPtOffset] & ptMask + return rp.buffer[markerPtOffset] & ptMask } -// SetSequence converts the sequence from host order into network order and stores it in the RTP packet header. +// SetSequence converts the sequence from host order into network order and stores it in the RTP packet header. func (rp *DataPacket) SetSequence(seq uint16) { - binary.BigEndian.PutUint16(rp.buffer[sequenceOffset:], seq) + binary.BigEndian.PutUint16(rp.buffer[sequenceOffset:], seq) } -// Sequence returns the sequence number as uint16 in host order. +// Sequence returns the sequence number as uint16 in host order. func (rp *DataPacket) Sequence() uint16 { - return binary.BigEndian.Uint16(rp.buffer[sequenceOffset:]) + return binary.BigEndian.Uint16(rp.buffer[sequenceOffset:]) } -// ExtensionBit returns true if the Extension bit is set in the header, false otherwise. +// ExtensionBit returns true if the Extension bit is set in the header, false otherwise. func (rp *DataPacket) ExtensionBit() bool { - return (rp.buffer[0] & extensionBit) == extensionBit + return (rp.buffer[0] & extensionBit) == extensionBit } -// ExtensionLength returns the full length in bytes of RTP packet extension (including the main extension header). +// ExtensionLength returns the full length in bytes of RTP packet extension (including the main extension header). func (rp *DataPacket) ExtensionLength() (length int) { - if !rp.ExtensionBit() { - return 0 - } - offset := int16(rp.CsrcCount()*4 + rtpHeaderLength) // offset to extension header 32bit word - offset += 2 - length = int(binary.BigEndian.Uint16(rp.buffer[offset:])) + 1 // +1 for the main extension header word - length *= 4 - return -} - -// Payload returns the byte slice of the payload after removing length of possible padding. -// -// The slice is not a copy of the payload but the slice points into the real RTP packet buffer. + if !rp.ExtensionBit() { + return 0 + } + offset := int16(rp.CsrcCount()*4 + rtpHeaderLength) // offset to extension header 32bit word + offset += 2 + length = int(binary.BigEndian.Uint16(rp.buffer[offset:])) + 1 // +1 for the main extension header word + length *= 4 + return +} + +// Payload returns the byte slice of the payload after removing length of possible padding. +// +// The slice is not a copy of the payload but the slice points into the real RTP packet buffer. func (rp *DataPacket) Payload() []byte { - payOffset := int(rp.CsrcCount()*4+rtpHeaderLength) + rp.ExtensionLength() - pad := 0 - if rp.Padding() { - pad = int(rp.buffer[rp.inUse-1]) - } - return rp.buffer[payOffset : rp.inUse-pad] -} - -// SetPayload copies the contents of payload byte slice into the RTP packet, and replaces an existing payload. -// -// Only SetPayload honors the Padding bit and pads the RTP packet to a multiple of the value specified -// in SetPadding. SetPayload performs padding only if the payload length is greate zero. A payload of -// zero length removes an existing payload including a possible padding + payOffset := int(rp.CsrcCount()*4+rtpHeaderLength) + rp.ExtensionLength() + pad := 0 + if rp.Padding() { + pad = int(rp.buffer[rp.inUse-1]) + } + return rp.buffer[payOffset : rp.inUse-pad] +} + +// SetPayload copies the contents of payload byte slice into the RTP packet, and replaces an existing payload. +// +// Only SetPayload honors the Padding bit and pads the RTP packet to a multiple of the value specified +// in SetPadding. SetPayload performs padding only if the payload length is greate zero. A payload of +// zero length removes an existing payload including a possible padding func (rp *DataPacket) SetPayload(payload []byte) { - payOffset := int(rp.CsrcCount()*4+rtpHeaderLength) + rp.ExtensionLength() - payloadLenOld := rp.inUse - payOffset - - pad := 0 - padOffset := 0 - if rp.Padding() { - // adjust payloadLenOld to honor padding length - if payloadLenOld > rp.padTo { - payloadLenOld += int(rp.buffer[rp.inUse]) - } - // Reduce length of inUse by length of old content, thus remove old content - rp.inUse -= payloadLenOld - // Compute new padding length - pad = (len(payload) + rp.inUse) % rp.padTo - if pad == 0 { - pad = rp.padTo - } - } else { - // Reduce length of inUse by length of old content, thus remove old content - rp.inUse -= payloadLenOld - } - if (payOffset + len(payload) + pad) > cap(rp.buffer) { - return - } - rp.inUse += copy(rp.buffer[payOffset:], payload) - - if rp.Padding() && len(payload) > 0 { - padOffset = payOffset + len(payload) - for i := 0; i < pad-1; i++ { - rp.buffer[padOffset] = 0 - padOffset++ - } - rp.buffer[padOffset] = byte(pad) - rp.inUse += pad - } - return + payOffset := int(rp.CsrcCount()*4+rtpHeaderLength) + rp.ExtensionLength() + payloadLenOld := rp.inUse - payOffset + + pad := 0 + padOffset := 0 + if rp.Padding() { + // adjust payloadLenOld to honor padding length + if payloadLenOld > rp.padTo { + payloadLenOld += int(rp.buffer[rp.inUse]) + } + // Reduce length of inUse by length of old content, thus remove old content + rp.inUse -= payloadLenOld + // Compute new padding length + pad = (len(payload) + rp.inUse) % rp.padTo + if pad == 0 { + pad = rp.padTo + } + } else { + // Reduce length of inUse by length of old content, thus remove old content + rp.inUse -= payloadLenOld + } + if (payOffset + len(payload) + pad) > cap(rp.buffer) { + return + } + rp.inUse += copy(rp.buffer[payOffset:], payload) + + if rp.Padding() && len(payload) > 0 { + padOffset = payOffset + len(payload) + for i := 0; i < pad-1; i++ { + rp.buffer[padOffset] = 0 + padOffset++ + } + rp.buffer[padOffset] = byte(pad) + rp.inUse += pad + } + return } func (rp *DataPacket) IsValid() bool { - if (rp.buffer[0] & version2Bit) != version2Bit { - return false - } - if PayloadFormatMap[int(rp.PayloadType())] == nil { - return false - } - return true + if (rp.buffer[0] & version2Bit) != version2Bit { + return false + } + if PayloadFormatMap[int(rp.PayloadType())] == nil { + return false + } + return true } -// Print outputs a formatted dump of the RTP packet. +// Print outputs a formatted dump of the RTP packet. func (rp *DataPacket) Print(label string) { - fmt.Printf("RTP Packet at: %s\n", label) - fmt.Printf(" fixed header dump: %s\n", hex.EncodeToString(rp.buffer[0:rtpHeaderLength])) - fmt.Printf(" Version: %d\n", (rp.buffer[0]&0xc0)>>6) - fmt.Printf(" Padding: %t\n", rp.Padding()) - fmt.Printf(" Extension: %t\n", rp.ExtensionBit()) - fmt.Printf(" Contributing SRCs: %d\n", rp.CsrcCount()) - fmt.Printf(" Marker: %t\n", rp.Marker()) - fmt.Printf(" Payload type: %d (0x%x)\n", rp.PayloadType(), rp.PayloadType()) - fmt.Printf(" Sequence number: %d (0x%x)\n", rp.Sequence(), rp.Sequence()) - fmt.Printf(" Timestamp: %d (0x%x)\n", rp.Timestamp(), rp.Timestamp()) - fmt.Printf(" SSRC: %d (0x%x)\n", rp.Ssrc(), rp.Ssrc()) - - if rp.CsrcCount() > 0 { - cscr := rp.CsrcList() - fmt.Printf(" CSRC list:\n") - for i, v := range cscr { - fmt.Printf(" %d: %d (0x%x)\n", i, v, v) - } - } - if rp.ExtensionBit() { - extLen := rp.ExtensionLength() - fmt.Printf(" Extentsion length: %d\n", extLen) - offsetExt := rtpHeaderLength + int(rp.CsrcCount()*4) - fmt.Printf(" extension: %s\n", hex.EncodeToString(rp.buffer[offsetExt:offsetExt+extLen])) - } - payOffset := rtpHeaderLength + int(rp.CsrcCount()*4) + rp.ExtensionLength() - fmt.Printf(" payload: %s\n", hex.EncodeToString(rp.buffer[payOffset:rp.inUse])) -} - -// *** RTCP specific funtions start here *** - -// RTCP packet type to define RTCP specific functions. + fmt.Printf("RTP Packet at: %s\n", label) + fmt.Printf(" fixed header dump: %s\n", hex.EncodeToString(rp.buffer[0:rtpHeaderLength])) + fmt.Printf(" Version: %d\n", (rp.buffer[0]&0xc0)>>6) + fmt.Printf(" Padding: %t\n", rp.Padding()) + fmt.Printf(" Extension: %t\n", rp.ExtensionBit()) + fmt.Printf(" Contributing SRCs: %d\n", rp.CsrcCount()) + fmt.Printf(" Marker: %t\n", rp.Marker()) + fmt.Printf(" Payload type: %d (0x%x)\n", rp.PayloadType(), rp.PayloadType()) + fmt.Printf(" Sequence number: %d (0x%x)\n", rp.Sequence(), rp.Sequence()) + fmt.Printf(" Timestamp: %d (0x%x)\n", rp.Timestamp(), rp.Timestamp()) + fmt.Printf(" SSRC: %d (0x%x)\n", rp.Ssrc(), rp.Ssrc()) + + if rp.CsrcCount() > 0 { + cscr := rp.CsrcList() + fmt.Printf(" CSRC list:\n") + for i, v := range cscr { + fmt.Printf(" %d: %d (0x%x)\n", i, v, v) + } + } + if rp.ExtensionBit() { + extLen := rp.ExtensionLength() + fmt.Printf(" Extentsion length: %d\n", extLen) + offsetExt := rtpHeaderLength + int(rp.CsrcCount()*4) + fmt.Printf(" extension: %s\n", hex.EncodeToString(rp.buffer[offsetExt:offsetExt+extLen])) + } + payOffset := rtpHeaderLength + int(rp.CsrcCount()*4) + rp.ExtensionLength() + fmt.Printf(" payload: %s\n", hex.EncodeToString(rp.buffer[payOffset:rp.inUse])) +} + +// *** RTCP specific funtions start here *** + +// RTCP packet type to define RTCP specific functions. type CtrlPacket struct { - RawPacket + RawPacket } var freeListRtcp = make(chan *CtrlPacket, freeListLengthRtcp) -// newCtrlPacket gets a raw packet, initializes the first fixed RTCP header, advances inUse to point after new fixed header. +// newCtrlPacket gets a raw packet, initializes the first fixed RTCP header, advances inUse to point after new fixed header. func newCtrlPacket() (rp *CtrlPacket, offset int) { - // Grab a packet if available; allocate if not. - select { - case rp = <-freeListRtcp: // Got one; nothing more to do. - default: - rp = new(CtrlPacket) // None free, so allocate a new one. - rp.buffer = make([]byte, defaultBufferSize) - } - rp.buffer[0] = version2Bit // RTCP: V = 2, P, RC = 0 - rp.inUse = rtcpHeaderLength - offset = rtcpHeaderLength - return + // Grab a packet if available; allocate if not. + select { + case rp = <-freeListRtcp: // Got one; nothing more to do. + default: + rp = new(CtrlPacket) // None free, so allocate a new one. + rp.buffer = make([]byte, defaultBufferSize) + } + rp.buffer[0] = version2Bit // RTCP: V = 2, P, RC = 0 + rp.inUse = rtcpHeaderLength + offset = rtcpHeaderLength + return } -// addHeaderCtrl adds a new fixed RTCP header field into the compound, initializes, advances inUse to point after new fixed header. +// addHeaderCtrl adds a new fixed RTCP header field into the compound, initializes, advances inUse to point after new fixed header. func (rp *CtrlPacket) addHeaderCtrl(offset int) int { - rp.buffer[offset] = version2Bit // RTCP: V = 2, P, RC = 0 - rp.inUse += 4 - return rp.inUse + rp.buffer[offset] = version2Bit // RTCP: V = 2, P, RC = 0 + rp.inUse += 4 + return rp.inUse } -// addHeaderSsrc adds a SSRC header into the compound (usually after fixed header field), advances inUse to point after SSRC. +// addHeaderSsrc adds a SSRC header into the compound (usually after fixed header field), advances inUse to point after SSRC. func (rp *CtrlPacket) addHeaderSsrc(offset int, ssrc uint32) int { - binary.BigEndian.PutUint32(rp.buffer[offset:], ssrc) - rp.inUse += 4 - return rp.inUse + binary.BigEndian.PutUint32(rp.buffer[offset:], ssrc) + rp.inUse += 4 + return rp.inUse } func (rp *CtrlPacket) FreePacket() { - if rp.isFree { - return - } - rp.buffer[0] = 0 // invalidate RTCP packet - rp.inUse = 0 - rp.padTo = 0 - rp.fromAddr.CtrlPort = 0 - rp.fromAddr.IpAddr = nil - rp.isFree = true - - select { - case freeListRtcp <- rp: // Packet on free list; nothing more to do. - default: // Free list full, just carry on. - } -} - -// SetSsrc converts SSRC from host order into network order and stores it in the RTCP as packet sender. + if rp.isFree { + return + } + rp.buffer[0] = 0 // invalidate RTCP packet + rp.inUse = 0 + rp.padTo = 0 + rp.fromAddr.CtrlPort = 0 + rp.fromAddr.IpAddr = nil + rp.isFree = true + + select { + case freeListRtcp <- rp: // Packet on free list; nothing more to do. + default: // Free list full, just carry on. + } +} + +// SetSsrc converts SSRC from host order into network order and stores it in the RTCP as packet sender. func (rp *CtrlPacket) SetSsrc(offset int, ssrc uint32) { - binary.BigEndian.PutUint32(rp.buffer[offset+ssrcOffsetRtcp:], ssrc) + binary.BigEndian.PutUint32(rp.buffer[offset+ssrcOffsetRtcp:], ssrc) } -// Ssrc returns the SSRC of the packet sender as uint32 in host order. +// Ssrc returns the SSRC of the packet sender as uint32 in host order. func (rp *CtrlPacket) Ssrc(offset int) (ssrc uint32) { - ssrc = binary.BigEndian.Uint32(rp.buffer[offset+ssrcOffsetRtcp:]) - return + ssrc = binary.BigEndian.Uint32(rp.buffer[offset+ssrcOffsetRtcp:]) + return } -// Count returns the counter bits in the word defined by offset. -// Offset points to the first byte of the header word of a RTCP packet. +// Count returns the counter bits in the word defined by offset. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) Count(offset int) int { - return int(rp.buffer[offset] & countMask) + return int(rp.buffer[offset] & countMask) } -// SetCount returns the counter bits in the word defined by offset. -// Offset points to the first byte of the header word of a RTCP packet. +// SetCount returns the counter bits in the word defined by offset. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) SetCount(offset, count int) { - rp.buffer[offset] |= byte(count & countMask) + rp.buffer[offset] |= byte(count & countMask) } -// SetLength converts the length from host order into network order and stores it in the RTCP packet header. -// Offset points to the first byte of the header word of a RTCP packet. +// SetLength converts the length from host order into network order and stores it in the RTCP packet header. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) SetLength(offset int, length uint16) { - binary.BigEndian.PutUint16(rp.buffer[offset+lengthOffset:], length) + binary.BigEndian.PutUint16(rp.buffer[offset+lengthOffset:], length) } -// Length returns the length as uint16 in host order. -// Offset points to the first byte of the header word of a RTCP packet. +// Length returns the length as uint16 in host order. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) Length(offset int) uint16 { - return binary.BigEndian.Uint16(rp.buffer[offset+lengthOffset:]) + return binary.BigEndian.Uint16(rp.buffer[offset+lengthOffset:]) } -// Type returns the report type stored in the header word. -// Offset points to the first byte of the header word of a RTCP packet. +// Type returns the report type stored in the header word. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) Type(offset int) int { - return int(rp.buffer[offset+packetTypeOffset]) + return int(rp.buffer[offset+packetTypeOffset]) } -// SetType sets the report type in the header word. -// Offset points to the first byte of the header word of a RTCP packet. +// SetType sets the report type in the header word. +// Offset points to the first byte of the header word of a RTCP packet. func (rp *CtrlPacket) SetType(offset, packetType int) { - rp.buffer[offset+packetTypeOffset] = byte(packetType) + rp.buffer[offset+packetTypeOffset] = byte(packetType) } type senderInfo []byte @@ -575,283 +575,284 @@ type recvReport []byte type sdesChunk []byte type byeData []byte -/* - * Functions to fill/access a sender info structure +/* + * Functions to fill/access a sender info structure */ -// newSenderInfo returns a senderInfo which is positioned at the current inUse offet and advances inUse to point after senderInfo. +// newSenderInfo returns a senderInfo which is positioned at the current inUse offet and advances inUse to point after senderInfo. func (rp *CtrlPacket) newSenderInfo() (info senderInfo, offset int) { - info = rp.toSenderInfo(rp.inUse) - rp.inUse += len(info) - offset = rp.inUse - return + info = rp.toSenderInfo(rp.inUse) + rp.inUse += len(info) + offset = rp.inUse + return } -// toSenderInfo returns the senderInfo byte slice inside the RTCP packet buffer as senderInfo type, used for received RTCP packets. -// Use functions for this type to parse and access the senderInfo data. +// toSenderInfo returns the senderInfo byte slice inside the RTCP packet buffer as senderInfo type, used for received RTCP packets. +// Use functions for this type to parse and access the senderInfo data. func (rp *CtrlPacket) toSenderInfo(offset int) senderInfo { - return rp.buffer[offset : offset+senderInfoLen] + return rp.buffer[offset : offset+senderInfoLen] } -// ntpTimeStamp returns the NTP time stamp as second, fraction as unsigned 32bit in host order. +// ntpTimeStamp returns the NTP time stamp as second, fraction as unsigned 32bit in host order. func (in senderInfo) ntpTimeStamp() (seconds, fraction uint32) { - seconds = binary.BigEndian.Uint32(in[0:]) - fraction = binary.BigEndian.Uint32(in[4:]) - return + seconds = binary.BigEndian.Uint32(in[0:]) + fraction = binary.BigEndian.Uint32(in[4:]) + return } -// setNtpTimeStamp takes NTP timestamp values in host order and sets it in network order in SR. +// setNtpTimeStamp takes NTP timestamp values in host order and sets it in network order in SR. func (in senderInfo) setNtpTimeStamp(seconds, fraction uint32) { - binary.BigEndian.PutUint32(in[0:], seconds) - binary.BigEndian.PutUint32(in[4:], fraction) + binary.BigEndian.PutUint32(in[0:], seconds) + binary.BigEndian.PutUint32(in[4:], fraction) } -// rtpTimeStamp returns the RTP time stamp as 32bit unsigned in host order. +// rtpTimeStamp returns the RTP time stamp as 32bit unsigned in host order. func (in senderInfo) rtpTimeStamp() uint32 { - return binary.BigEndian.Uint32(in[8:]) + return binary.BigEndian.Uint32(in[8:]) } -// setRtpTimeStamp takes a 32 unsigned timestamp in host order and sets it in network order in SR. +// setRtpTimeStamp takes a 32 unsigned timestamp in host order and sets it in network order in SR. func (in senderInfo) setRtpTimeStamp(stamp uint32) { - binary.BigEndian.PutUint32(in[8:], stamp) + binary.BigEndian.PutUint32(in[8:], stamp) } -// packetCount returns the sender's packet count as 32bit unsigned in host order. +// packetCount returns the sender's packet count as 32bit unsigned in host order. func (in senderInfo) packetCount() uint32 { - return binary.BigEndian.Uint32(in[12:]) + return binary.BigEndian.Uint32(in[12:]) } -// setPacketCount takes a 32 unsigned counter in host order and sets it in network order in SR. +// setPacketCount takes a 32 unsigned counter in host order and sets it in network order in SR. func (in senderInfo) setPacketCount(cnt uint32) { - binary.BigEndian.PutUint32(in[12:], cnt) + binary.BigEndian.PutUint32(in[12:], cnt) } -// octetCount returns the sender's octet count as 32bit unsigned in host order. +// octetCount returns the sender's octet count as 32bit unsigned in host order. func (in senderInfo) octetCount() uint32 { - return binary.BigEndian.Uint32(in[16:]) + return binary.BigEndian.Uint32(in[16:]) } -// setOctetCount takes a 32 unsigned counter in host order and sets it in network order in SR. +// setOctetCount takes a 32 unsigned counter in host order and sets it in network order in SR. func (in senderInfo) setOctetCount(cnt uint32) { - binary.BigEndian.PutUint32(in[16:], cnt) + binary.BigEndian.PutUint32(in[16:], cnt) } -/* - * Functions to fill/access a receiver report structure +/* + * Functions to fill/access a receiver report structure */ -// newSenderInfo returns a senderInfo which is positioned at the current inUse offet and advances inUse to point after senderInfo. +// newSenderInfo returns a senderInfo which is positioned at the current inUse offet and advances inUse to point after senderInfo. func (rp *CtrlPacket) newRecvReport() (report recvReport, offset int) { - report = rp.toRecvReport(rp.inUse) - rp.inUse += len(report) - offset = rp.inUse - return + report = rp.toRecvReport(rp.inUse) + rp.inUse += len(report) + offset = rp.inUse + return } -// toRecvReport returns the report blocks byte slices inside the RTCP packet buffer as recvReport type. -// Use functions for this type to parse and access the report blocks data. +// toRecvReport returns the report blocks byte slices inside the RTCP packet buffer as recvReport type. +// Use functions for this type to parse and access the report blocks data. func (rp *CtrlPacket) toRecvReport(offset int) recvReport { - return rp.buffer[offset : offset+reportBlockLen] + return rp.buffer[offset : offset+reportBlockLen] } -// ssrc returns the receiver report SSRC as 32bit unsigned in host order. +// ssrc returns the receiver report SSRC as 32bit unsigned in host order. func (rr recvReport) ssrc() uint32 { - return binary.BigEndian.Uint32(rr[0:]) + return binary.BigEndian.Uint32(rr[0:]) } -// setSSrc takes a 32 unsigned SSRC in host order and sets it in network order in RR. +// setSSrc takes a 32 unsigned SSRC in host order and sets it in network order in RR. func (rr recvReport) setSsrc(ssrc uint32) { - binary.BigEndian.PutUint32(rr[0:], ssrc) + binary.BigEndian.PutUint32(rr[0:], ssrc) } -// packetsLost returns the receiver report packets lost data as 32bit unsigned in host order. +// packetsLost returns the receiver report packets lost data as 32bit unsigned in host order. func (rr recvReport) packetsLost() uint32 { - lost := binary.BigEndian.Uint32(rr[4:]) - return lost >> 8 + lost := binary.BigEndian.Uint32(rr[4:]) + return lost >> 8 } -// setPacketsLost takes a 32 unsigned packet lost number in host order and sets lower 24 bits in network order in RR. +// setPacketsLost takes a 32 unsigned packet lost number in host order and sets lower 24 bits in network order in RR. func (rr recvReport) setPacketsLost(pktLost uint32) { - fracSave := rr[4] - pktLost &= 0xffffff - binary.BigEndian.PutUint32(rr[4:], pktLost) - rr[4] = fracSave + fracSave := rr[4] + pktLost &= 0xffffff + binary.BigEndian.PutUint32(rr[4:], pktLost) + rr[4] = fracSave } -// packetsLostFrac returns the receiver report packets lost fractional data as byte. +// packetsLostFrac returns the receiver report packets lost fractional data as byte. func (rr recvReport) packetsLostFrac() byte { - return rr[4] + return rr[4] } -// setPacketsLostFrac takes the byte packet lost fractional and sets it in RR. +// setPacketsLostFrac takes the byte packet lost fractional and sets it in RR. func (rr recvReport) setPacketsLostFrac(frac byte) { - rr[4] = frac + rr[4] = frac } -// highestSeq returns the receiver report highest sequence number as 32bit unsigned in host order. +// highestSeq returns the receiver report highest sequence number as 32bit unsigned in host order. func (rr recvReport) highestSeq() uint32 { - return binary.BigEndian.Uint32(rr[8:]) + return binary.BigEndian.Uint32(rr[8:]) } -// setHighestSeq takes a 32 unsigned sequence number in host order and sets it in network order in RR. +// setHighestSeq takes a 32 unsigned sequence number in host order and sets it in network order in RR. func (rr recvReport) setHighestSeq(seq uint32) { - binary.BigEndian.PutUint32(rr[8:], seq) + binary.BigEndian.PutUint32(rr[8:], seq) } -// jitter returns the receiver report jitter as 32bit unsigned in host order. +// jitter returns the receiver report jitter as 32bit unsigned in host order. func (rr recvReport) jitter() uint32 { - return binary.BigEndian.Uint32(rr[12:]) + return binary.BigEndian.Uint32(rr[12:]) } -// setJitter takes a 32 unsigned jitter value in host order and sets it in network order in RR. +// setJitter takes a 32 unsigned jitter value in host order and sets it in network order in RR. func (rr recvReport) setJitter(jitter uint32) { - binary.BigEndian.PutUint32(rr[12:], jitter) + binary.BigEndian.PutUint32(rr[12:], jitter) } -// lsr returns the receiver report LSR as 32bit unsigned in host order. +// lsr returns the receiver report LSR as 32bit unsigned in host order. func (rr recvReport) lsr() uint32 { - return binary.BigEndian.Uint32(rr[16:]) + return binary.BigEndian.Uint32(rr[16:]) } -// setLsr takes a 32 unsigned LSR value in host order and sets it in network order in RR. +// setLsr takes a 32 unsigned LSR value in host order and sets it in network order in RR. func (rr recvReport) setLsr(lsr uint32) { - binary.BigEndian.PutUint32(rr[16:], lsr) + binary.BigEndian.PutUint32(rr[16:], lsr) } -// dlsr returns the receiver report DLSR as 32bit unsigned in host order. +// dlsr returns the receiver report DLSR as 32bit unsigned in host order. func (rr recvReport) dlsr() uint32 { - return binary.BigEndian.Uint32(rr[20:]) + return binary.BigEndian.Uint32(rr[20:]) } -// setDlsr takes a 32 unsigned DLSR value in host order and sets it in network order in RR. +// setDlsr takes a 32 unsigned DLSR value in host order and sets it in network order in RR. func (rr recvReport) setDlsr(dlsr uint32) { - binary.BigEndian.PutUint32(rr[20:], dlsr) + binary.BigEndian.PutUint32(rr[20:], dlsr) } -/* - * Functions to fill/access a SDES structure +/* + * Functions to fill/access a SDES structure */ -// newSdesChunk returns a SDES chunk which is positioned at the current inUse offet and advances inUse to point after sdesChunk. +// newSdesChunk returns a SDES chunk which is positioned at the current inUse offet and advances inUse to point after sdesChunk. func (rp *CtrlPacket) newSdesChunk(length int) (chunk sdesChunk, offset int) { - chunk = rp.toSdesChunk(rp.inUse, length) - rp.inUse += len(chunk) - offset = rp.inUse - return + chunk = rp.toSdesChunk(rp.inUse, length) + rp.inUse += len(chunk) + offset = rp.inUse + return } -// toSdesChunk returns the SDES byte slices inside the RTCP packet buffer as sdesChunktype. -// Use functions for this type to parse and access the report blocks data. +// toSdesChunk returns the SDES byte slices inside the RTCP packet buffer as sdesChunktype. +// Use functions for this type to parse and access the report blocks data. func (rp *CtrlPacket) toSdesChunk(offset, length int) sdesChunk { - if offset > len(rp.buffer) || offset+length > len(rp.buffer) { - return nil - } - return rp.buffer[offset : offset+length] + if offset > len(rp.buffer) || offset+length > len(rp.buffer) { + return nil + } + return rp.buffer[offset : offset+length] } -// ssrc returns the receiver report SSRC as 32bit unsigned in host order. +// ssrc returns the receiver report SSRC as 32bit unsigned in host order. func (sdes sdesChunk) ssrc() uint32 { - return binary.BigEndian.Uint32(sdes[0:]) + return binary.BigEndian.Uint32(sdes[0:]) } -// setSSrc takes a 32 unsigned SSRC in host order and sets it in network order in SDES chunk. +// setSSrc takes a 32 unsigned SSRC in host order and sets it in network order in SDES chunk. func (sdes sdesChunk) setSsrc(ssrc uint32) { - binary.BigEndian.PutUint32(sdes[0:], ssrc) + binary.BigEndian.PutUint32(sdes[0:], ssrc) } -// setItemData takes the item type and the item text and fills it into the chunk. -// The functions returns the offset where to store the next item. +// setItemData takes the item type and the item text and fills it into the chunk. +// The functions returns the offset where to store the next item. func (sdes sdesChunk) setItemData(itemOffset int, itemType byte, text string) int { - sdes[itemOffset] = itemType - sdes[itemOffset+1] = byte(len(text)) - return copy(sdes[itemOffset+2:], text) + 2 + sdes[itemOffset] = itemType + sdes[itemOffset+1] = byte(len(text)) + return copy(sdes[itemOffset+2:], text) + 2 } func (sdes sdesChunk) getItemType(itemOffset int) int { - return int(sdes[itemOffset]) + return int(sdes[itemOffset]) } func (sdes sdesChunk) getItemLen(itemOffset int) int { - return int(sdes[itemOffset+1]) + return int(sdes[itemOffset+1]) } func (sdes sdesChunk) getItemText(itemOffset, length int) string { - if itemOffset+2+length > len(sdes) { - return "" - } - return string(sdes[itemOffset+2 : itemOffset+2+length]) + if itemOffset+2+length > len(sdes) { + return "" + } + return string(sdes[itemOffset+2 : itemOffset+2+length]) } func (sc sdesChunk) chunkLen() (int, bool) { - // length is at least: SSRC plus SdesEnd byte - if 4+1 > len(sc) { - return 0, false - } - length := 4 // include SSRC field of this chunk - itemType := sc[length] - if itemType == SdesEnd { // Cover case if chunk has zero items - if 4+4 > len(sc) { // SSRC (4 byte), SdesEnd (1 byte) plus 3 bytes padding - return 0, false - } - return 8, true - } - // Loop over valid items and add their overall length to offset. - for ; itemType != SdesEnd; itemType = sc[length] { - length += int(sc[length+1]) + 2 // lenght points to next item type field - if length > len(sc) { - return 0, false - } - } - return (length + 4) &^ 0x3, true -} - -// newByePacket returns a BYE data structure which is positioned at the current inUse offet and advances inUse to point after BYE. + // length is at least: SSRC plus SdesEnd byte + if 4+1 > len(sc) { + return 0, false + } + length := 4 // include SSRC field of this chunk + itemType := sc[length] + if itemType == SdesEnd { // Cover case if chunk has zero items + if 4+4 > len(sc) { // SSRC (4 byte), SdesEnd (1 byte) plus 3 bytes padding + return 0, false + } + return 8, true + } + // Loop over valid items and add their overall length to offset. + for ; itemType != SdesEnd; itemType = sc[length] { + length += int(sc[length+1]) + 2 // lenght points to next item type field + if length > len(sc) { + return 0, false + } + } + return (length + 4) &^ 0x3, true +} + +// newByePacket returns a BYE data structure which is positioned at the current inUse offet and advances inUse to point after BYE. func (rp *CtrlPacket) newByeData(length int) (bye byeData, offset int) { - bye = rp.toByeData(rp.inUse, length) - rp.inUse += len(bye) - offset = rp.inUse - return + bye = rp.toByeData(rp.inUse, length) + rp.inUse += len(bye) + offset = rp.inUse + return } -// toByePacket returns the BYE byte slices inside the RTCP packet buffer as byePacket type. -// Use functions for this type to parse and access the BYE data. + +// toByePacket returns the BYE byte slices inside the RTCP packet buffer as byePacket type. +// Use functions for this type to parse and access the BYE data. func (rp *CtrlPacket) toByeData(offset, length int) byeData { - if offset > len(rp.buffer) || offset+length > len(rp.buffer) { - return nil - } - return rp.buffer[offset : offset+length] + if offset > len(rp.buffer) || offset+length > len(rp.buffer) { + return nil + } + return rp.buffer[offset : offset+length] } -// ssrc returns the bye data SSRC at ssrcIdx as 32bit unsigned in host order. +// ssrc returns the bye data SSRC at ssrcIdx as 32bit unsigned in host order. func (bye byeData) ssrc(ssrcIdx int) uint32 { - if (ssrcIdx+1)*4 > len(bye) { - return 0 - } - return binary.BigEndian.Uint32(bye[ssrcIdx*4:]) + if (ssrcIdx+1)*4 > len(bye) { + return 0 + } + return binary.BigEndian.Uint32(bye[ssrcIdx*4:]) } -// setSSrc takes a 32 unsigned SSRC in host order and sets it at ssrcIdx in bye data (network order). +// setSSrc takes a 32 unsigned SSRC in host order and sets it at ssrcIdx in bye data (network order). func (bye byeData) setSsrc(ssrcIdx int, ssrc uint32) { - binary.BigEndian.PutUint32(bye[ssrcIdx*4:], ssrc) + binary.BigEndian.PutUint32(bye[ssrcIdx*4:], ssrc) } -// setReason takes reason text and fills it into the bye data after ssrcCnt SSRC/CSRC entries. -// The functions returns the offset where to store the next item. +// setReason takes reason text and fills it into the bye data after ssrcCnt SSRC/CSRC entries. +// The functions returns the offset where to store the next item. func (bye byeData) setReason(reason string, ssrcCnt int) { - bye[ssrcCnt*4] = byte(len(reason)) - copy(bye[ssrcCnt*4+1:], reason) + bye[ssrcCnt*4] = byte(len(reason)) + copy(bye[ssrcCnt*4+1:], reason) } -// getReason returns the reason string if it is available +// getReason returns the reason string if it is available func (bye byeData) getReason(ssrcCnt int) string { - offset := ssrcCnt * 4 - if offset >= len(bye) { - return "" - } - length := int(bye[offset]) - offset++ - if offset+length > len(bye) { - return "" - } - return string(bye[offset : offset+length]) + offset := ssrcCnt * 4 + if offset >= len(bye) { + return "" + } + length := int(bye[offset]) + offset++ + if offset+length > len(bye) { + return "" + } + return string(bye[offset : offset+length]) } diff --git a/src/net/rtp/payload.go b/src/net/rtp/payload.go index 0806e91..57c258a 100644 --- a/src/net/rtp/payload.go +++ b/src/net/rtp/payload.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -25,7 +25,7 @@ package rtp // PT encoding name audio/video (A/V) clock rate (Hz) channels (audio) Reference // -------- -------------- ----------------- --------------- ---------------- --------- // 0 PCMU A 8000 1 [RFC3551] -// 1 Reserved +// 1 Reserved // 2 Reserved // 3 GSM A 8000 1 [RFC3551] // 4 G723 A 8000 1 [Kumar][RFC3551] @@ -62,79 +62,79 @@ package rtp // 35-71 Unassigned ? // 72-76 Reserved for RTCP conflict avoidance [RFC3551] // 77-95 Unassigned ? -// 96-127 dynamic ? [RFC3551] +// 96-127 dynamic ? [RFC3551] const ( - Audio = 1 - Video = 2 + Audio = 1 + Video = 2 ) // PayloadFormat holds RTP payload formats. // -// The global variable PayloadFormatMap holds the well known payload formats -// (see http://www.iana.org/assignments/rtp-parameters). +// The global variable PayloadFormatMap holds the well known payload formats +// (see http://www.iana.org/assignments/rtp-parameters). // Applications shall not alter these predefined formats. // // If an application needs additional payload formats it must create and populate // PayloadFormat structures and insert them into PayloadFormatMap before setting -// up the RTP communication. The index (key) into the map must be the payload +// up the RTP communication. The index (key) into the map must be the payload // format number. For dynamic payload formats applications shall use payload // format numbers between 96 and 127 only. // -// For example if a dynamic format uses the payload number 98 then the application +// For example if a dynamic format uses the payload number 98 then the application // may perform: // // PayloadFormatMap[98] = &net.rtp.PayloadFormat{98, net.rtp.Audio, 41000, 2, "CD"} // type PayloadFormat struct { - TypeNumber, - MediaType, - ClockRate, - Channels int - Name string + TypeNumber, + MediaType, + ClockRate, + Channels int + Name string } type payloadMap map[int]*PayloadFormat var PayloadFormatMap = make(payloadMap, 25) func init() { - PayloadFormatMap[0] = &PayloadFormat{0, Audio, 8000, 1, "PCMU"} - // 1 Reserved - // 2 Reserved - PayloadFormatMap[3] = &PayloadFormat{3, Audio, 8000, 1, "GSM"} - PayloadFormatMap[4] = &PayloadFormat{4, Audio, 8000, 1, "G723"} - PayloadFormatMap[5] = &PayloadFormat{5, Audio, 8000, 1, "DVI4"} - PayloadFormatMap[6] = &PayloadFormat{6, Audio, 16000, 1, "DVI4"} - PayloadFormatMap[7] = &PayloadFormat{7, Audio, 8000, 1, "LPC"} - PayloadFormatMap[8] = &PayloadFormat{8, Audio, 8000, 1, "PCMA"} - PayloadFormatMap[9] = &PayloadFormat{9, Audio, 8000, 1, "G722"} - PayloadFormatMap[10] = &PayloadFormat{10, Audio, 44100, 2, "L16"} - PayloadFormatMap[11] = &PayloadFormat{11, Audio, 44100, 1, "L16"} - PayloadFormatMap[12] = &PayloadFormat{12, Audio, 8000, 1, "QCELP"} - PayloadFormatMap[13] = &PayloadFormat{13, Audio, 8000, 1, "CN"} - PayloadFormatMap[14] = &PayloadFormat{14, Audio, 90000, 0, "MPA"} - PayloadFormatMap[15] = &PayloadFormat{15, Audio, 8000, 1, "G728"} - PayloadFormatMap[16] = &PayloadFormat{16, Audio, 11025, 1, "DVI4"} - PayloadFormatMap[17] = &PayloadFormat{17, Audio, 22050, 1, "DVI4"} - PayloadFormatMap[18] = &PayloadFormat{18, Audio, 8000, 1, "G729"} - // 19 Reserved A - // 20 Unassigned A - // 21 Unassigned A - // 22 Unassigned A - // 23 Unassigned A - // 24 Unassigned V - PayloadFormatMap[25] = &PayloadFormat{25, Video, 90000, 0, "CelB"} - PayloadFormatMap[26] = &PayloadFormat{26, Video, 90000, 0, "JPEG"} - // 27 Unassigned V - PayloadFormatMap[28] = &PayloadFormat{28, Video, 90000, 0, "nv"} - // 29 Unassigned V - // 30 Unassigned V - PayloadFormatMap[31] = &PayloadFormat{31, Video, 90000, 0, "H261"} - PayloadFormatMap[32] = &PayloadFormat{32, Video, 90000, 0, "MPV"} - PayloadFormatMap[33] = &PayloadFormat{33, Audio | Video, 90000, 0, "MP2T"} - PayloadFormatMap[34] = &PayloadFormat{34, Video, 90000, 0, "H263"} - // 35-71 Unassigned ? - // 72-76 Reserved for RTCP conflict avoidance - // 77-95 Unassigned ? - // 96-127 dynamic ? + PayloadFormatMap[0] = &PayloadFormat{0, Audio, 8000, 1, "PCMU"} + // 1 Reserved + // 2 Reserved + PayloadFormatMap[3] = &PayloadFormat{3, Audio, 8000, 1, "GSM"} + PayloadFormatMap[4] = &PayloadFormat{4, Audio, 8000, 1, "G723"} + PayloadFormatMap[5] = &PayloadFormat{5, Audio, 8000, 1, "DVI4"} + PayloadFormatMap[6] = &PayloadFormat{6, Audio, 16000, 1, "DVI4"} + PayloadFormatMap[7] = &PayloadFormat{7, Audio, 8000, 1, "LPC"} + PayloadFormatMap[8] = &PayloadFormat{8, Audio, 8000, 1, "PCMA"} + PayloadFormatMap[9] = &PayloadFormat{9, Audio, 8000, 1, "G722"} + PayloadFormatMap[10] = &PayloadFormat{10, Audio, 44100, 2, "L16"} + PayloadFormatMap[11] = &PayloadFormat{11, Audio, 44100, 1, "L16"} + PayloadFormatMap[12] = &PayloadFormat{12, Audio, 8000, 1, "QCELP"} + PayloadFormatMap[13] = &PayloadFormat{13, Audio, 8000, 1, "CN"} + PayloadFormatMap[14] = &PayloadFormat{14, Audio, 90000, 0, "MPA"} + PayloadFormatMap[15] = &PayloadFormat{15, Audio, 8000, 1, "G728"} + PayloadFormatMap[16] = &PayloadFormat{16, Audio, 11025, 1, "DVI4"} + PayloadFormatMap[17] = &PayloadFormat{17, Audio, 22050, 1, "DVI4"} + PayloadFormatMap[18] = &PayloadFormat{18, Audio, 8000, 1, "G729"} + // 19 Reserved A + // 20 Unassigned A + // 21 Unassigned A + // 22 Unassigned A + // 23 Unassigned A + // 24 Unassigned V + PayloadFormatMap[25] = &PayloadFormat{25, Video, 90000, 0, "CelB"} + PayloadFormatMap[26] = &PayloadFormat{26, Video, 90000, 0, "JPEG"} + // 27 Unassigned V + PayloadFormatMap[28] = &PayloadFormat{28, Video, 90000, 0, "nv"} + // 29 Unassigned V + // 30 Unassigned V + PayloadFormatMap[31] = &PayloadFormat{31, Video, 90000, 0, "H261"} + PayloadFormatMap[32] = &PayloadFormat{32, Video, 90000, 0, "MPV"} + PayloadFormatMap[33] = &PayloadFormat{33, Audio | Video, 90000, 0, "MP2T"} + PayloadFormatMap[34] = &PayloadFormat{34, Video, 90000, 0, "H263"} + // 35-71 Unassigned ? + // 72-76 Reserved for RTCP conflict avoidance + // 77-95 Unassigned ? + // 96-127 dynamic ? } diff --git a/src/net/rtp/receive_test.go b/src/net/rtp/receive_test.go index d3ab4cd..6f8081b 100644 --- a/src/net/rtp/receive_test.go +++ b/src/net/rtp/receive_test.go @@ -1,11 +1,11 @@ package rtp import ( - "net" - "testing" - "time" - // "encoding/hex" - // "fmt" + "net" + "testing" + "time" + // "encoding/hex" + // "fmt" ) var rsRecv, rsSender *Session @@ -16,450 +16,450 @@ var senderAddr *net.IPAddr var dataReceiver DataReceiveChan func initSessions() { - recvAddr, _ := net.ResolveIPAddr("ip", "127.0.0.1") - senderAddr, _ = net.ResolveIPAddr("ip", "127.0.0.2") - - // Create a UDP transport with "local" address and use this for a "local" RTP session - // Not used in these tests, used to initialize and get a Session - tpRecv, _ := NewTransportUDP(recvAddr, recvPort) - - // TransportUDP implements RtpTransportWrite and RtpTransportRecv interfaces thus - // set it in the RtpSession for both interfaces - rsRecv = NewSession(tpRecv, tpRecv) - - // Create and store the data receive channel. - dataReceiver = rsRecv.CreateDataReceiveChan() - - // Create a media stream. - // The SSRC identifies the stream. Each stream has its own sequence number and other - // context. A RTP session can have several RTP stream for example to send several - // streams of the same media. Need an output stream to test for collisions/loops - // - strIdx, _ := rsRecv.NewSsrcStreamOut(&Address{recvAddr.IP, recvPort, recvPort + 1}, 0x01020304, 0x4711) - rsRecv.SsrcStreamOutForIndex(strIdx).SetSdesItem(SdesCname, "AAAAAA") - rsRecv.SsrcStreamOutForIndex(strIdx).SetPayloadType(0) - rsRecv.rtcpServiceActive = true // to simulate an active RTCP service - - tpSender, _ := NewTransportUDP(senderAddr, senderPort) - rsSender = NewSession(tpSender, tpSender) + recvAddr, _ := net.ResolveIPAddr("ip", "127.0.0.1") + senderAddr, _ = net.ResolveIPAddr("ip", "127.0.0.2") + + // Create a UDP transport with "local" address and use this for a "local" RTP session + // Not used in these tests, used to initialize and get a Session + tpRecv, _ := NewTransportUDP(recvAddr, recvPort) + + // TransportUDP implements RtpTransportWrite and RtpTransportRecv interfaces thus + // set it in the RtpSession for both interfaces + rsRecv = NewSession(tpRecv, tpRecv) + + // Create and store the data receive channel. + dataReceiver = rsRecv.CreateDataReceiveChan() + + // Create a media stream. + // The SSRC identifies the stream. Each stream has its own sequence number and other + // context. A RTP session can have several RTP stream for example to send several + // streams of the same media. Need an output stream to test for collisions/loops + // + strIdx, _ := rsRecv.NewSsrcStreamOut(&Address{recvAddr.IP, recvPort, recvPort + 1}, 0x01020304, 0x4711) + rsRecv.SsrcStreamOutForIndex(strIdx).SetSdesItem(SdesCname, "AAAAAA") + rsRecv.SsrcStreamOutForIndex(strIdx).SetPayloadType(0) + rsRecv.rtcpServiceActive = true // to simulate an active RTCP service + + tpSender, _ := NewTransportUDP(senderAddr, senderPort) + rsSender = NewSession(tpSender, tpSender) } func receivePacket(t *testing.T, num int) { - select { - case rp := <-dataReceiver: // just get a packet - maybe we add some tests later - rp.FreePacket() - default: // no packet - should not happen, report this - t.Errorf("Unexpected case: data receiver channel is empty at %d.\n", num) - } + select { + case rp := <-dataReceiver: // just get a packet - maybe we add some tests later + rp.FreePacket() + default: // no packet - should not happen, report this + t.Errorf("Unexpected case: data receiver channel is empty at %d.\n", num) + } } // Create a RTP "sender" packet, no payload, just SSRC and address pair func newSenderPacket(stamp uint32) (rp *DataPacket) { - rp = rsSender.NewDataPacket(stamp) + rp = rsSender.NewDataPacket(stamp) - // initialize with "sender" address to enable all necessary checks - rp.fromAddr.IpAddr = senderAddr.IP - rp.fromAddr.DataPort = senderPort - rp.fromAddr.CtrlPort = 0 - return + // initialize with "sender" address to enable all necessary checks + rp.fromAddr.IpAddr = senderAddr.IP + rp.fromAddr.DataPort = senderPort + rp.fromAddr.CtrlPort = 0 + return } // The following tests are really white box tests - they check internal variables, manipulate -// internal variables to get the expected results. +// internal variables to get the expected results. func rtpReceive(t *testing.T) { - // ******************** New session setup to have fresh data *************************** - initSessions() - - pay := make([]byte, 160) - // Create a RTP "sender" stream, with defined SSRC, sequence and payload type (PCMU in this case) - // The defined sequence number (maxDropout-1) tests one path of sequence number initialization for - // the input stream. - seqNum := uint16(maxDropout - 1) - strIdx, _ := rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) - strOut := rsSender.SsrcStreamOutForIndex(strIdx) - strOut.SetPayloadType(0) - - // Test the SDES management stuff - strOut.SetSdesItem(SdesCname, "AAAAAA") - strOut.SetSdesItem(SdesEmail, "BBBBBBB") - if strOut.sdesChunkLen != 24 { // Chunk length does not include SDES header (4 bytes) - t.Errorf("SDES chunk length check 1 failed. Expected: 24, got: %d\n", strOut.sdesChunkLen) - return - } - strOut.SetSdesItem(SdesEmail, "BBBBBB") // reset e-mail name, on char less, total length must stay (padding) - if strOut.sdesChunkLen != 24 { - t.Errorf("SDES chunk length check 2 failed. Expected: 24, got: %d\n", strOut.sdesChunkLen) - return - } - rpSender := newSenderPacket(160) - rpSender.SetPayload(pay) - - // Feed into receiver session, then check if packet was processed correctly - rsRecv.OnRecvData(rpSender) - - // Expect one new input stream with SSRC and address of sender packet - idx := rsRecv.streamInIndex - if idx != 1 { - t.Errorf("StreamIn index check failed. Expected: 1, got: %d\n", idx) - return - } - // get the new (default) input stream - strIn := rsRecv.SsrcStreamIn() - ssrc := strIn.Ssrc() - if ssrc != 0x04030201 { - t.Errorf("StreamIn SSRC check failed. Expected: 0x04030201, got: %x\n", ssrc) - return - } - maxSeq := strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("First maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - badSeq := strIn.statistics.badSeqNum - if badSeq != seqNumMod+1 { - t.Errorf("First badSeqNum check failed. Expected: 0x%x, got: 0x%x\n", seqNumMod+1, badSeq) - return - } - receivePacket(t, 0) - - // The 20/15ms sleeps simulate a jitter at the receiver's end. The expected jitter range takes some - // additional delays into account. "go thread" switching introduces additional delays - time.Sleep(20e6) - - rpSender = newSenderPacket(320) - seqNum++ - rsRecv.OnRecvData(rpSender) - receivePacket(t, 1) - time.Sleep(15e6) - - rpSender = newSenderPacket(480) - seqNum++ - rsRecv.OnRecvData(rpSender) - receivePacket(t, 2) - time.Sleep(20e6) - - rpSender = newSenderPacket(640) - seqNum++ - rsRecv.OnRecvData(rpSender) - receivePacket(t, 3) - time.Sleep(15e6) - - rpSender = newSenderPacket(800) - seqNum++ - rsRecv.OnRecvData(rpSender) - receivePacket(t, 4) - time.Sleep(20e6) - - rpSender = newSenderPacket(960) - seqNum++ - rsRecv.OnRecvData(rpSender) - - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Second maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != seqNumMod+1 { - t.Errorf("Second badSeqNum check failed. Expected: 0x%x, got: 0x%x\n", seqNumMod+1, badSeq) - return - } - jitter := strIn.statistics.jitter >> 4 - if jitter <= 0 && jitter > 10 { - t.Errorf("Jitter test failed. Expected jitter range: 0 < jitter < 10, got: %d\n", jitter) - return - } - receivePacket(t, 5) - - // Create a RTCP packet and fill in senderInfo of the output stream - rcTime, offset := strOut.newCtrlPacket(RtcpSR) - rcTime.addHeaderSsrc(offset, strOut.Ssrc()) - - newInfo, _ := rcTime.newSenderInfo() - strOut.fillSenderInfo(newInfo) // create a sender info block after fixed header and SSRC. - - // the above jitter test took 90ms. Thus the difference between session start and now is about 90ms and - // the RTP timestamp should be 720 units for the selected payload (PCMU, 8000Hz). To get this we have - // to subtract the random initial timestamp. - tm := time.Now().UnixNano() - info := rcTime.toSenderInfo(rtcpHeaderLength + rtcpSsrcLength) - stamp := info.rtpTimeStamp() - strOut.initialStamp - - if stamp != 720 { - t.Logf("rtpTimeStamp test out of range - logged only. Expected rtpTimeStamp: 720, got: %d\n", stamp) - } - high, low := info.ntpTimeStamp() - tm1 := fromNtp(high, low) - diff := tm - tm1 - // check if it is in a reasonable range. Take some thread switching into account. tm1 must be smaller than - // tm because tm was taken after makeSenderInfo that computes the timestamp in the senderInfo. - if diff > 30000 { - t.Errorf("NTP time check in senderInfo failed. Expected range: +30000, got: %d\n", diff) - return - } - - // Create a RTCP compound packet that will contain: one RTCP header, one recvReport, one SDES with - // chunk length 28 which gives a compound total of 8 + 24 + 28 = 60 bytes - - // First get a new ctrl packet and initialize it so that we can "send" it to some internal "receiver" methods. - - // Set the receiver as "sender" as well, thus we will have a senderInfo part in the packet as well - rsRecv.streamsOut[0].sender = true - - // build a RTCP packet for the standard output stream - rcSender := rsRecv.buildRtcpPkt(rsRecv.SsrcStreamOut(), 31) - - rcSender.fromAddr.IpAddr = senderAddr.IP - rcSender.fromAddr.DataPort = 0 - rcSender.fromAddr.CtrlPort = senderPort + 1 - - // *** fmt.Printf("1st Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) - rcTotalLength := rcSender.InUse() - if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+senderInfoLen+reportBlockLen+20 { // 20: SDES header plus SDES chunk - t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", - rtcpHeaderLength+rtcpSsrcLength+senderInfoLen+reportBlockLen+20, rcTotalLength) - return - } - if !rsRecv.OnRecvCtrl(rcSender) { - t.Errorf("OnRecvCtrl failed for RTCP packet.\n") - return - } - // Need to perform a lookup here: with the last OnRecvCtrl we have produced a collision. Now the receiver has two - // input streams: one with 0x04030201 and one with 0x01020304. This happened because, for this test, - // we have produced the control packet from the receiver session and fed that packet into the receiver. - // The receiver now has a newly initialized output stream (one only) with new random SSRC and sequence numbers. - - if rsRecv.streamInIndex != 2 { - t.Errorf("Input stream index check failed. Expected: 2, got: %d\n", rsRecv.streamInIndex) - return - } - if rsRecv.streamOutIndex != 1 { - t.Errorf("Output stream index check failed. Expected: 1, got: %d\n", rsRecv.streamOutIndex) - return - } - // lookup and get the new input stream and check if SDES was parsed correctly - inx, _, _ := rsRecv.lookupSsrcMapIn(rcSender.Ssrc(0)) - if inx.SdesItems[SdesCname] != "AAAAAA" { - t.Errorf("SDES chunk parsing failed. Expected: 'AAAAAA', got: %s\n", strIn.SdesItems[SdesCname]) - return - } - - // Now set sender to false, only RR packet plus SDES - rsRecv.streamsOut[0].sender = false - rsRecv.streamsOut[0].streamStatus = active // just to pass the active check during onRecvCtrl() - - rsRecv.streamsIn[0].dataAfterLastReport = true // just to simulate received RTP data to generate correct RR - - rcSender = rsRecv.buildRtcpPkt(rsRecv.SsrcStreamOut(), 31) - - rcSender.fromAddr.IpAddr = senderAddr.IP - rcSender.fromAddr.DataPort = 0 - rcSender.fromAddr.CtrlPort = senderPort + 3 // just to avoid an addtional conflict - but collosion will happen - - // *** fmt.Printf("2nd Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) - rcTotalLength = rcSender.InUse() - - // we have still have 1 receiver report here because the second receiver generated (see test above) never - // sent an RTP packet, thus is not included in RR - if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+reportBlockLen+20 { // 20: SDES header plus SDES chunk - t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", - rtcpHeaderLength+rtcpSsrcLength+reportBlockLen+20, rcTotalLength) - return - } - if !rsRecv.OnRecvCtrl(rcSender) { - t.Errorf("OnRecvCtrl failed for RTCP packet.\n") - return - } - // Need to perform a lookup here: with this test we have produced another collision. Now the receiver has three - // input streams: one with 0x04030201, one with 0x01020304, one with random SSRC (see test above). This - // happened because, for this test, we have produced the control packet from the receiver session and fed that - // packet into the receiver. - // The receiver now again has a newly initialized output stream (one only) with new random SSRC and sequence numbers. - - if rsRecv.streamInIndex != 3 { - t.Errorf("Input stream index check failed. Expected: 3, got: %d\n", rsRecv.streamInIndex) - return - } - if rsRecv.streamOutIndex != 1 { - t.Errorf("Output stream index check failed. Expected: 1, got: %d\n", rsRecv.streamOutIndex) - return - } - // lookup and get the new input stream and check if SDES was parsed correctly - inx, _, _ = rsRecv.lookupSsrcMapIn(rcSender.Ssrc(0)) - if inx.SdesItems[SdesCname] != "AAAAAA" { - t.Errorf("SDES chunk parsing failed. Expected: 'AAAAAA', got: %s\n", strIn.SdesItems[SdesCname]) - return - } - // The receiver has three input streams: one with 0x04030201, one with 0x01020304, one with random - // SSRC (see test above) - the latest one with random SSRC is ommited from receiver reports because - // it was no "active", neither sent or received a packet - - rcSender = rsRecv.buildRtcpByePkt(rsRecv.SsrcStreamOut(), "CCCCCC") - rcTotalLength = rcSender.InUse() - // *** fmt.Printf("3rd Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) - - // BYE packet has empty RR; 20: SDES header plus SDES chunk; 16: BYE RTCP packet - if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+20+16 { - t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", - rtcpHeaderLength+rtcpSsrcLength+20+16, rcTotalLength) - return - } - - // ******************** New session setup to have fresh data *************************** - initSessions() - // Create a RTP "sender" stream, with defined SSRC, sequence and payload type. Define the sequence number to - // check second if-path when initalizing the sequence number for input stream - seqNum = uint16(maxDropout) - strIdx, _ = rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) - strOut = rsSender.SsrcStreamOutForIndex(strIdx) - strOut.SetPayloadType(0) - - rpSender = newSenderPacket(160) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 6) - - strIn = rsRecv.SsrcStreamIn() - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Third maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != uint32(seqNum+1) { - t.Errorf("Third badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) - return - } - - // After receiving a packet with a sequence number "maxDropout" now simulate a large step in the sequence - // number. Expected result: the old sequence nummber (maxSeq) stays, badSeq is the new (higher) sequence plus one - rpSender = newSenderPacket(160) - - // Force a large jump in sequence number which causes the receiver to drop this packet, so don't try to receive it. - seqNum = uint16(maxDropout * 2) - rpSender.SetSequence(seqNum) - rsRecv.OnRecvData(rpSender) - // receivePacket(t, 7) - - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != maxDropout { - t.Errorf("Forth maxSeqNum check failed. Expected: %d, got: %d\n", maxDropout, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != uint32(seqNum+1) { - t.Errorf("Forth badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) - return - } - - // Now send a packet which is in sequence to the first (lower, maxDropout+1) sequence. This simluates - // a lingering RTP packet after the sender switched to new higher sequence numbers. - // Expected result: sequence nummber maxSeq is maxDropout+1, badSeq is the new (higer) sequence plus one - rpSender = newSenderPacket(160) - seqNum = uint16(maxDropout + 1) - rpSender.SetSequence(seqNum) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 8) - - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != maxDropout+1 { - t.Errorf("Fifth maxSeqNum check failed. Expected: %d, got: %d\n", maxDropout+1, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != 2*maxDropout+1 { - t.Errorf("Fifth badSeqNum check failed. Expected: %d, got: %d\n", 2*maxDropout+1, badSeq) - return - } - // Now send a packet which is in sequence with the new higher (maxDropout*2+1) sequence. This simluates - // a RTP packet in sequence after the sender switched to new higher sequence numbers - // Expected result: sequence nummber maxSeq is maxDropout*2+1, badSeq is seqNumMod + 1, a resync happened - rpSender = newSenderPacket(160) - seqNum = uint16(maxDropout*2 + 1) - rpSender.SetSequence(seqNum) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 9) - - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Sixth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != seqNumMod+1 { - t.Errorf("Sixth badSeqNum check failed. Expected: %d, got: %d\n", seqNumMod+1, badSeq) - return - } - - // ******************** New session setup to have fresh data *************************** - initSessions() - // Create a RTP "sender" stream, with defined SSRC, sequence and payload type. Define the sequence number to - // enable checks if sequence number wraps. First use a sequence number near wrap but small enough to go through - // the initial tests - seqNum = uint16(seqNumMod - maxMisorder - 2) - strIdx, _ = rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) - strOut = rsSender.SsrcStreamOutForIndex(strIdx) - strOut.SetPayloadType(0) - - rpSender = newSenderPacket(160) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 10) - - strIn = rsRecv.SsrcStreamIn() - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Seventh maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - badSeq = strIn.statistics.badSeqNum - if badSeq != uint32(seqNum+1) { - t.Errorf("Seventh badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) - return - } - rpSender = newSenderPacket(160) - - // Now step up sequence number near wrapping value (2^16-1), this step is small, thus it is considered "in sequence" - // and badSeqNum will not change from its value above - seqNum = uint16(seqNumMod - 1) - rpSender.SetSequence(seqNum) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 11) - - strIn = rsRecv.SsrcStreamIn() - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Eighth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - rpSender = newSenderPacket(160) - - // Now step up sequence number so it wraps from 2^16-1 to 2^16 (i.e. 0), it is considered "in sequence" and - // the sequence numbers will wrap to 0, the warp-counter (accum) is enhanced by seqNumMod (was zero in this case) - seqNum++ - rpSender.SetSequence(seqNum) - rsRecv.OnRecvData(rpSender) - receivePacket(t, 12) - - strIn = rsRecv.SsrcStreamIn() - maxSeq = strIn.statistics.maxSeqNum - if maxSeq != seqNum { - t.Errorf("Nineth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) - return - } - accu := strIn.statistics.seqNumAccum - if accu != seqNumMod { - t.Errorf("Sequence number wrapping check failed. Expected: %d, got: %d\n", seqNumMod, accu) - return - } - select { - case <-dataReceiver: // Here we have a lingering packet. - t.Errorf("Unexpected packet received after all tests done.\n") - default: // no packet - should not happen, report this - } + // ******************** New session setup to have fresh data *************************** + initSessions() + + pay := make([]byte, 160) + // Create a RTP "sender" stream, with defined SSRC, sequence and payload type (PCMU in this case) + // The defined sequence number (maxDropout-1) tests one path of sequence number initialization for + // the input stream. + seqNum := uint16(maxDropout - 1) + strIdx, _ := rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) + strOut := rsSender.SsrcStreamOutForIndex(strIdx) + strOut.SetPayloadType(0) + + // Test the SDES management stuff + strOut.SetSdesItem(SdesCname, "AAAAAA") + strOut.SetSdesItem(SdesEmail, "BBBBBBB") + if strOut.sdesChunkLen != 24 { // Chunk length does not include SDES header (4 bytes) + t.Errorf("SDES chunk length check 1 failed. Expected: 24, got: %d\n", strOut.sdesChunkLen) + return + } + strOut.SetSdesItem(SdesEmail, "BBBBBB") // reset e-mail name, on char less, total length must stay (padding) + if strOut.sdesChunkLen != 24 { + t.Errorf("SDES chunk length check 2 failed. Expected: 24, got: %d\n", strOut.sdesChunkLen) + return + } + rpSender := newSenderPacket(160) + rpSender.SetPayload(pay) + + // Feed into receiver session, then check if packet was processed correctly + rsRecv.OnRecvData(rpSender) + + // Expect one new input stream with SSRC and address of sender packet + idx := rsRecv.streamInIndex + if idx != 1 { + t.Errorf("StreamIn index check failed. Expected: 1, got: %d\n", idx) + return + } + // get the new (default) input stream + strIn := rsRecv.SsrcStreamIn() + ssrc := strIn.Ssrc() + if ssrc != 0x04030201 { + t.Errorf("StreamIn SSRC check failed. Expected: 0x04030201, got: %x\n", ssrc) + return + } + maxSeq := strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("First maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + badSeq := strIn.statistics.badSeqNum + if badSeq != seqNumMod+1 { + t.Errorf("First badSeqNum check failed. Expected: 0x%x, got: 0x%x\n", seqNumMod+1, badSeq) + return + } + receivePacket(t, 0) + + // The 20/15ms sleeps simulate a jitter at the receiver's end. The expected jitter range takes some + // additional delays into account. "go thread" switching introduces additional delays + time.Sleep(20e6) + + rpSender = newSenderPacket(320) + seqNum++ + rsRecv.OnRecvData(rpSender) + receivePacket(t, 1) + time.Sleep(15e6) + + rpSender = newSenderPacket(480) + seqNum++ + rsRecv.OnRecvData(rpSender) + receivePacket(t, 2) + time.Sleep(20e6) + + rpSender = newSenderPacket(640) + seqNum++ + rsRecv.OnRecvData(rpSender) + receivePacket(t, 3) + time.Sleep(15e6) + + rpSender = newSenderPacket(800) + seqNum++ + rsRecv.OnRecvData(rpSender) + receivePacket(t, 4) + time.Sleep(20e6) + + rpSender = newSenderPacket(960) + seqNum++ + rsRecv.OnRecvData(rpSender) + + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Second maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != seqNumMod+1 { + t.Errorf("Second badSeqNum check failed. Expected: 0x%x, got: 0x%x\n", seqNumMod+1, badSeq) + return + } + jitter := strIn.statistics.jitter >> 4 + if jitter <= 0 && jitter > 10 { + t.Errorf("Jitter test failed. Expected jitter range: 0 < jitter < 10, got: %d\n", jitter) + return + } + receivePacket(t, 5) + + // Create a RTCP packet and fill in senderInfo of the output stream + rcTime, offset := strOut.newCtrlPacket(RtcpSR) + rcTime.addHeaderSsrc(offset, strOut.Ssrc()) + + newInfo, _ := rcTime.newSenderInfo() + strOut.fillSenderInfo(newInfo) // create a sender info block after fixed header and SSRC. + + // the above jitter test took 90ms. Thus the difference between session start and now is about 90ms and + // the RTP timestamp should be 720 units for the selected payload (PCMU, 8000Hz). To get this we have + // to subtract the random initial timestamp. + tm := time.Now().UnixNano() + info := rcTime.toSenderInfo(rtcpHeaderLength + rtcpSsrcLength) + stamp := info.rtpTimeStamp() - strOut.initialStamp + + if stamp != 720 { + t.Logf("rtpTimeStamp test out of range - logged only. Expected rtpTimeStamp: 720, got: %d\n", stamp) + } + high, low := info.ntpTimeStamp() + tm1 := fromNtp(high, low) + diff := tm - tm1 + // check if it is in a reasonable range. Take some thread switching into account. tm1 must be smaller than + // tm because tm was taken after makeSenderInfo that computes the timestamp in the senderInfo. + if diff > 30000 { + t.Errorf("NTP time check in senderInfo failed. Expected range: +30000, got: %d\n", diff) + return + } + + // Create a RTCP compound packet that will contain: one RTCP header, one recvReport, one SDES with + // chunk length 28 which gives a compound total of 8 + 24 + 28 = 60 bytes + + // First get a new ctrl packet and initialize it so that we can "send" it to some internal "receiver" methods. + + // Set the receiver as "sender" as well, thus we will have a senderInfo part in the packet as well + rsRecv.streamsOut[0].sender = true + + // build a RTCP packet for the standard output stream + rcSender := rsRecv.buildRtcpPkt(rsRecv.SsrcStreamOut(), 31) + + rcSender.fromAddr.IpAddr = senderAddr.IP + rcSender.fromAddr.DataPort = 0 + rcSender.fromAddr.CtrlPort = senderPort + 1 + + // *** fmt.Printf("1st Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) + rcTotalLength := rcSender.InUse() + if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+senderInfoLen+reportBlockLen+20 { // 20: SDES header plus SDES chunk + t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", + rtcpHeaderLength+rtcpSsrcLength+senderInfoLen+reportBlockLen+20, rcTotalLength) + return + } + if !rsRecv.OnRecvCtrl(rcSender) { + t.Errorf("OnRecvCtrl failed for RTCP packet.\n") + return + } + // Need to perform a lookup here: with the last OnRecvCtrl we have produced a collision. Now the receiver has two + // input streams: one with 0x04030201 and one with 0x01020304. This happened because, for this test, + // we have produced the control packet from the receiver session and fed that packet into the receiver. + // The receiver now has a newly initialized output stream (one only) with new random SSRC and sequence numbers. + + if rsRecv.streamInIndex != 2 { + t.Errorf("Input stream index check failed. Expected: 2, got: %d\n", rsRecv.streamInIndex) + return + } + if rsRecv.streamOutIndex != 1 { + t.Errorf("Output stream index check failed. Expected: 1, got: %d\n", rsRecv.streamOutIndex) + return + } + // lookup and get the new input stream and check if SDES was parsed correctly + inx, _, _ := rsRecv.lookupSsrcMapIn(rcSender.Ssrc(0)) + if inx.SdesItems[SdesCname] != "AAAAAA" { + t.Errorf("SDES chunk parsing failed. Expected: 'AAAAAA', got: %s\n", strIn.SdesItems[SdesCname]) + return + } + + // Now set sender to false, only RR packet plus SDES + rsRecv.streamsOut[0].sender = false + rsRecv.streamsOut[0].streamStatus = active // just to pass the active check during onRecvCtrl() + + rsRecv.streamsIn[0].dataAfterLastReport = true // just to simulate received RTP data to generate correct RR + + rcSender = rsRecv.buildRtcpPkt(rsRecv.SsrcStreamOut(), 31) + + rcSender.fromAddr.IpAddr = senderAddr.IP + rcSender.fromAddr.DataPort = 0 + rcSender.fromAddr.CtrlPort = senderPort + 3 // just to avoid an addtional conflict - but collosion will happen + + // *** fmt.Printf("2nd Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) + rcTotalLength = rcSender.InUse() + + // we have still have 1 receiver report here because the second receiver generated (see test above) never + // sent an RTP packet, thus is not included in RR + if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+reportBlockLen+20 { // 20: SDES header plus SDES chunk + t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", + rtcpHeaderLength+rtcpSsrcLength+reportBlockLen+20, rcTotalLength) + return + } + if !rsRecv.OnRecvCtrl(rcSender) { + t.Errorf("OnRecvCtrl failed for RTCP packet.\n") + return + } + // Need to perform a lookup here: with this test we have produced another collision. Now the receiver has three + // input streams: one with 0x04030201, one with 0x01020304, one with random SSRC (see test above). This + // happened because, for this test, we have produced the control packet from the receiver session and fed that + // packet into the receiver. + // The receiver now again has a newly initialized output stream (one only) with new random SSRC and sequence numbers. + + if rsRecv.streamInIndex != 3 { + t.Errorf("Input stream index check failed. Expected: 3, got: %d\n", rsRecv.streamInIndex) + return + } + if rsRecv.streamOutIndex != 1 { + t.Errorf("Output stream index check failed. Expected: 1, got: %d\n", rsRecv.streamOutIndex) + return + } + // lookup and get the new input stream and check if SDES was parsed correctly + inx, _, _ = rsRecv.lookupSsrcMapIn(rcSender.Ssrc(0)) + if inx.SdesItems[SdesCname] != "AAAAAA" { + t.Errorf("SDES chunk parsing failed. Expected: 'AAAAAA', got: %s\n", strIn.SdesItems[SdesCname]) + return + } + // The receiver has three input streams: one with 0x04030201, one with 0x01020304, one with random + // SSRC (see test above) - the latest one with random SSRC is ommited from receiver reports because + // it was no "active", neither sent or received a packet + + rcSender = rsRecv.buildRtcpByePkt(rsRecv.SsrcStreamOut(), "CCCCCC") + rcTotalLength = rcSender.InUse() + // *** fmt.Printf("3rd Ctrl buffer: %s\n", hex.EncodeToString(rcSender.buffer[:rcSender.InUse()])) + + // BYE packet has empty RR; 20: SDES header plus SDES chunk; 16: BYE RTCP packet + if rcTotalLength != rtcpHeaderLength+rtcpSsrcLength+20+16 { + t.Errorf("rcSender packet length check failed. Expected: %d, got: %d\n", + rtcpHeaderLength+rtcpSsrcLength+20+16, rcTotalLength) + return + } + + // ******************** New session setup to have fresh data *************************** + initSessions() + // Create a RTP "sender" stream, with defined SSRC, sequence and payload type. Define the sequence number to + // check second if-path when initalizing the sequence number for input stream + seqNum = uint16(maxDropout) + strIdx, _ = rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) + strOut = rsSender.SsrcStreamOutForIndex(strIdx) + strOut.SetPayloadType(0) + + rpSender = newSenderPacket(160) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 6) + + strIn = rsRecv.SsrcStreamIn() + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Third maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != uint32(seqNum+1) { + t.Errorf("Third badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) + return + } + + // After receiving a packet with a sequence number "maxDropout" now simulate a large step in the sequence + // number. Expected result: the old sequence nummber (maxSeq) stays, badSeq is the new (higher) sequence plus one + rpSender = newSenderPacket(160) + + // Force a large jump in sequence number which causes the receiver to drop this packet, so don't try to receive it. + seqNum = uint16(maxDropout * 2) + rpSender.SetSequence(seqNum) + rsRecv.OnRecvData(rpSender) + // receivePacket(t, 7) + + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != maxDropout { + t.Errorf("Forth maxSeqNum check failed. Expected: %d, got: %d\n", maxDropout, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != uint32(seqNum+1) { + t.Errorf("Forth badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) + return + } + + // Now send a packet which is in sequence to the first (lower, maxDropout+1) sequence. This simluates + // a lingering RTP packet after the sender switched to new higher sequence numbers. + // Expected result: sequence nummber maxSeq is maxDropout+1, badSeq is the new (higer) sequence plus one + rpSender = newSenderPacket(160) + seqNum = uint16(maxDropout + 1) + rpSender.SetSequence(seqNum) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 8) + + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != maxDropout+1 { + t.Errorf("Fifth maxSeqNum check failed. Expected: %d, got: %d\n", maxDropout+1, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != 2*maxDropout+1 { + t.Errorf("Fifth badSeqNum check failed. Expected: %d, got: %d\n", 2*maxDropout+1, badSeq) + return + } + // Now send a packet which is in sequence with the new higher (maxDropout*2+1) sequence. This simluates + // a RTP packet in sequence after the sender switched to new higher sequence numbers + // Expected result: sequence nummber maxSeq is maxDropout*2+1, badSeq is seqNumMod + 1, a resync happened + rpSender = newSenderPacket(160) + seqNum = uint16(maxDropout*2 + 1) + rpSender.SetSequence(seqNum) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 9) + + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Sixth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != seqNumMod+1 { + t.Errorf("Sixth badSeqNum check failed. Expected: %d, got: %d\n", seqNumMod+1, badSeq) + return + } + + // ******************** New session setup to have fresh data *************************** + initSessions() + // Create a RTP "sender" stream, with defined SSRC, sequence and payload type. Define the sequence number to + // enable checks if sequence number wraps. First use a sequence number near wrap but small enough to go through + // the initial tests + seqNum = uint16(seqNumMod - maxMisorder - 2) + strIdx, _ = rsSender.NewSsrcStreamOut(&Address{senderAddr.IP, senderPort, senderPort + 1}, 0x04030201, seqNum) + strOut = rsSender.SsrcStreamOutForIndex(strIdx) + strOut.SetPayloadType(0) + + rpSender = newSenderPacket(160) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 10) + + strIn = rsRecv.SsrcStreamIn() + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Seventh maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + badSeq = strIn.statistics.badSeqNum + if badSeq != uint32(seqNum+1) { + t.Errorf("Seventh badSeqNum check failed. Expected: %d, got: %d\n", seqNum+1, badSeq) + return + } + rpSender = newSenderPacket(160) + + // Now step up sequence number near wrapping value (2^16-1), this step is small, thus it is considered "in sequence" + // and badSeqNum will not change from its value above + seqNum = uint16(seqNumMod - 1) + rpSender.SetSequence(seqNum) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 11) + + strIn = rsRecv.SsrcStreamIn() + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Eighth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + rpSender = newSenderPacket(160) + + // Now step up sequence number so it wraps from 2^16-1 to 2^16 (i.e. 0), it is considered "in sequence" and + // the sequence numbers will wrap to 0, the warp-counter (accum) is enhanced by seqNumMod (was zero in this case) + seqNum++ + rpSender.SetSequence(seqNum) + rsRecv.OnRecvData(rpSender) + receivePacket(t, 12) + + strIn = rsRecv.SsrcStreamIn() + maxSeq = strIn.statistics.maxSeqNum + if maxSeq != seqNum { + t.Errorf("Nineth maxSeqNum check failed. Expected: %d, got: %d\n", seqNum, maxSeq) + return + } + accu := strIn.statistics.seqNumAccum + if accu != seqNumMod { + t.Errorf("Sequence number wrapping check failed. Expected: %d, got: %d\n", seqNumMod, accu) + return + } + select { + case <-dataReceiver: // Here we have a lingering packet. + t.Errorf("Unexpected packet received after all tests done.\n") + default: // no packet - should not happen, report this + } } func TestReceive(t *testing.T) { - parseFlags() - rtpReceive(t) + parseFlags() + rtpReceive(t) } diff --git a/src/net/rtp/session.go b/src/net/rtp/session.go index 8deabc7..b3c895c 100644 --- a/src/net/rtp/session.go +++ b/src/net/rtp/session.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -23,62 +23,62 @@ package rtp */ import ( - "net" - "sync" - "time" + "net" + "sync" + "time" ) // Session contols and manages the resources and actions of a RTP session. // type Session struct { - RtcpTransmission // Data structure to control and manage RTCP reports. - MaxNumberOutStreams int // Applications may set this to increase the number of supported output streams - MaxNumberInStreams int // Applications may set this to increase the number of supported input streams - - dataReceiveChan DataReceiveChan - ctrlEventChan CtrlEventChan - - streamsMapMutex sync.Mutex // synchronize activities on stream maps - streamsOut streamOutMap - streamsIn streamInMap - remotes remoteMap - conflicts conflictMap - - activeSenders, - streamOutIndex, - streamInIndex, - remoteIndex, - conflictIndex uint32 - - weSent bool // is true if an output stream sent some RTP data - rtcpServiceActive bool // true if an input stream received RTP packets after last RR - rtcpCtrlChan rtcpCtrlChan - transportEnd TransportEnd - transportEndUpper TransportEnd - transportWrite TransportWrite - transportRecv TransportRecv + RtcpTransmission // Data structure to control and manage RTCP reports. + MaxNumberOutStreams int // Applications may set this to increase the number of supported output streams + MaxNumberInStreams int // Applications may set this to increase the number of supported input streams + + dataReceiveChan DataReceiveChan + ctrlEventChan CtrlEventChan + + streamsMapMutex sync.Mutex // synchronize activities on stream maps + streamsOut streamOutMap + streamsIn streamInMap + remotes remoteMap + conflicts conflictMap + + activeSenders, + streamOutIndex, + streamInIndex, + remoteIndex, + conflictIndex uint32 + + weSent bool // is true if an output stream sent some RTP data + rtcpServiceActive bool // true if an input stream received RTP packets after last RR + rtcpCtrlChan rtcpCtrlChan + transportEnd TransportEnd + transportEndUpper TransportEnd + transportWrite TransportWrite + transportRecv TransportRecv } // Remote stores a remote addess in a transport independent way. // // The transport implementations construct UDP or TCP addresses and use them to send the data. type Address struct { - IpAddr net.IP - DataPort, CtrlPort int + IpAddr net.IP + DataPort, CtrlPort int } // The RTP stack sends CtrlEvent to the application if it creates a new input stream or receives RTCP packets. // -// A RTCP compound may contain several RTCP packets. The RTP stack creates a CtrlEvent structure for each RTCP -// packet (SDES, BYE, etc) or report and stores them in a slice of CtrlEvent pointers and sends +// A RTCP compound may contain several RTCP packets. The RTP stack creates a CtrlEvent structure for each RTCP +// packet (SDES, BYE, etc) or report and stores them in a slice of CtrlEvent pointers and sends // this slice to the application after all RTCP packets and reports are processed. The application may now loop // over the slice and select the events that it may process. -// +// type CtrlEvent struct { - EventType int // Either a Stream event or a Rtcp* packet type event, e.g. RtcpSR, RtcpRR, RtcpSdes, RtcpBye - Ssrc uint32 // the input stream's SSRC - Index uint32 // and its index - Reason string // Resaon string if it was available, empty otherwise + EventType int // Either a Stream event or a Rtcp* packet type event, e.g. RtcpSR, RtcpRR, RtcpSdes, RtcpBye + Ssrc uint32 // the input stream's SSRC + Index uint32 // and its index + Reason string // Resaon string if it was available, empty otherwise } // Use a channel to signal if the transports are really closed. @@ -92,40 +92,40 @@ type CtrlEventChan chan []*CtrlEvent // RTCP values to manage RTCP transmission intervals type RtcpTransmission struct { - tprev, // the last time an RTCP packet was transmitted - tnext int64 // next scheduled transmission time - RtcpSessionBandwidth float64 // Applications may (should) set this to bits/sec for RTCP traffic. - // If not set RTP stack makes an educated guess. - avrgPacketLength float64 + tprev, // the last time an RTCP packet was transmitted + tnext int64 // next scheduled transmission time + RtcpSessionBandwidth float64 // Applications may (should) set this to bits/sec for RTCP traffic. + // If not set RTP stack makes an educated guess. + avrgPacketLength float64 } // Returned in case of an error. type Error string func (s Error) Error() string { - return string(s) + return string(s) } // Specific control event type that signal that a new input stream was created. -// +// // If the RTP stack receives a data or control packet for a yet unknown input stream // (SSRC not known) the stack creates a new input stream and signals this action to the application. const ( - NewStreamData = iota // Input stream creation triggered by a RTP data packet - NewStreamCtrl // Input stream creation triggered by a RTCP control packet - MaxNumInStreamReachedData // Maximum number of input streams reached while receiving an RTP packet - MaxNumInStreamReachedCtrl // Maximum number of input streams reached while receiving an RTCP packet - WrongStreamStatusData // Received RTP packet for an inactive stream - WrongStreamStatusCtrl // Received RTCP packet for an inactive stream - StreamCollisionLoopData // Detected a collision or loop processing an RTP packet - StreamCollisionLoopCtrl // Detected a collision or loop processing an RTCP packet + NewStreamData = iota // Input stream creation triggered by a RTP data packet + NewStreamCtrl // Input stream creation triggered by a RTCP control packet + MaxNumInStreamReachedData // Maximum number of input streams reached while receiving an RTP packet + MaxNumInStreamReachedCtrl // Maximum number of input streams reached while receiving an RTCP packet + WrongStreamStatusData // Received RTP packet for an inactive stream + WrongStreamStatusCtrl // Received RTCP packet for an inactive stream + StreamCollisionLoopData // Detected a collision or loop processing an RTP packet + StreamCollisionLoopCtrl // Detected a collision or loop processing an RTCP packet ) // The receiver transports return these vaules via the TransportEnd channel when they are -// done stopping the data or control receivers. +// done stopping the data or control receivers. const ( - DataTransportRecvStopped = 0x1 - CtrlTransportRecvStopped = 0x2 + DataTransportRecvStopped = 0x1 + CtrlTransportRecvStopped = 0x2 ) // Global Session functions. @@ -137,27 +137,27 @@ const ( // tpr - a transport that implements the RtpTransportRecv interface // func NewSession(tpw TransportWrite, tpr TransportRecv) *Session { - rs := new(Session) + rs := new(Session) - // Maps grow dynamically, set size to avoid resizing in normal cases. - rs.streamsOut = make(streamOutMap, maxNumberOutStreams) - rs.streamsIn = make(streamInMap, maxNumberInStreams) - rs.remotes = make(remoteMap, 2) - rs.conflicts = make(conflictMap, 2) + // Maps grow dynamically, set size to avoid resizing in normal cases. + rs.streamsOut = make(streamOutMap, maxNumberOutStreams) + rs.streamsIn = make(streamInMap, maxNumberInStreams) + rs.remotes = make(remoteMap, 2) + rs.conflicts = make(conflictMap, 2) - rs.MaxNumberOutStreams = maxNumberOutStreams - rs.MaxNumberInStreams = maxNumberInStreams + rs.MaxNumberOutStreams = maxNumberOutStreams + rs.MaxNumberInStreams = maxNumberInStreams - rs.transportWrite = tpw - rs.transportRecv = tpr + rs.transportWrite = tpw + rs.transportRecv = tpr - rs.transportEnd = make(TransportEnd, 2) - rs.rtcpCtrlChan = make(rtcpCtrlChan, 1) + rs.transportEnd = make(TransportEnd, 2) + rs.rtcpCtrlChan = make(rtcpCtrlChan, 1) - tpr.SetCallUpper(rs) - tpr.SetEndChannel(rs.transportEnd) + tpr.SetCallUpper(rs) + tpr.SetEndChannel(rs.transportEnd) - return rs + return rs } // AddRemote adds the address and RTP port number of an additional remote peer. @@ -168,19 +168,19 @@ func NewSession(tpw TransportWrite, tpr TransportRecv) *Session { // remote - the RTP address of the remote peer. The RTP data port number must be even. // func (rs *Session) AddRemote(remote *Address) (index uint32, err error) { - if (remote.DataPort & 0x1) == 0x1 { - return 0, Error("RTP data port number is not an even number.") - } - rs.remotes[rs.remoteIndex] = remote - index = rs.remoteIndex - rs.remoteIndex++ - return + if (remote.DataPort & 0x1) == 0x1 { + return 0, Error("RTP data port number is not an even number.") + } + rs.remotes[rs.remoteIndex] = remote + index = rs.remoteIndex + rs.remoteIndex++ + return } // RemoveRemote removes the address at the specified index. // func (rs *Session) RemoveRemote(index uint32) { - delete(rs.remotes, index) + delete(rs.remotes, index) } // NewOutputStream creates a new RTP output stream and returns its index. @@ -193,32 +193,32 @@ func (rs *Session) RemoveRemote(index uint32) { // (up to 2^64 streams per session :-) ) // // own - Output stream's own address. Required to detect collisions and loops. -// ssrc - If not zero then this is the SSRC of the output stream. If zero then +// ssrc - If not zero then this is the SSRC of the output stream. If zero then // the method generates a random SSRC according to RFC 3550. -// sequenceNo - If not zero then this is the starting sequence number of the output stream. -// If zero then the method generates a random starting sequence number according +// sequenceNo - If not zero then this is the starting sequence number of the output stream. +// If zero then the method generates a random starting sequence number according // to RFC 3550 // func (rs *Session) NewSsrcStreamOut(own *Address, ssrc uint32, sequenceNo uint16) (index uint32, err Error) { - if len(rs.streamsOut) > rs.MaxNumberOutStreams { - return 0, Error("Maximum number of output streams reached.") - } - str := newSsrcStreamOut(own, ssrc, sequenceNo) - str.streamStatus = active - - // Synchronize - may be called from several Go application functions in parallel - rs.streamsMapMutex.Lock() - defer rs.streamsMapMutex.Unlock() - - // Don't reuse an existing SSRC - for _, _, exists := rs.lookupSsrcMap(str.Ssrc()); exists; _, _, exists = rs.lookupSsrcMap(str.Ssrc()) { - str.newSsrc() - } - rs.streamsOut[rs.streamOutIndex] = str - index = rs.streamOutIndex - rs.streamOutIndex++ - return + if len(rs.streamsOut) > rs.MaxNumberOutStreams { + return 0, Error("Maximum number of output streams reached.") + } + str := newSsrcStreamOut(own, ssrc, sequenceNo) + str.streamStatus = active + + // Synchronize - may be called from several Go application functions in parallel + rs.streamsMapMutex.Lock() + defer rs.streamsMapMutex.Unlock() + + // Don't reuse an existing SSRC + for _, _, exists := rs.lookupSsrcMap(str.Ssrc()); exists; _, _, exists = rs.lookupSsrcMap(str.Ssrc()) { + str.newSsrc() + } + rs.streamsOut[rs.streamOutIndex] = str + index = rs.streamOutIndex + rs.streamOutIndex++ + return } // StartSession activates the transports and starts the RTCP service. @@ -228,45 +228,45 @@ func (rs *Session) NewSsrcStreamOut(own *Address, ssrc uint32, sequenceNo uint16 // reports to it's remote peers. // func (rs *Session) StartSession() (err error) { - err = rs.ListenOnTransports() // activate the transports - if err != nil { - return - } - // compute first transmission interval - if rs.RtcpSessionBandwidth == 0.0 { // If not set by application try to guess a value - for _, str := range rs.streamsOut { - format := PayloadFormatMap[int(str.PayloadType())] - if format == nil { - rs.RtcpSessionBandwidth += 64000. / 20.0 // some standard: 5% of a 64000 bit connection - } - // Assumption: fixed codec used, 8 byte per sample, one channel - rs.RtcpSessionBandwidth += float64(format.ClockRate) * 8.0 / 20. - } - } - rs.avrgPacketLength = float64(len(rs.streamsOut)*senderInfoLen + reportBlockLen + 20) // 28 for SDES - - // initial call: members, senders, RTCP bandwidth, packet length, weSent, initial - ti, td := rtcpInterval(1, 0, rs.RtcpSessionBandwidth, rs.avrgPacketLength, false, true) - rs.tnext = ti + time.Now().UnixNano() - - go rs.rtcpService(ti, td) - return + err = rs.ListenOnTransports() // activate the transports + if err != nil { + return + } + // compute first transmission interval + if rs.RtcpSessionBandwidth == 0.0 { // If not set by application try to guess a value + for _, str := range rs.streamsOut { + format := PayloadFormatMap[int(str.PayloadType())] + if format == nil { + rs.RtcpSessionBandwidth += 64000. / 20.0 // some standard: 5% of a 64000 bit connection + } + // Assumption: fixed codec used, 8 byte per sample, one channel + rs.RtcpSessionBandwidth += float64(format.ClockRate) * 8.0 / 20. + } + } + rs.avrgPacketLength = float64(len(rs.streamsOut)*senderInfoLen + reportBlockLen + 20) // 28 for SDES + + // initial call: members, senders, RTCP bandwidth, packet length, weSent, initial + ti, td := rtcpInterval(1, 0, rs.RtcpSessionBandwidth, rs.avrgPacketLength, false, true) + rs.tnext = ti + time.Now().UnixNano() + + go rs.rtcpService(ti, td) + return } // CloseSession closes the complete RTP session immediately. // -// The methods stops the RTCP service, sends a BYE to all remaining active output streams, and +// The methods stops the RTCP service, sends a BYE to all remaining active output streams, and // closes the receiver transports, // func (rs *Session) CloseSession() { - if rs.rtcpServiceActive { - rs.rtcpCtrlChan <- rtcpStopService - for idx := range rs.streamsOut { - rs.SsrcStreamCloseForIndex(idx) - } - rs.CloseRecv() // de-activate the transports - } - return + if rs.rtcpServiceActive { + rs.rtcpCtrlChan <- rtcpStopService + for idx := range rs.streamsOut { + rs.SsrcStreamCloseForIndex(idx) + } + rs.CloseRecv() // de-activate the transports + } + return } // NewDataPacket creates a new RTP packet suitable for use with the standard output stream. @@ -275,18 +275,18 @@ func (rs *Session) CloseSession() { // number, the updated timestamp, and payload type if payload type was set in the stream. // // The application computes the next stamp based on the payload's frequency. The stamp usually -// advances by the number of samples contained in the RTP packet. +// advances by the number of samples contained in the RTP packet. // // For example PCMU with a 8000Hz frequency sends 160 samples every 20m - thus the timestamp -// must adavance by 160 for each following packet. For fixed codecs, for example PCMU, the +// must adavance by 160 for each following packet. For fixed codecs, for example PCMU, the // number of samples correspond to the payload length. For variable codecs the number of samples // has no direct relationship with the payload length. // -// stamp - the RTP timestamp for this packet. +// stamp - the RTP timestamp for this packet. // func (rs *Session) NewDataPacket(stamp uint32) *DataPacket { - str := rs.streamsOut[0] - return str.newDataPacket(stamp) + str := rs.streamsOut[0] + return str.newDataPacket(stamp) } // NewDataPacketForStream creates a new RTP packet suitable for use with the specified output stream. @@ -299,8 +299,8 @@ func (rs *Session) NewDataPacket(stamp uint32) *DataPacket { // stamp - the RTP timestamp for this packet. // func (rs *Session) NewDataPacketForStream(streamIndex uint32, stamp uint32) *DataPacket { - str := rs.streamsOut[streamIndex] - return str.newDataPacket(stamp) + str := rs.streamsOut[streamIndex] + return str.newDataPacket(stamp) } // CreateDataReceivedChan creates the data received channel and returns it to the caller. @@ -309,8 +309,8 @@ func (rs *Session) NewDataPacketForStream(streamIndex uint32, stamp uint32) *Dat // If the channel is full then the RTP receiver discards the data packets. // func (rs *Session) CreateDataReceiveChan() DataReceiveChan { - rs.dataReceiveChan = make(DataReceiveChan, dataReceiveChanLen) - return rs.dataReceiveChan + rs.dataReceiveChan = make(DataReceiveChan, dataReceiveChanLen) + return rs.dataReceiveChan } // RemoveDataReceivedChan deletes the data received channel. @@ -318,7 +318,7 @@ func (rs *Session) CreateDataReceiveChan() DataReceiveChan { // The receiver discards all received packets. // func (rs *Session) RemoveDataReceiveChan() { - rs.dataReceiveChan = nil + rs.dataReceiveChan = nil } // CreateCtrlEventChan creates the control event channel and returns it to the caller. @@ -327,20 +327,20 @@ func (rs *Session) RemoveDataReceiveChan() { // If the channel is full then the RTCP receiver does not send control events. // func (rs *Session) CreateCtrlEventChan() CtrlEventChan { - rs.ctrlEventChan = make(CtrlEventChan, ctrlEventChanLen) - return rs.ctrlEventChan + rs.ctrlEventChan = make(CtrlEventChan, ctrlEventChanLen) + return rs.ctrlEventChan } // RemoveCtrlEventChan deletes the control event channel. // func (rs *Session) RemoveCtrlEventChan() { - rs.ctrlEventChan = nil + rs.ctrlEventChan = nil } // SsrcStreamOut gets the standard output stream. // func (rs *Session) SsrcStreamOut() *SsrcStream { - return rs.streamsOut[0] + return rs.streamsOut[0] } // SsrcStreamOut gets the output stream at streamIndex. @@ -348,13 +348,13 @@ func (rs *Session) SsrcStreamOut() *SsrcStream { // streamindex - the index of the output stream as returned by NewSsrcStreamOut // func (rs *Session) SsrcStreamOutForIndex(streamIndex uint32) *SsrcStream { - return rs.streamsOut[streamIndex] + return rs.streamsOut[streamIndex] } // SsrcStreamIn gets the standard input stream. // func (rs *Session) SsrcStreamIn() *SsrcStream { - return rs.streamsIn[0] + return rs.streamsIn[0] } // SsrcStreamInForIndex Get the input stream with index. @@ -362,7 +362,7 @@ func (rs *Session) SsrcStreamIn() *SsrcStream { // streamindex - the index of the output stream as returned by NewSsrcStreamOut // func (rs *Session) SsrcStreamInForIndex(streamIndex uint32) *SsrcStream { - return rs.streamsIn[streamIndex] + return rs.streamsIn[streamIndex] } // SsrcStreamClose sends a RTCP BYE to the standard output stream (index 0). @@ -371,9 +371,9 @@ func (rs *Session) SsrcStreamInForIndex(streamIndex uint32) *SsrcStream { // In this state the stream stops its activities, does not send any new data or // control packets. Eventually it will be in the state "is closed" and its resources // are returned to the system. An application must not re-use a session. -// +// func (rs *Session) SsrcStreamClose() { - rs.SsrcStreamOutForIndex(0) + rs.SsrcStreamOutForIndex(0) } // SsrcStreamCloseForIndex sends a RTCP BYE to the stream at index index. @@ -383,13 +383,13 @@ func (rs *Session) SsrcStreamClose() { // streamindex - the index of the output stream as returned by NewSsrcStreamOut // func (rs *Session) SsrcStreamCloseForIndex(streamIndex uint32) { - if rs.rtcpServiceActive { - str := rs.streamsOut[streamIndex] - rc := rs.buildRtcpByePkt(str, "Go RTP says good-bye") - rs.WriteCtrl(rc) + if rs.rtcpServiceActive { + str := rs.streamsOut[streamIndex] + rc := rs.buildRtcpByePkt(str, "Go RTP says good-bye") + rs.WriteCtrl(rc) - str.streamStatus = isClosing - } + str.streamStatus = isClosing + } } /* @@ -413,7 +413,7 @@ func (rs *Session) SetCallUpper(upper TransportRecv) { // Only relevant if an application uses "simple RTP". // func (rs *Session) ListenOnTransports() (err error) { - return rs.transportRecv.ListenOnTransports() + return rs.transportRecv.ListenOnTransports() } // OnRecvData implements the rtp.TransportRecv OnRecvData method. @@ -425,67 +425,67 @@ func (rs *Session) ListenOnTransports() (err error) { // func (rs *Session) OnRecvData(rp *DataPacket) bool { - if !rp.IsValid() { - rp.FreePacket() - return false - } - // Check here if SRTP is enabled for the SSRC of the packet - a stream attribute - - if rs.rtcpServiceActive { - ssrc := rp.Ssrc() - - now := time.Now().UnixNano() - - rs.streamsMapMutex.Lock() - str, _, existing := rs.lookupSsrcMap(ssrc) - - // if not found in the input stream then create a new SSRC input stream - if !existing { - str = newSsrcStreamIn(&rp.fromAddr, ssrc) - if len(rs.streamsIn) > rs.MaxNumberInStreams { - rs.sendDataCtrlEvent(MaxNumInStreamReachedData, ssrc, 0) - rp.FreePacket() - rs.streamsMapMutex.Unlock() - return false - } - rs.streamsIn[rs.streamInIndex] = str - rs.streamInIndex++ - str.streamStatus = active - str.statistics.initialDataTime = now // First packet arrival time. - rs.sendDataCtrlEvent(NewStreamData, ssrc, rs.streamInIndex-1) - } else { - // Check if an existing stream is active - if str.streamStatus != active { - rs.sendDataCtrlEvent(WrongStreamStatusData, ssrc, rs.streamInIndex-1) - rp.FreePacket() - rs.streamsMapMutex.Unlock() - return false - - } - // Test if RTCP packets had been received but this is the first data packet from this source. - if str.DataPort == 0 { - str.DataPort = rp.fromAddr.DataPort - } - } - rs.streamsMapMutex.Unlock() - - // Before forwarding packet to next upper layer (application) for further processing: - // 1) check for collisions and loops. If the packet cannot be assigned to a source, it will be rejected. - // 2) check the source is a sufficiently well known source - // TODO: also check CSRC identifiers. - if !str.checkSsrcIncomingData(existing, rs, rp) || !str.recordReceptionData(rp, rs, now) { - // must be discarded due to collision or loop or invalid source - rs.sendDataCtrlEvent(StreamCollisionLoopData, ssrc, rs.streamInIndex-1) - rp.FreePacket() - return false - } - } - select { - case rs.dataReceiveChan <- rp: // forwarded packet, that's all folks - default: - rp.FreePacket() // either channel full or not created - free packet - } - return true + if !rp.IsValid() { + rp.FreePacket() + return false + } + // Check here if SRTP is enabled for the SSRC of the packet - a stream attribute + + if rs.rtcpServiceActive { + ssrc := rp.Ssrc() + + now := time.Now().UnixNano() + + rs.streamsMapMutex.Lock() + str, _, existing := rs.lookupSsrcMap(ssrc) + + // if not found in the input stream then create a new SSRC input stream + if !existing { + str = newSsrcStreamIn(&rp.fromAddr, ssrc) + if len(rs.streamsIn) > rs.MaxNumberInStreams { + rs.sendDataCtrlEvent(MaxNumInStreamReachedData, ssrc, 0) + rp.FreePacket() + rs.streamsMapMutex.Unlock() + return false + } + rs.streamsIn[rs.streamInIndex] = str + rs.streamInIndex++ + str.streamStatus = active + str.statistics.initialDataTime = now // First packet arrival time. + rs.sendDataCtrlEvent(NewStreamData, ssrc, rs.streamInIndex-1) + } else { + // Check if an existing stream is active + if str.streamStatus != active { + rs.sendDataCtrlEvent(WrongStreamStatusData, ssrc, rs.streamInIndex-1) + rp.FreePacket() + rs.streamsMapMutex.Unlock() + return false + + } + // Test if RTCP packets had been received but this is the first data packet from this source. + if str.DataPort == 0 { + str.DataPort = rp.fromAddr.DataPort + } + } + rs.streamsMapMutex.Unlock() + + // Before forwarding packet to next upper layer (application) for further processing: + // 1) check for collisions and loops. If the packet cannot be assigned to a source, it will be rejected. + // 2) check the source is a sufficiently well known source + // TODO: also check CSRC identifiers. + if !str.checkSsrcIncomingData(existing, rs, rp) || !str.recordReceptionData(rp, rs, now) { + // must be discarded due to collision or loop or invalid source + rs.sendDataCtrlEvent(StreamCollisionLoopData, ssrc, rs.streamInIndex-1) + rp.FreePacket() + return false + } + } + select { + case rs.dataReceiveChan <- rp: // forwarded packet, that's all folks + default: + rp.FreePacket() // either channel full or not created - free packet + } + return true } // OnRecvCtrl implements the rtp.TransportRecv OnRecvCtrl method. @@ -493,172 +493,172 @@ func (rs *Session) OnRecvData(rp *DataPacket) bool { // Normal application don't use this method. Only if an application implements its own idea // of the rtp.TransportRecv interface it must implement this function. // -// Delegating is not yet implemented. Applications may receive control events via +// Delegating is not yet implemented. Applications may receive control events via // the CtrlEventChan. // func (rs *Session) OnRecvCtrl(rp *CtrlPacket) bool { - if !rs.rtcpServiceActive { - return true - } - - if pktType := rp.Type(0); pktType != RtcpSR && pktType != RtcpRR { - rp.FreePacket() - return false - } - // Check here if SRTCP is enabled for the SSRC of the packet - a stream attribute - - ctrlEvArr := make([]*CtrlEvent, 0, 10) - - offset := 0 - for offset < rp.inUse { - pktLen := int((rp.Length(offset) + 1) * 4) - - switch rp.Type(offset) { - case RtcpSR: - rrCnt := rp.Count(offset) - if offset+pktLen > len(rp.Buffer()) { - return false - } - // Always check sender's SSRC first in case of RR or SR - str, strIdx, existing := rs.rtcpSenderCheck(rp, offset) - if str == nil { - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(int(strIdx), str.Ssrc(), 0)) - } else { - if !existing { - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(NewStreamCtrl, str.Ssrc(), rs.streamInIndex-1)) - } - str.statistics.lastRtcpSrTime = str.statistics.lastRtcpPacketTime - str.readSenderInfo(rp.toSenderInfo(rtcpHeaderLength + rtcpSsrcLength + offset)) - - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpSR, str.Ssrc(), strIdx)) - - // Offset to first RR block: offset to SR + fixed Header length for SR + length of sender info - rrOffset := offset + rtcpHeaderLength + rtcpSsrcLength + senderInfoLen - - for i := 0; i < rrCnt; i++ { - rr := rp.toRecvReport(rrOffset) - strOut, idx, exists := rs.lookupSsrcMapOut(rr.ssrc()) - // Process Receive Reports that match own output streams (SSRC). - if exists { - strOut.readRecvReport(rr) - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpRR, rr.ssrc(), idx)) - } - rrOffset += reportBlockLen - } - } - // Advance to the next packet in the compound. - offset += pktLen - - case RtcpRR: - if offset+pktLen > len(rp.Buffer()) { - return false - } - // Always check sender's SSRC first in case of RR or SR - str, strIdx, existing := rs.rtcpSenderCheck(rp, offset) - if str == nil { - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(int(strIdx), str.Ssrc(), 0)) - } else { - if !existing { - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(NewStreamCtrl, str.Ssrc(), rs.streamInIndex-1)) - } - - rrCnt := rp.Count(offset) - // Offset to first RR block: offset to RR + fixed Header length for RR - rrOffset := offset + rtcpHeaderLength + rtcpSsrcLength - for i := 0; i < rrCnt; i++ { - rr := rp.toRecvReport(rrOffset) - strOut, idx, exists := rs.lookupSsrcMapOut(rr.ssrc()) - // Process Receive Reports that match own output streams (SSRC) - if exists { - strOut.readRecvReport(rr) - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpRR, rr.ssrc(), idx)) - } - rrOffset += reportBlockLen - } - } - // Advance to the next packet in the compound. - offset += pktLen - - case RtcpSdes: - if offset+pktLen > len(rp.Buffer()) { - return false - } - sdesChunkCnt := rp.Count(offset) - sdesPktLen := int(rp.Length(offset) * 4) // length excl. header word - // Offset to first SDES chunk: offset to SDES + Header word for SDES - sdesChunkOffset := offset + 4 - for i := 0; i < sdesChunkCnt; i++ { - chunk := rp.toSdesChunk(sdesChunkOffset, sdesPktLen) - if chunk == nil { - break - } - chunkLen, idx, ok := rs.processSdesChunk(chunk, rp) - if !ok { - break - } - ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpSdes, chunk.ssrc(), idx)) - sdesChunkOffset += chunkLen - sdesPktLen -= chunkLen - } - // Advance to the next packet in the compound, is also index after SDES packet - offset += pktLen - - case RtcpBye: - if offset+pktLen > len(rp.Buffer()) { - return false - } - // Currently the method suports only one SSRC per BYE packet. To enhance this we need - // to return an array of SSRC/CSRC values. - // - byeCnt := rp.Count(offset) - byePkt := rp.toByeData(offset+4, pktLen-4) - if byePkt != nil { - // Send BYE control event only for known input streams. - if st, idx, ok := rs.lookupSsrcMapIn(byePkt.ssrc(0)); ok { - ctrlEv := newCrtlEvent(RtcpBye, byePkt.ssrc(0), idx) - ctrlEv.Reason = byePkt.getReason(byeCnt) - ctrlEvArr = append(ctrlEvArr, ctrlEv) - st.streamStatus = isClosing - } - // Recompute time intervals, see chapter 6.3.4 - // TODO: not len(rs.streamsIn) but get number of members with streamStatus == active - pmembers := float64(len(rs.streamsOut) + len(rs.streamsIn)) - members := pmembers - 1.0 // received a BYE for one input channel - tc := float64(time.Now().UnixNano()) - tn := tc + members/pmembers*(float64(rs.tnext)-tc) - rs.tnext = int64(tn) - } - // Advance to the next packet in the compound. - offset += pktLen - - case RtcpApp: - // Advance to the next packet in the compound. - offset += pktLen - case RtcpRtpfb: - // Advance to the next packet in the compound. - offset += pktLen - case RtcpPsfb: - // Advance to the next packet in the compound. - offset += pktLen - case RtcpXr: - // Advance to the next packet in the compound. - offset += pktLen - - } - } - select { - case rs.ctrlEventChan <- ctrlEvArr: // send control event - default: - } - // re-compute average packet size. Don't re-compute RTCP interval time, will be done on next RTCP report - // interval. The timing is not affected that much by delaying the interval re-computation. - size := float64(rp.InUse() + 20 + 8) // TODO: get real values for IP and transport from transport module - rs.avrgPacketLength = (1.0/16.0)*size + (15.0/16.0)*rs.avrgPacketLength - - rp.FreePacket() - ctrlEvArr = nil - return true + if !rs.rtcpServiceActive { + return true + } + + if pktType := rp.Type(0); pktType != RtcpSR && pktType != RtcpRR { + rp.FreePacket() + return false + } + // Check here if SRTCP is enabled for the SSRC of the packet - a stream attribute + + ctrlEvArr := make([]*CtrlEvent, 0, 10) + + offset := 0 + for offset < rp.inUse { + pktLen := int((rp.Length(offset) + 1) * 4) + + switch rp.Type(offset) { + case RtcpSR: + rrCnt := rp.Count(offset) + if offset+pktLen > len(rp.Buffer()) { + return false + } + // Always check sender's SSRC first in case of RR or SR + str, strIdx, existing := rs.rtcpSenderCheck(rp, offset) + if str == nil { + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(int(strIdx), str.Ssrc(), 0)) + } else { + if !existing { + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(NewStreamCtrl, str.Ssrc(), rs.streamInIndex-1)) + } + str.statistics.lastRtcpSrTime = str.statistics.lastRtcpPacketTime + str.readSenderInfo(rp.toSenderInfo(rtcpHeaderLength + rtcpSsrcLength + offset)) + + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpSR, str.Ssrc(), strIdx)) + + // Offset to first RR block: offset to SR + fixed Header length for SR + length of sender info + rrOffset := offset + rtcpHeaderLength + rtcpSsrcLength + senderInfoLen + + for i := 0; i < rrCnt; i++ { + rr := rp.toRecvReport(rrOffset) + strOut, idx, exists := rs.lookupSsrcMapOut(rr.ssrc()) + // Process Receive Reports that match own output streams (SSRC). + if exists { + strOut.readRecvReport(rr) + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpRR, rr.ssrc(), idx)) + } + rrOffset += reportBlockLen + } + } + // Advance to the next packet in the compound. + offset += pktLen + + case RtcpRR: + if offset+pktLen > len(rp.Buffer()) { + return false + } + // Always check sender's SSRC first in case of RR or SR + str, strIdx, existing := rs.rtcpSenderCheck(rp, offset) + if str == nil { + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(int(strIdx), str.Ssrc(), 0)) + } else { + if !existing { + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(NewStreamCtrl, str.Ssrc(), rs.streamInIndex-1)) + } + + rrCnt := rp.Count(offset) + // Offset to first RR block: offset to RR + fixed Header length for RR + rrOffset := offset + rtcpHeaderLength + rtcpSsrcLength + for i := 0; i < rrCnt; i++ { + rr := rp.toRecvReport(rrOffset) + strOut, idx, exists := rs.lookupSsrcMapOut(rr.ssrc()) + // Process Receive Reports that match own output streams (SSRC) + if exists { + strOut.readRecvReport(rr) + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpRR, rr.ssrc(), idx)) + } + rrOffset += reportBlockLen + } + } + // Advance to the next packet in the compound. + offset += pktLen + + case RtcpSdes: + if offset+pktLen > len(rp.Buffer()) { + return false + } + sdesChunkCnt := rp.Count(offset) + sdesPktLen := int(rp.Length(offset) * 4) // length excl. header word + // Offset to first SDES chunk: offset to SDES + Header word for SDES + sdesChunkOffset := offset + 4 + for i := 0; i < sdesChunkCnt; i++ { + chunk := rp.toSdesChunk(sdesChunkOffset, sdesPktLen) + if chunk == nil { + break + } + chunkLen, idx, ok := rs.processSdesChunk(chunk, rp) + if !ok { + break + } + ctrlEvArr = append(ctrlEvArr, newCrtlEvent(RtcpSdes, chunk.ssrc(), idx)) + sdesChunkOffset += chunkLen + sdesPktLen -= chunkLen + } + // Advance to the next packet in the compound, is also index after SDES packet + offset += pktLen + + case RtcpBye: + if offset+pktLen > len(rp.Buffer()) { + return false + } + // Currently the method suports only one SSRC per BYE packet. To enhance this we need + // to return an array of SSRC/CSRC values. + // + byeCnt := rp.Count(offset) + byePkt := rp.toByeData(offset+4, pktLen-4) + if byePkt != nil { + // Send BYE control event only for known input streams. + if st, idx, ok := rs.lookupSsrcMapIn(byePkt.ssrc(0)); ok { + ctrlEv := newCrtlEvent(RtcpBye, byePkt.ssrc(0), idx) + ctrlEv.Reason = byePkt.getReason(byeCnt) + ctrlEvArr = append(ctrlEvArr, ctrlEv) + st.streamStatus = isClosing + } + // Recompute time intervals, see chapter 6.3.4 + // TODO: not len(rs.streamsIn) but get number of members with streamStatus == active + pmembers := float64(len(rs.streamsOut) + len(rs.streamsIn)) + members := pmembers - 1.0 // received a BYE for one input channel + tc := float64(time.Now().UnixNano()) + tn := tc + members/pmembers*(float64(rs.tnext)-tc) + rs.tnext = int64(tn) + } + // Advance to the next packet in the compound. + offset += pktLen + + case RtcpApp: + // Advance to the next packet in the compound. + offset += pktLen + case RtcpRtpfb: + // Advance to the next packet in the compound. + offset += pktLen + case RtcpPsfb: + // Advance to the next packet in the compound. + offset += pktLen + case RtcpXr: + // Advance to the next packet in the compound. + offset += pktLen + + } + } + select { + case rs.ctrlEventChan <- ctrlEvArr: // send control event + default: + } + // re-compute average packet size. Don't re-compute RTCP interval time, will be done on next RTCP report + // interval. The timing is not affected that much by delaying the interval re-computation. + size := float64(rp.InUse() + 20 + 8) // TODO: get real values for IP and transport from transport module + rs.avrgPacketLength = (1.0/16.0)*size + (15.0/16.0)*rs.avrgPacketLength + + rp.FreePacket() + ctrlEvArr = nil + return true } // CloseRecv implements the rtp.TransportRecv CloseRecv method. @@ -671,26 +671,26 @@ func (rs *Session) OnRecvCtrl(rp *CtrlPacket) bool { // Only relevant if an application uses "simple RTP". // func (rs *Session) CloseRecv() { - if rs.transportRecv != nil { - rs.transportRecv.CloseRecv() - for allClosed := 0; allClosed != (DataTransportRecvStopped | CtrlTransportRecvStopped); { - allClosed |= <-rs.transportEnd - } - } - if rs.transportEndUpper != nil { - rs.transportEndUpper <- (DataTransportRecvStopped | CtrlTransportRecvStopped) - } + if rs.transportRecv != nil { + rs.transportRecv.CloseRecv() + for allClosed := 0; allClosed != (DataTransportRecvStopped | CtrlTransportRecvStopped); { + allClosed |= <-rs.transportEnd + } + } + if rs.transportEndUpper != nil { + rs.transportEndUpper <- (DataTransportRecvStopped | CtrlTransportRecvStopped) + } } // SetEndChannel implements the rtp.TransportRecv SetEndChannel method. // // An application may register a specific control channel to get information after -// all receiver transports were closed. +// all receiver transports were closed. // // Only relevant if an application uses "simple RTP". // func (rs *Session) SetEndChannel(ch TransportEnd) { - rs.transportEndUpper = ch + rs.transportEndUpper = ch } /* @@ -704,30 +704,30 @@ func (rs *Session) SetEndChannel(ch TransportEnd) { // func (rs *Session) WriteData(rp *DataPacket) (n int, err error) { - strOut, _, _ := rs.lookupSsrcMapOut(rp.Ssrc()) - if strOut.streamStatus != active { - return 0, nil - } - strOut.SenderPacketCnt++ - strOut.SenderOctectCnt += uint32(len(rp.Payload())) - - strOut.streamMutex.Lock() - if !strOut.sender && rs.rtcpCtrlChan != nil { - rs.rtcpCtrlChan <- rtcpIncrementSender - strOut.sender = true - } - strOut.statistics.lastPacketTime = time.Now().UnixNano() - strOut.streamMutex.Unlock() - rs.weSent = true - - // Check here if SRTP is enabled for the SSRC of the packet - a stream attribute - for _, remote := range rs.remotes { - _, err := rs.transportWrite.WriteDataTo(rp, remote) - if err != nil { - return 0, err - } - } - return n, nil + strOut, _, _ := rs.lookupSsrcMapOut(rp.Ssrc()) + if strOut.streamStatus != active { + return 0, nil + } + strOut.SenderPacketCnt++ + strOut.SenderOctectCnt += uint32(len(rp.Payload())) + + strOut.streamMutex.Lock() + if !strOut.sender && rs.rtcpCtrlChan != nil { + rs.rtcpCtrlChan <- rtcpIncrementSender + strOut.sender = true + } + strOut.statistics.lastPacketTime = time.Now().UnixNano() + strOut.streamMutex.Unlock() + rs.weSent = true + + // Check here if SRTP is enabled for the SSRC of the packet - a stream attribute + for _, remote := range rs.remotes { + _, err := rs.transportWrite.WriteDataTo(rp, remote) + if err != nil { + return 0, err + } + } + return n, nil } // WriteCtrl implements the rtp.TransportWrite WriteCtrl method and sends an RTCP packet. @@ -737,16 +737,16 @@ func (rs *Session) WriteData(rp *DataPacket) (n int, err error) { // func (rs *Session) WriteCtrl(rp *CtrlPacket) (n int, err error) { - // Check here if SRTCP is enabled for the SSRC of the packet - a stream attribute - strOut, _, _ := rs.lookupSsrcMapOut(rp.Ssrc(0)) - if strOut.streamStatus != active { - return 0, nil - } - for _, remote := range rs.remotes { - _, err := rs.transportWrite.WriteCtrlTo(rp, remote) - if err != nil { - return 0, err - } - } - return n, nil + // Check here if SRTCP is enabled for the SSRC of the packet - a stream attribute + strOut, _, _ := rs.lookupSsrcMapOut(rp.Ssrc(0)) + if strOut.streamStatus != active { + return 0, nil + } + for _, remote := range rs.remotes { + _, err := rs.transportWrite.WriteCtrlTo(rp, remote) + if err != nil { + return 0, err + } + } + return n, nil } diff --git a/src/net/rtp/sessionlocal.go b/src/net/rtp/sessionlocal.go index e10bd4f..43d592d 100644 --- a/src/net/rtp/sessionlocal.go +++ b/src/net/rtp/sessionlocal.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -23,34 +23,35 @@ package rtp */ import ( - "crypto/rand" - "time" + "crypto/rand" + "time" ) const ( - dataReceiveChanLen = 3 - ctrlEventChanLen = 3 + dataReceiveChanLen = 3 + ctrlEventChanLen = 3 ) const ( - maxNumberOutStreams = 5 - maxNumberInStreams = 30 + maxNumberOutStreams = 5 + maxNumberInStreams = 30 ) + // conflictAddr stores conflicting address detected during loop and collision check // It also stores the time of the latest conflict- type conflictAddr struct { - Address - seenAt int64 + Address + seenAt int64 } // The RTCP control commands a a simple uint32: the MSB defines the command, the lower // 3 bytes the value for the command, if a value is necssary for the command. // const ( - rtcpCtrlCmdMask = 0xff000000 - rtcpStopService = 0x01000000 - rtcpModifyInterval = 0x02000000 // Modify RTCP timer interval, low 3 bytes contain new tick time in ms - rtcpIncrementSender = 0x03000000 // a stream became an active sender, count this globally + rtcpCtrlCmdMask = 0xff000000 + rtcpStopService = 0x01000000 + rtcpModifyInterval = 0x02000000 // Modify RTCP timer interval, low 3 bytes contain new tick time in ms + rtcpIncrementSender = 0x03000000 // a stream became an active sender, count this globally ) // rtcpCtrlChan sends control data to the RTCP service. @@ -60,8 +61,8 @@ const ( // type rtcpCtrlChan chan uint32 -// Manages the output SSRC streams. -// +// Manages the output SSRC streams. +// // Refer to RFC 3550: do not use SSRC to multiplex different media types on one session. One RTP session // shall handle one media type only. However, a RTP session can have several SSRC output streams for the // same media types, for example sending video data from two or more cameras. @@ -76,153 +77,152 @@ type remoteMap map[uint32]*Address type conflictMap map[uint32]*conflictAddr - // rtcpService provides the RTCP service and sends RTCP reports at computed intervals. // func (rs *Session) rtcpService(ti, td int64) { - granularity := time.Duration(250e6) // 250 ms - ssrcTimeout := 5 * td - dataTimeout := 2 * ti - - rs.rtcpServiceActive = true - ticker := time.NewTicker(granularity) - var cmd uint32 - for cmd != rtcpStopService { - select { - case <-ticker.C: - now := time.Now().UnixNano() - if now < rs.tnext { - continue - } - - var outActive, inActive int // Counts all members in active state - var inActiveSinceLastRR int - - for idx, str := range rs.streamsIn { - switch str.streamStatus { - case active: - str.streamMutex.Lock() - // Manage number of active senders on input streams. - // Every time this stream receives a packet it updates the last packet time. If the input stream - // did not receive a RTP packet for 2 RTCP intervals its sender status is set to false and the - // number of active senders in this session is decremented if not already zero. See chapter 6.3.5 - rtpDiff := now - str.statistics.lastPacketTime - if str.sender { - if str.dataAfterLastReport { - inActiveSinceLastRR++ - } - if rtpDiff > dataTimeout { - str.sender = false - if rs.activeSenders > 0 { - rs.activeSenders-- - } - } - } - // SSRC timeout processing: check for inactivity longer than 5*non-random interval time - // (both RTP/RTCP inactivity) chapter 6.3.5 - rtcpDiff := now - str.statistics.lastRtcpPacketTime - if rtpDiff > rtcpDiff { - rtpDiff = rtcpDiff - } - if rtpDiff > ssrcTimeout { - delete(rs.streamsIn, idx) - } - str.streamMutex.Unlock() - - case isClosing: - str.streamStatus = isClosed - - case isClosed: - delete(rs.streamsOut, idx) - } - } - - var rc *CtrlPacket - var streamForRR *SsrcStream - var outputSenders int - - for idx, str := range rs.streamsOut { - switch str.streamStatus { - case active: - outActive++ - streamForRR = str // remember one active stream in case there is no sending output stream - - // Manage number of active senders. Every time this stream sends a packet the output stream - // sender updates the last packet time. If the output stream did not send RTP for 2 RTCP - // intervals its sender status is set to false and the number of active senders in this session - // is decremented if not already zero. See chapter 6.3.8 - // - str.streamMutex.Lock() - rtpDiff := now - str.statistics.lastPacketTime - if str.sender { - outputSenders++ - if rtpDiff > dataTimeout { - str.sender = false - outputSenders-- - if rs.activeSenders > 0 { - rs.activeSenders-- - } - } - } - str.streamMutex.Unlock() - if str.sender { - if rc == nil { - rc = rs.buildRtcpPkt(str, inActiveSinceLastRR) - } else { - rs.addSenderReport(str, rc) - } - } - - case isClosing: - str.streamStatus = isClosed - - case isClosed: - delete(rs.streamsOut, idx) - } - } - // If no active output stream is left then weSent becomes false - rs.weSent = outputSenders > 0 - - // if rc is nil then we found no sending stream and havent't build a control packet. Just use - // one active output stream as proxy to create at least an RR and the proxy's SDES (RR may be - // empty as well). If also no active output stream - don't create and send RTCP report. In this - // case the RTP stack in completey inactive. - if rc == nil && streamForRR != nil { - rc = rs.buildRtcpPkt(streamForRR, inActiveSinceLastRR) - } - if rc != nil { - rs.WriteCtrl(rc) - rs.tprev = now - size := float64(rc.InUse() + 20 + 8) // TODO: get real values for IP and transport from transport module - rs.avrgPacketLength = (1.0/16.0)*size + (15.0/16.0)*rs.avrgPacketLength - - ti, td := rtcpInterval(outActive+inActive, int(rs.activeSenders), rs.RtcpSessionBandwidth, - rs.avrgPacketLength, rs.weSent, false) - rs.tnext = ti + now - dataTimeout = 2 * ti - ssrcTimeout = 5 * td - rc.FreePacket() - } - outActive = 0 - inActive = 0 - - case cmd = <-rs.rtcpCtrlChan: - switch cmd & rtcpCtrlCmdMask { - case rtcpStopService: - ticker.Stop() - - case rtcpModifyInterval: - ticker.Stop() - granularity = time.Duration(cmd &^ rtcpCtrlCmdMask) - ticker = time.NewTicker(granularity) - - case rtcpIncrementSender: - rs.activeSenders++ - } - } - } - rs.rtcpServiceActive = false + granularity := time.Duration(250e6) // 250 ms + ssrcTimeout := 5 * td + dataTimeout := 2 * ti + + rs.rtcpServiceActive = true + ticker := time.NewTicker(granularity) + var cmd uint32 + for cmd != rtcpStopService { + select { + case <-ticker.C: + now := time.Now().UnixNano() + if now < rs.tnext { + continue + } + + var outActive, inActive int // Counts all members in active state + var inActiveSinceLastRR int + + for idx, str := range rs.streamsIn { + switch str.streamStatus { + case active: + str.streamMutex.Lock() + // Manage number of active senders on input streams. + // Every time this stream receives a packet it updates the last packet time. If the input stream + // did not receive a RTP packet for 2 RTCP intervals its sender status is set to false and the + // number of active senders in this session is decremented if not already zero. See chapter 6.3.5 + rtpDiff := now - str.statistics.lastPacketTime + if str.sender { + if str.dataAfterLastReport { + inActiveSinceLastRR++ + } + if rtpDiff > dataTimeout { + str.sender = false + if rs.activeSenders > 0 { + rs.activeSenders-- + } + } + } + // SSRC timeout processing: check for inactivity longer than 5*non-random interval time + // (both RTP/RTCP inactivity) chapter 6.3.5 + rtcpDiff := now - str.statistics.lastRtcpPacketTime + if rtpDiff > rtcpDiff { + rtpDiff = rtcpDiff + } + if rtpDiff > ssrcTimeout { + delete(rs.streamsIn, idx) + } + str.streamMutex.Unlock() + + case isClosing: + str.streamStatus = isClosed + + case isClosed: + delete(rs.streamsOut, idx) + } + } + + var rc *CtrlPacket + var streamForRR *SsrcStream + var outputSenders int + + for idx, str := range rs.streamsOut { + switch str.streamStatus { + case active: + outActive++ + streamForRR = str // remember one active stream in case there is no sending output stream + + // Manage number of active senders. Every time this stream sends a packet the output stream + // sender updates the last packet time. If the output stream did not send RTP for 2 RTCP + // intervals its sender status is set to false and the number of active senders in this session + // is decremented if not already zero. See chapter 6.3.8 + // + str.streamMutex.Lock() + rtpDiff := now - str.statistics.lastPacketTime + if str.sender { + outputSenders++ + if rtpDiff > dataTimeout { + str.sender = false + outputSenders-- + if rs.activeSenders > 0 { + rs.activeSenders-- + } + } + } + str.streamMutex.Unlock() + if str.sender { + if rc == nil { + rc = rs.buildRtcpPkt(str, inActiveSinceLastRR) + } else { + rs.addSenderReport(str, rc) + } + } + + case isClosing: + str.streamStatus = isClosed + + case isClosed: + delete(rs.streamsOut, idx) + } + } + // If no active output stream is left then weSent becomes false + rs.weSent = outputSenders > 0 + + // if rc is nil then we found no sending stream and havent't build a control packet. Just use + // one active output stream as proxy to create at least an RR and the proxy's SDES (RR may be + // empty as well). If also no active output stream - don't create and send RTCP report. In this + // case the RTP stack in completey inactive. + if rc == nil && streamForRR != nil { + rc = rs.buildRtcpPkt(streamForRR, inActiveSinceLastRR) + } + if rc != nil { + rs.WriteCtrl(rc) + rs.tprev = now + size := float64(rc.InUse() + 20 + 8) // TODO: get real values for IP and transport from transport module + rs.avrgPacketLength = (1.0/16.0)*size + (15.0/16.0)*rs.avrgPacketLength + + ti, td := rtcpInterval(outActive+inActive, int(rs.activeSenders), rs.RtcpSessionBandwidth, + rs.avrgPacketLength, rs.weSent, false) + rs.tnext = ti + now + dataTimeout = 2 * ti + ssrcTimeout = 5 * td + rc.FreePacket() + } + outActive = 0 + inActive = 0 + + case cmd = <-rs.rtcpCtrlChan: + switch cmd & rtcpCtrlCmdMask { + case rtcpStopService: + ticker.Stop() + + case rtcpModifyInterval: + ticker.Stop() + granularity = time.Duration(cmd &^ rtcpCtrlCmdMask) + ticker = time.NewTicker(granularity) + + case rtcpIncrementSender: + rs.activeSenders++ + } + } + } + rs.rtcpServiceActive = false } // buildRtcpPkt creates an RTCP compound and fills it with a SR or RR packet. @@ -235,71 +235,71 @@ func (rs *Session) rtcpService(ti, td int64) { // func (rs *Session) buildRtcpPkt(strOut *SsrcStream, inStreamCnt int) (rc *CtrlPacket) { - var pktLen, offset int - if strOut.sender { - rc, offset = strOut.newCtrlPacket(RtcpSR) - offset = rc.addHeaderSsrc(offset, strOut.Ssrc()) - - var info senderInfo - info, offset = rc.newSenderInfo() - strOut.fillSenderInfo(info) // create a sender info block after fixed header and SSRC. - } else { - rc, offset = strOut.newCtrlPacket(RtcpRR) - offset = rc.addHeaderSsrc(offset, strOut.Ssrc()) - } - pktLen = offset/4 - 1 - // TODO Handle round-robin if we have more then 31 really active input streams (chap 6.4) - if inStreamCnt >= 31 { - inStreamCnt = 31 - } - var rrCnt int - if inStreamCnt > 0 { - for _, strIn := range rs.streamsIn { - if strIn.dataAfterLastReport { - strIn.dataAfterLastReport = false - strIn.makeRecvReport(rc) - pktLen += reportBlockLen / 4 // increment SR/RR to include length of this recv report block - rrCnt++ - if inStreamCnt--; inStreamCnt <= 0 { - break - } - } - } - } - rc.SetLength(0, uint16(pktLen)) // length of first RTCP packet in compound: fixed header, 0 or 1 SR, n*RR - rc.SetCount(0, rrCnt) - - rs.addSdes(strOut, rc) - - return + var pktLen, offset int + if strOut.sender { + rc, offset = strOut.newCtrlPacket(RtcpSR) + offset = rc.addHeaderSsrc(offset, strOut.Ssrc()) + + var info senderInfo + info, offset = rc.newSenderInfo() + strOut.fillSenderInfo(info) // create a sender info block after fixed header and SSRC. + } else { + rc, offset = strOut.newCtrlPacket(RtcpRR) + offset = rc.addHeaderSsrc(offset, strOut.Ssrc()) + } + pktLen = offset/4 - 1 + // TODO Handle round-robin if we have more then 31 really active input streams (chap 6.4) + if inStreamCnt >= 31 { + inStreamCnt = 31 + } + var rrCnt int + if inStreamCnt > 0 { + for _, strIn := range rs.streamsIn { + if strIn.dataAfterLastReport { + strIn.dataAfterLastReport = false + strIn.makeRecvReport(rc) + pktLen += reportBlockLen / 4 // increment SR/RR to include length of this recv report block + rrCnt++ + if inStreamCnt--; inStreamCnt <= 0 { + break + } + } + } + } + rc.SetLength(0, uint16(pktLen)) // length of first RTCP packet in compound: fixed header, 0 or 1 SR, n*RR + rc.SetCount(0, rrCnt) + + rs.addSdes(strOut, rc) + + return } // buildRtcpByePkt builds an RTCP BYE compound. // func (rs *Session) buildRtcpByePkt(strOut *SsrcStream, reason string) (rc *CtrlPacket) { - rc = rs.buildRtcpPkt(strOut, 0) - - headerOffset := rc.InUse() - strOut.addCtrlHeader(rc, headerOffset, RtcpBye) - // Here we may add a loop over CSRC (addtional data in ouput steam) and hand over to makeByeData - offset := strOut.makeByeData(rc, reason) - rc.SetCount(headerOffset, 1) // currently one BYE SSRC/CSRC per packet - rc.SetLength(headerOffset, uint16((offset-headerOffset)/4-1)) // length of BYE packet in compound: fixed header plus BYE data - return + rc = rs.buildRtcpPkt(strOut, 0) + + headerOffset := rc.InUse() + strOut.addCtrlHeader(rc, headerOffset, RtcpBye) + // Here we may add a loop over CSRC (addtional data in ouput steam) and hand over to makeByeData + offset := strOut.makeByeData(rc, reason) + rc.SetCount(headerOffset, 1) // currently one BYE SSRC/CSRC per packet + rc.SetLength(headerOffset, uint16((offset-headerOffset)/4-1)) // length of BYE packet in compound: fixed header plus BYE data + return } // addSenderReport appends a SDES packet into to control packet. // func (rs *Session) addSdes(strOut *SsrcStream, rc *CtrlPacket) { - offsetSdes := rc.InUse() - if strOut.sdesChunkLen > 0 { - strOut.addCtrlHeader(rc, offsetSdes, RtcpSdes) // Add a RTCP SDES packet header after the SR/RR packet - // makeSdesChunk returns position where to append next chunk - for CSRCs that contribute to this, chap 6.5, RFC 3550 - // CSRCs currently not supported, need additional data structures in output stream. - nextChunk := strOut.makeSdesChunk(rc) - rc.SetCount(offsetSdes, 1) // currently one SDES chunk per SDES packet - rc.SetLength(offsetSdes, uint16((nextChunk-offsetSdes)/4-1)) // length of SDES packet in compound: fixed header plus SDES chunk len - } + offsetSdes := rc.InUse() + if strOut.sdesChunkLen > 0 { + strOut.addCtrlHeader(rc, offsetSdes, RtcpSdes) // Add a RTCP SDES packet header after the SR/RR packet + // makeSdesChunk returns position where to append next chunk - for CSRCs that contribute to this, chap 6.5, RFC 3550 + // CSRCs currently not supported, need additional data structures in output stream. + nextChunk := strOut.makeSdesChunk(rc) + rc.SetCount(offsetSdes, 1) // currently one SDES chunk per SDES packet + rc.SetLength(offsetSdes, uint16((nextChunk-offsetSdes)/4-1)) // length of SDES packet in compound: fixed header plus SDES chunk len + } } // addSenderReport appends a sender report into to control packet if the output stream is a sender. @@ -310,129 +310,129 @@ func (rs *Session) addSdes(strOut *SsrcStream, rc *CtrlPacket) { // func (rs *Session) addSenderReport(strOut *SsrcStream, rc *CtrlPacket) { - if !strOut.sender { - return - } + if !strOut.sender { + return + } - headerOffset := rc.InUse() - if headerOffset+strOut.sdesChunkLen+8 > len(rc.Buffer()) { - return - } - offset := strOut.addCtrlHeader(rc, headerOffset, RtcpSR) - rc.addHeaderSsrc(offset, strOut.Ssrc()) + headerOffset := rc.InUse() + if headerOffset+strOut.sdesChunkLen+8 > len(rc.Buffer()) { + return + } + offset := strOut.addCtrlHeader(rc, headerOffset, RtcpSR) + rc.addHeaderSsrc(offset, strOut.Ssrc()) - var info senderInfo - info, offset = rc.newSenderInfo() - strOut.fillSenderInfo(info) // create a sender info block after fixed header and SSRC. + var info senderInfo + info, offset = rc.newSenderInfo() + strOut.fillSenderInfo(info) // create a sender info block after fixed header and SSRC. - pktLen := (offset-headerOffset)/4 - 1 - rc.SetLength(headerOffset, uint16(pktLen)) // length of RTCP packet in compound: fixed header, SR, 0*RR - rc.SetCount(headerOffset, 0) // zero receiver reports in this SR + pktLen := (offset-headerOffset)/4 - 1 + rc.SetLength(headerOffset, uint16(pktLen)) // length of RTCP packet in compound: fixed header, SR, 0*RR + rc.SetCount(headerOffset, 0) // zero receiver reports in this SR - rs.addSdes(strOut, rc) + rs.addSdes(strOut, rc) } // rtcpSenderCheck is a helper function for OnRecvCtrl and checks if a sender's SSRC. -// +// func (rs *Session) rtcpSenderCheck(rp *CtrlPacket, offset int) (*SsrcStream, uint32, bool) { - ssrc := rp.Ssrc(offset) // get SSRC from control packet - - rs.streamsMapMutex.Lock() - str, strIdx, existing := rs.lookupSsrcMap(ssrc) - - // if not found in the input stream then create a new SSRC input stream - if !existing { - if len(rs.streamsIn) > rs.MaxNumberInStreams { - rs.streamsMapMutex.Unlock() - return nil, MaxNumInStreamReachedCtrl, false - } - str = newSsrcStreamIn(&rp.fromAddr, ssrc) - str.streamStatus = active - rs.streamsIn[rs.streamInIndex] = str - rs.streamInIndex++ - } else { - // Check if an existing stream is active - if str.streamStatus != active { - rs.streamsMapMutex.Unlock() - return nil, WrongStreamStatusCtrl, false - } - // Test if RTP packets had been received but this is the first control packet from this source. - if str.CtrlPort == 0 { - str.CtrlPort = rp.fromAddr.CtrlPort - } - } - rs.streamsMapMutex.Unlock() - - // Check if sender's SSRC collides or loops - if !str.checkSsrcIncomingCtrl(existing, rs, &rp.fromAddr) { - return nil, StreamCollisionLoopCtrl, false - } - // record reception time - str.statistics.lastRtcpPacketTime = time.Now().UnixNano() - return str, strIdx, existing + ssrc := rp.Ssrc(offset) // get SSRC from control packet + + rs.streamsMapMutex.Lock() + str, strIdx, existing := rs.lookupSsrcMap(ssrc) + + // if not found in the input stream then create a new SSRC input stream + if !existing { + if len(rs.streamsIn) > rs.MaxNumberInStreams { + rs.streamsMapMutex.Unlock() + return nil, MaxNumInStreamReachedCtrl, false + } + str = newSsrcStreamIn(&rp.fromAddr, ssrc) + str.streamStatus = active + rs.streamsIn[rs.streamInIndex] = str + rs.streamInIndex++ + } else { + // Check if an existing stream is active + if str.streamStatus != active { + rs.streamsMapMutex.Unlock() + return nil, WrongStreamStatusCtrl, false + } + // Test if RTP packets had been received but this is the first control packet from this source. + if str.CtrlPort == 0 { + str.CtrlPort = rp.fromAddr.CtrlPort + } + } + rs.streamsMapMutex.Unlock() + + // Check if sender's SSRC collides or loops + if !str.checkSsrcIncomingCtrl(existing, rs, &rp.fromAddr) { + return nil, StreamCollisionLoopCtrl, false + } + // record reception time + str.statistics.lastRtcpPacketTime = time.Now().UnixNano() + return str, strIdx, existing } -// sendDataCtrlEvent is a helper function to OnRecvData and sends one control event to the application +// sendDataCtrlEvent is a helper function to OnRecvData and sends one control event to the application // if the control event chanel is active. // func (rs *Session) sendDataCtrlEvent(code int, ssrc, index uint32) { - var ctrlEvArr [1]*CtrlEvent - ctrlEvArr[0] = newCrtlEvent(code, ssrc, index) - - if ctrlEvArr[0] != nil { - select { - case rs.ctrlEventChan <- ctrlEvArr[:]: // send control event - default: - } - } + var ctrlEvArr [1]*CtrlEvent + ctrlEvArr[0] = newCrtlEvent(code, ssrc, index) + + if ctrlEvArr[0] != nil { + select { + case rs.ctrlEventChan <- ctrlEvArr[:]: // send control event + default: + } + } } // lookupSsrcMap returns a SsrcStream, either a SsrcStreamIn or SsrcStreamOut for a given SSRC, nil and false if none found. // func (rs *Session) lookupSsrcMap(ssrc uint32) (str *SsrcStream, idx uint32, exists bool) { - if str, idx, exists = rs.lookupSsrcMapOut(ssrc); exists { - return - } - if str, idx, exists = rs.lookupSsrcMapIn(ssrc); exists { - return - } - return nil, 0, false + if str, idx, exists = rs.lookupSsrcMapOut(ssrc); exists { + return + } + if str, idx, exists = rs.lookupSsrcMapIn(ssrc); exists { + return + } + return nil, 0, false } // lookupSsrcMapIn returns a SsrcStreamIn for a given SSRC, nil and false if none found. // func (rs *Session) lookupSsrcMapIn(ssrc uint32) (*SsrcStream, uint32, bool) { - for idx, str := range rs.streamsIn { - if ssrc == str.ssrc { - return str, idx, true - } - } - return nil, 0, false + for idx, str := range rs.streamsIn { + if ssrc == str.ssrc { + return str, idx, true + } + } + return nil, 0, false } // lookupSsrcMapOut returns a SsrcStreamOut for a given SSRC, nil and false if none found. // func (rs *Session) lookupSsrcMapOut(ssrc uint32) (*SsrcStream, uint32, bool) { - for idx, str := range rs.streamsOut { - if ssrc == str.ssrc { - return str, idx, true - } - } - return nil, 0, false + for idx, str := range rs.streamsOut { + if ssrc == str.ssrc { + return str, idx, true + } + } + return nil, 0, false } // isOutputSsrc checks if a given SSRC is already used in our output streams. // Use this functions to detect collisions. // func (rs *Session) isOutputSsrc(ssrc uint32) (found bool) { - var str *SsrcStream - for _, str = range rs.streamsOut { - if ssrc == str.ssrc { - found = true - break - } - } - return + var str *SsrcStream + for _, str = range rs.streamsOut { + if ssrc == str.ssrc { + found = true + break + } + } + return } // checkConflictData checks and manages entries of conflicting data addresses. @@ -443,24 +443,24 @@ func (rs *Session) isOutputSsrc(ssrc uint32) (found bool) { // and false. // func (rs *Session) checkConflictData(addr *Address) (found bool) { - var entry *conflictAddr - tm := time.Now().UnixNano() - - for _, entry = range rs.conflicts { - if addr.IpAddr.Equal(entry.IpAddr) && addr.DataPort == entry.DataPort { - found = true - entry.seenAt = tm - return - } - } - entry = new(conflictAddr) - entry.IpAddr = addr.IpAddr - entry.DataPort = addr.DataPort - entry.seenAt = tm - rs.conflicts[rs.conflictIndex] = entry - rs.conflictIndex++ - found = false - return + var entry *conflictAddr + tm := time.Now().UnixNano() + + for _, entry = range rs.conflicts { + if addr.IpAddr.Equal(entry.IpAddr) && addr.DataPort == entry.DataPort { + found = true + entry.seenAt = tm + return + } + } + entry = new(conflictAddr) + entry.IpAddr = addr.IpAddr + entry.DataPort = addr.DataPort + entry.seenAt = tm + rs.conflicts[rs.conflictIndex] = entry + rs.conflictIndex++ + found = false + return } // checkConflictData checks and manages entries of conflicting data addresses. @@ -471,94 +471,94 @@ func (rs *Session) checkConflictData(addr *Address) (found bool) { // and false. // func (rs *Session) checkConflictCtrl(addr *Address) (found bool) { - var entry *conflictAddr - tm := time.Now().UnixNano() - - for _, entry = range rs.conflicts { - if addr.IpAddr.Equal(entry.IpAddr) && addr.CtrlPort == entry.CtrlPort { - found = true - entry.seenAt = tm - return - } - } - entry = new(conflictAddr) - entry.IpAddr = addr.IpAddr - entry.CtrlPort = addr.CtrlPort - entry.seenAt = tm - rs.conflicts[rs.conflictIndex] = entry - rs.conflictIndex++ - found = false - return + var entry *conflictAddr + tm := time.Now().UnixNano() + + for _, entry = range rs.conflicts { + if addr.IpAddr.Equal(entry.IpAddr) && addr.CtrlPort == entry.CtrlPort { + found = true + entry.seenAt = tm + return + } + } + entry = new(conflictAddr) + entry.IpAddr = addr.IpAddr + entry.CtrlPort = addr.CtrlPort + entry.seenAt = tm + rs.conflicts[rs.conflictIndex] = entry + rs.conflictIndex++ + found = false + return } // processSdesChunk checks if the chunk's SSRC is already known and if yes, parse it. // The method returns the length of the chunk . // func (rs *Session) processSdesChunk(chunk sdesChunk, rp *CtrlPacket) (int, uint32, bool) { - chunkLen, ok := chunk.chunkLen() - if !ok { - return 0, 0, false - } - strIn, idx, existing := rs.lookupSsrcMapIn(chunk.ssrc()) - if !existing { - return chunkLen, idx, true - } - strIn.parseSdesChunk(chunk) - return chunkLen, idx, true + chunkLen, ok := chunk.chunkLen() + if !ok { + return 0, 0, false + } + strIn, idx, existing := rs.lookupSsrcMapIn(chunk.ssrc()) + if !existing { + return chunkLen, idx, true + } + strIn.parseSdesChunk(chunk) + return chunkLen, idx, true } -// replaceStream creates a new output stream, initializes it from the old output stream and replaces the old output stream. +// replaceStream creates a new output stream, initializes it from the old output stream and replaces the old output stream. // // The old output stream will then become an input streamm - this handling is called if we have a conflict during // collision, loop detection (see algorithm in chap 8.2, RFC 3550). // func (rs *Session) replaceStream(oldOut *SsrcStream) (newOut *SsrcStream) { - var str *SsrcStream - var idx uint32 - for idx, str = range rs.streamsOut { - if oldOut.ssrc == str.ssrc { - break - } - } - // get new stream and copy over attributes from old stream - newOut = newSsrcStreamOut(&Address{oldOut.IpAddr, oldOut.DataPort, oldOut.CtrlPort}, 0, 0) - - for itemType, itemTxt := range oldOut.SdesItems { - newOut.SetSdesItem(itemType, itemTxt) - } - newOut.SetPayloadType(oldOut.PayloadType()) - newOut.sender = oldOut.sender - - // Now lock and re-shuffle the streams - rs.streamsMapMutex.Lock() - defer rs.streamsMapMutex.Unlock() - - // Don't reuse an existing SSRC - for _, _, exists := rs.lookupSsrcMap(newOut.Ssrc()); exists; _, _, exists = rs.lookupSsrcMap(newOut.Ssrc()) { - newOut.newSsrc() - } - newOut.streamType = OutputStream - rs.streamsOut[idx] = newOut // replace the oldOut with a new initialized out, new SSRC, sequence but old address - - // sanity check - this is a panic, something stange happened - for idx, str = range rs.streamsIn { - if oldOut.ssrc == str.ssrc { - panic("Panic: found input stream during collision handling - expected none") - return - } - } - oldOut.streamType = InputStream - rs.streamsIn[rs.streamInIndex] = oldOut - rs.streamInIndex++ - return + var str *SsrcStream + var idx uint32 + for idx, str = range rs.streamsOut { + if oldOut.ssrc == str.ssrc { + break + } + } + // get new stream and copy over attributes from old stream + newOut = newSsrcStreamOut(&Address{oldOut.IpAddr, oldOut.DataPort, oldOut.CtrlPort}, 0, 0) + + for itemType, itemTxt := range oldOut.SdesItems { + newOut.SetSdesItem(itemType, itemTxt) + } + newOut.SetPayloadType(oldOut.PayloadType()) + newOut.sender = oldOut.sender + + // Now lock and re-shuffle the streams + rs.streamsMapMutex.Lock() + defer rs.streamsMapMutex.Unlock() + + // Don't reuse an existing SSRC + for _, _, exists := rs.lookupSsrcMap(newOut.Ssrc()); exists; _, _, exists = rs.lookupSsrcMap(newOut.Ssrc()) { + newOut.newSsrc() + } + newOut.streamType = OutputStream + rs.streamsOut[idx] = newOut // replace the oldOut with a new initialized out, new SSRC, sequence but old address + + // sanity check - this is a panic, something stange happened + for idx, str = range rs.streamsIn { + if oldOut.ssrc == str.ssrc { + panic("Panic: found input stream during collision handling - expected none") + return + } + } + oldOut.streamType = InputStream + rs.streamsIn[rs.streamInIndex] = oldOut + rs.streamInIndex++ + return } // The following constants were taken from RFC 3550, chapters 6.3.1 and A.7 const ( - rtcpMinimumTime = 5.0 - rtcpSenderFraction = 0.25 - rtcpRecvFraction = 1.0 - rtcpSenderFraction - compensation = 2.71828 - 1.5 + rtcpMinimumTime = 5.0 + rtcpSenderFraction = 0.25 + rtcpRecvFraction = 1.0 - rtcpSenderFraction + compensation = 2.71828 - 1.5 ) // rtcpInterval helper function computes the next time when to send an RTCP packet. @@ -567,59 +567,59 @@ const ( // func rtcpInterval(members, senders int, rtcpBw, avrgSize float64, weSent, initial bool) (int64, int64) { - rtcpMinTime := rtcpMinimumTime - if initial { - rtcpMinTime /= 2 - } - - /* - * Dedicate a fraction of the RTCP bandwidth to senders unless - * the number of senders is large enough that their share is - * more than that fraction. - */ - n := members - if senders <= int((float64(members) * rtcpSenderFraction)) { - if weSent { - rtcpBw *= rtcpSenderFraction - n = senders - } else { - rtcpBw *= rtcpRecvFraction - n -= senders - } - } - /* - * The effective number of sites times the average packet size is - * the total number of octets sent when each site sends a report. - * Dividing this by the effective bandwidth gives the time - * interval over which those packets must be sent in order to - * meet the bandwidth target, with a minimum enforced. In that - * time interval we send one report so this time is also our - * average time between reports. - */ - t := avrgSize * float64(n) / rtcpBw - if t < rtcpMinTime { - t = rtcpMinTime - } - td := int64(t * 1e9) // determinisitic interval, see chap 6.3.1, 6.3.5 - var randBuf [2]byte - rand.Read(randBuf[:]) - randNo := uint16(randBuf[0]) - randNo |= uint16(randBuf[1]) << 8 - randFloat := float64(randNo)/65536.0 + 0.5 - - t *= randFloat - t /= compensation - // return as nanoseconds - return int64(t * 1e9), td + rtcpMinTime := rtcpMinimumTime + if initial { + rtcpMinTime /= 2 + } + + /* + * Dedicate a fraction of the RTCP bandwidth to senders unless + * the number of senders is large enough that their share is + * more than that fraction. + */ + n := members + if senders <= int((float64(members) * rtcpSenderFraction)) { + if weSent { + rtcpBw *= rtcpSenderFraction + n = senders + } else { + rtcpBw *= rtcpRecvFraction + n -= senders + } + } + /* + * The effective number of sites times the average packet size is + * the total number of octets sent when each site sends a report. + * Dividing this by the effective bandwidth gives the time + * interval over which those packets must be sent in order to + * meet the bandwidth target, with a minimum enforced. In that + * time interval we send one report so this time is also our + * average time between reports. + */ + t := avrgSize * float64(n) / rtcpBw + if t < rtcpMinTime { + t = rtcpMinTime + } + td := int64(t * 1e9) // determinisitic interval, see chap 6.3.1, 6.3.5 + var randBuf [2]byte + rand.Read(randBuf[:]) + randNo := uint16(randBuf[0]) + randNo |= uint16(randBuf[1]) << 8 + randFloat := float64(randNo)/65536.0 + 0.5 + + t *= randFloat + t /= compensation + // return as nanoseconds + return int64(t * 1e9), td } // newCrtlEvent is a little helper function to create and initialize a new control event. func newCrtlEvent(eventType int, ssrc, idx uint32) (ctrlEv *CtrlEvent) { - ctrlEv = new(CtrlEvent) - ctrlEv.EventType = eventType - ctrlEv.Ssrc = ssrc - ctrlEv.Index = idx - return + ctrlEv = new(CtrlEvent) + ctrlEv.EventType = eventType + ctrlEv.Ssrc = ssrc + ctrlEv.Index = idx + return } // Number of seconds ellapsed from 1900 to 1970, see RFC 5905 @@ -627,14 +627,14 @@ const ntpEpochOffset = 2208988800 // toNtpStamp converts a GO time into the NTP format according to RFC 5905 func toNtpStamp(tm int64) (seconds, fraction uint32) { - seconds = uint32(tm/1e9 + ntpEpochOffset) // Go uses ns, thus divide by 1e9 to get seconds - fraction = uint32(((tm % 1e9) << 32) / 1e9) - return + seconds = uint32(tm/1e9 + ntpEpochOffset) // Go uses ns, thus divide by 1e9 to get seconds + fraction = uint32(((tm % 1e9) << 32) / 1e9) + return } -// fromNtp converts a NTP timestamp into GO time +// fromNtp converts a NTP timestamp into GO time func fromNtp(seconds, fraction uint32) (tm int64) { - n := (int64(fraction) * 1e9) >> 32 - tm = (int64(seconds)-ntpEpochOffset)*1e9 + n - return + n := (int64(fraction) * 1e9) >> 32 + tm = (int64(seconds)-ntpEpochOffset)*1e9 + n + return } diff --git a/src/net/rtp/stream.go b/src/net/rtp/stream.go index fefaa61..1a782c4 100644 --- a/src/net/rtp/stream.go +++ b/src/net/rtp/stream.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -19,115 +19,115 @@ package rtp import ( - "crypto/rand" - "time" - "sync" + "crypto/rand" + "sync" + "time" ) const ( - maxDropout = 3000 - minSequential = 0 - maxMisorder = 100 + maxDropout = 3000 + minSequential = 0 + maxMisorder = 100 ) // The type of the stream. const ( - InputStream = iota - OutputStream + InputStream = iota + OutputStream ) const ( - idle = iota - active - isClosing - isClosed + idle = iota + active + isClosing + isClosed ) type SdesItemMap map[int]string // ctrlStatistics holds data relevant to compute RTCP Receive Reports (RR) and this is part of SsrcStreamIn type ctrlStatistics struct { - // - // all times are in nanoseconds - lastPacketTime, // time the last RTP packet from this source was received - lastRtcpPacketTime, // time the last RTCP packet was received. - initialDataTime, - lastRtcpSrTime int64 // time the last RTCP SR was received. Required for DLSR computation. - - // Data used to compute outgoing RR reports. - packetCount, // number of packets received from this source. - octetCount, // number of octets received from this source. - extendedMaxSeqNum, - lastPacketTransitTime, - initialDataTimestamp, - cumulativePacketLost uint32 - maxSeqNum uint16 // the highest sequence number seen from this source - fractionLost uint8 - // for interarrivel jitter computation - // interarrival jitter of packets from this source. - jitter uint32 - - // this flag assures we only call one gotHello and one gotGoodbye for this src. - flag bool - // for source validation: - probation int // packets in sequence before valid. - baseSeqNum uint16 - expectedPrior, - receivedPrior, - badSeqNum, - seqNumAccum uint32 + // + // all times are in nanoseconds + lastPacketTime, // time the last RTP packet from this source was received + lastRtcpPacketTime, // time the last RTCP packet was received. + initialDataTime, + lastRtcpSrTime int64 // time the last RTCP SR was received. Required for DLSR computation. + + // Data used to compute outgoing RR reports. + packetCount, // number of packets received from this source. + octetCount, // number of octets received from this source. + extendedMaxSeqNum, + lastPacketTransitTime, + initialDataTimestamp, + cumulativePacketLost uint32 + maxSeqNum uint16 // the highest sequence number seen from this source + fractionLost uint8 + // for interarrivel jitter computation + // interarrival jitter of packets from this source. + jitter uint32 + + // this flag assures we only call one gotHello and one gotGoodbye for this src. + flag bool + // for source validation: + probation int // packets in sequence before valid. + baseSeqNum uint16 + expectedPrior, + receivedPrior, + badSeqNum, + seqNumAccum uint32 } // SenderInfoData stores the counters if used for an output stream, stores the received sender info data for an input stream. type SenderInfoData struct { - NtpTime int64 - RtpTimestamp, - SenderPacketCnt, - SenderOctectCnt uint32 + NtpTime int64 + RtpTimestamp, + SenderPacketCnt, + SenderOctectCnt uint32 } type RecvReportData struct { - FracLost uint8 - PacketsLost, - HighestSeqNo, - Jitter, - LastSr, - Dlsr uint32 + FracLost uint8 + PacketsLost, + HighestSeqNo, + Jitter, + LastSr, + Dlsr uint32 } type SsrcStream struct { - streamType int - streamStatus int - streamMutex sync.Mutex - Address // Own if it is an output stream, remote address in case of input stream - SenderInfoData // Sender reports if this is an input stream, read only. - RecvReportData // Receiver reports if this is an output stream, read anly. - SdesItems SdesItemMap // SDES item map, indexed by the RTCP SDES item types constants. - // Read only, to set item use SetSdesItem() - sdesChunkLen int // pre-computed SDES chunk length - updated when setting a new name, relevant for output streams - - // the following two field are active for input streams ony - prevConflictAddr *Address - statistics ctrlStatistics - - // The following two field are active for ouput streams only - initialTime int64 - initialStamp uint32 - - sequenceNumber uint16 - ssrc uint32 - payloadType byte - sender bool // true if this source (ouput or input) was identified as active sender - - // For input streams: true if RTP packet seen after last RR - dataAfterLastReport bool + streamType int + streamStatus int + streamMutex sync.Mutex + Address // Own if it is an output stream, remote address in case of input stream + SenderInfoData // Sender reports if this is an input stream, read only. + RecvReportData // Receiver reports if this is an output stream, read anly. + SdesItems SdesItemMap // SDES item map, indexed by the RTCP SDES item types constants. + // Read only, to set item use SetSdesItem() + sdesChunkLen int // pre-computed SDES chunk length - updated when setting a new name, relevant for output streams + + // the following two field are active for input streams ony + prevConflictAddr *Address + statistics ctrlStatistics + + // The following two field are active for ouput streams only + initialTime int64 + initialStamp uint32 + + sequenceNumber uint16 + ssrc uint32 + payloadType byte + sender bool // true if this source (ouput or input) was identified as active sender + + // For input streams: true if RTP packet seen after last RR + dataAfterLastReport bool } const defaultCname = "GoRTP1.0.0@somewhere" const seqNumMod = (1 << 16) -/* +/* * ***************************************************************** * SsrcStream functions, valid for output and input streams * ***************************************************************** @@ -135,12 +135,12 @@ const seqNumMod = (1 << 16) // Ssrc returns the SSRC of this stream in host order. func (str *SsrcStream) Ssrc() uint32 { - return str.ssrc + return str.ssrc } // SequenceNo returns the current RTP packet sequence number of this stream in host order. func (str *SsrcStream) SequenceNo() uint16 { - return str.sequenceNumber + return str.sequenceNumber } // SetPayloadType sets the payload type of this stream. @@ -156,51 +156,50 @@ func (str *SsrcStream) SequenceNo() uint16 { // pt - the payload type number. // func (str *SsrcStream) SetPayloadType(pt byte) (ok bool) { - if _, ok = PayloadFormatMap[int(pt)]; !ok { - return - } - str.payloadType = pt - return + if _, ok = PayloadFormatMap[int(pt)]; !ok { + return + } + str.payloadType = pt + return } // PayloadType returns the payload type of this stream. func (str *SsrcStream) PayloadType() byte { - return str.payloadType + return str.payloadType } - // StreamType returns stream's type, either input stream or otput stream. // func (str *SsrcStream) StreamType() int { - return str.streamType + return str.streamType } -/* +/* * ***************************************************************** * Processing for output streams * ***************************************************************** */ func newSsrcStreamOut(own *Address, ssrc uint32, sequenceNo uint16) (so *SsrcStream) { - so = new(SsrcStream) - so.streamType = OutputStream - so.ssrc = ssrc - if ssrc == 0 { - so.newSsrc() - } - so.sequenceNumber = sequenceNo - if sequenceNo == 0 { - so.newSequence() - } - so.IpAddr = own.IpAddr - so.DataPort = own.DataPort - so.CtrlPort = own.CtrlPort - so.payloadType = 0xff // initialize to illegal payload type - so.initialTime = time.Now().UnixNano() - so.newInitialTimestamp() - so.SdesItems = make(SdesItemMap, 2) - so.SetSdesItem(SdesCname, defaultCname) - return + so = new(SsrcStream) + so.streamType = OutputStream + so.ssrc = ssrc + if ssrc == 0 { + so.newSsrc() + } + so.sequenceNumber = sequenceNo + if sequenceNo == 0 { + so.newSequence() + } + so.IpAddr = own.IpAddr + so.DataPort = own.DataPort + so.CtrlPort = own.CtrlPort + so.payloadType = 0xff // initialize to illegal payload type + so.initialTime = time.Now().UnixNano() + so.newInitialTimestamp() + so.SdesItems = make(SdesItemMap, 2) + so.SetSdesItem(SdesCname, defaultCname) + return } // newDataPacket creates a new RTP packet suitable for use with the output stream. @@ -209,364 +208,364 @@ func newSsrcStreamOut(own *Address, ssrc uint32, sequenceNo uint16) (so *SsrcStr // number, the updated timestamp, and payload type if payload type was set in the stream. // // The application computes the next stamp based on the payload's frequency. The stamp usually -// advances by the number of samples contained in the RTP packet. +// advances by the number of samples contained in the RTP packet. // // For example PCMU with a 8000Hz frequency sends 160 samples every 20m - thus the timestamp -// must adavance by 160 for each following packet. For fixed codecs, for example PCMU, the +// must adavance by 160 for each following packet. For fixed codecs, for example PCMU, the // number of samples correspond to the payload length. For variable codecs the number of samples // has no direct relationship with the payload length. // -// stamp - the RTP timestamp for this packet. +// stamp - the RTP timestamp for this packet. // func (str *SsrcStream) newDataPacket(stamp uint32) (rp *DataPacket) { - rp = newDataPacket() - rp.SetSsrc(str.ssrc) - rp.SetPayloadType(str.payloadType) - rp.SetTimestamp(stamp + str.initialStamp) - rp.SetSequence(str.sequenceNumber) - str.sequenceNumber++ - return + rp = newDataPacket() + rp.SetSsrc(str.ssrc) + rp.SetPayloadType(str.payloadType) + rp.SetTimestamp(stamp + str.initialStamp) + rp.SetSequence(str.sequenceNumber) + str.sequenceNumber++ + return } // newCtrlPacket creates a new RTCP packet suitable for use with the specified output stream. // // This method returns an initialized RTCP packet that contains the correct SSRC, and the RTCP packet // type. In addition the method returns the offset to the next position inside the buffer. -// +// // // streamindex - the index of the output stream as returned by NewSsrcStreamOut // stamp - the RTP timestamp for this packet. // func (str *SsrcStream) newCtrlPacket(pktType int) (rp *CtrlPacket, offset int) { - rp, offset = newCtrlPacket() - rp.SetType(0, pktType) - rp.SetSsrc(0, str.ssrc) - return + rp, offset = newCtrlPacket() + rp.SetType(0, pktType) + rp.SetSsrc(0, str.ssrc) + return } // AddHeaderCtrl adds a new fixed RTCP header word into the compound, does not set SSRC after fixed header field func (str *SsrcStream) addCtrlHeader(rp *CtrlPacket, offset, pktType int) (newOffset int) { - newOffset = rp.addHeaderCtrl(offset) - rp.SetType(offset, pktType) - return + newOffset = rp.addHeaderCtrl(offset) + rp.SetType(offset, pktType) + return } // newSsrc generates a random SSRC and sets it in stream func (str *SsrcStream) newSsrc() { - var randBuf [4]byte - rand.Read(randBuf[:]) - ssrc := uint32(randBuf[0]) - ssrc |= uint32(randBuf[1]) << 8 - ssrc |= uint32(randBuf[2]) << 16 - ssrc |= uint32(randBuf[3]) << 24 - str.ssrc = ssrc + var randBuf [4]byte + rand.Read(randBuf[:]) + ssrc := uint32(randBuf[0]) + ssrc |= uint32(randBuf[1]) << 8 + ssrc |= uint32(randBuf[2]) << 16 + ssrc |= uint32(randBuf[3]) << 24 + str.ssrc = ssrc } // newInitialTimestamp creates a random initiali timestamp for outgoing packets func (str *SsrcStream) newInitialTimestamp() { - var randBuf [4]byte - rand.Read(randBuf[:]) - tmp := uint32(randBuf[0]) - tmp |= uint32(randBuf[1]) << 8 - tmp |= uint32(randBuf[2]) << 16 - tmp |= uint32(randBuf[3]) << 24 - str.initialStamp = (tmp & 0xFFFFFFF) + var randBuf [4]byte + rand.Read(randBuf[:]) + tmp := uint32(randBuf[0]) + tmp |= uint32(randBuf[1]) << 8 + tmp |= uint32(randBuf[2]) << 16 + tmp |= uint32(randBuf[3]) << 24 + str.initialStamp = (tmp & 0xFFFFFFF) } // newSequence generates a random sequence and sets it in stream func (str *SsrcStream) newSequence() { - var randBuf [2]byte - rand.Read(randBuf[:]) - sequenceNo := uint16(randBuf[0]) - sequenceNo |= uint16(randBuf[1]) << 8 - sequenceNo &= 0xEFFF - str.sequenceNumber = sequenceNo + var randBuf [2]byte + rand.Read(randBuf[:]) + sequenceNo := uint16(randBuf[0]) + sequenceNo |= uint16(randBuf[1]) << 8 + sequenceNo &= 0xEFFF + str.sequenceNumber = sequenceNo } // readRecvReport reads data from receive report and fills it into output stream RecvReportData structure func (so *SsrcStream) readRecvReport(report recvReport) { - so.FracLost = report.packetsLostFrac() - so.PacketsLost = report.packetsLost() - so.HighestSeqNo = report.highestSeq() - so.Jitter = report.jitter() - so.LastSr = report.lsr() - so.Dlsr = report.dlsr() + so.FracLost = report.packetsLostFrac() + so.PacketsLost = report.packetsLost() + so.HighestSeqNo = report.highestSeq() + so.Jitter = report.jitter() + so.LastSr = report.lsr() + so.Dlsr = report.dlsr() } // fillSenderInfo fills in the senderInfo. func (so *SsrcStream) fillSenderInfo(info senderInfo) { - info.setOctetCount(so.SenderOctectCnt) - info.setPacketCount(so.SenderPacketCnt) - tm := time.Now().UnixNano() - sec, frac := toNtpStamp(tm) - info.setNtpTimeStamp(sec, frac) - - tm1 := uint32(tm-so.initialTime) / 1e6 // time since session creation in ms - tm1 *= uint32(PayloadFormatMap[int(so.payloadType)].ClockRate / 1e3) // compute number of samples - tm1 += so.initialStamp - info.setRtpTimeStamp(tm1) + info.setOctetCount(so.SenderOctectCnt) + info.setPacketCount(so.SenderPacketCnt) + tm := time.Now().UnixNano() + sec, frac := toNtpStamp(tm) + info.setNtpTimeStamp(sec, frac) + + tm1 := uint32(tm-so.initialTime) / 1e6 // time since session creation in ms + tm1 *= uint32(PayloadFormatMap[int(so.payloadType)].ClockRate / 1e3) // compute number of samples + tm1 += so.initialStamp + info.setRtpTimeStamp(tm1) } // makeSdesChunk creates an SDES chunk at the current inUse position and returns offset that points after the chunk. func (so *SsrcStream) makeSdesChunk(rc *CtrlPacket) (newOffset int) { - chunk, newOffset := rc.newSdesChunk(so.sdesChunkLen) - copy(chunk, nullArray[:]) // fill with zeros before using - chunk.setSsrc(so.ssrc) - itemOffset := 4 - for itemType, name := range so.SdesItems { - itemOffset += chunk.setItemData(itemOffset, byte(itemType), name) - } - return + chunk, newOffset := rc.newSdesChunk(so.sdesChunkLen) + copy(chunk, nullArray[:]) // fill with zeros before using + chunk.setSsrc(so.ssrc) + itemOffset := 4 + for itemType, name := range so.SdesItems { + itemOffset += chunk.setItemData(itemOffset, byte(itemType), name) + } + return } // SetSdesItem set a new SDES item or overwrites an existing one with new text (string). // An application shall set at least a CNAME SDES item text otherwise w use a default string. func (so *SsrcStream) SetSdesItem(itemType int, itemText string) bool { - if itemType <= SdesEnd || itemType >= sdesMax { - return false - } - so.SdesItems[itemType] = itemText - length := 4 // Initialize with SSRC length - for _, name := range so.SdesItems { - length += 2 + len(name) // add length of each item - } - if rem := length & 0x3; rem == 0 { // if already multiple of 4 add another 4 that holds "end" marker byte plus 3 bytes padding - length += 4 - } else { - length += 4 - rem - } - so.sdesChunkLen = length - return true + if itemType <= SdesEnd || itemType >= sdesMax { + return false + } + so.SdesItems[itemType] = itemText + length := 4 // Initialize with SSRC length + for _, name := range so.SdesItems { + length += 2 + len(name) // add length of each item + } + if rem := length & 0x3; rem == 0 { // if already multiple of 4 add another 4 that holds "end" marker byte plus 3 bytes padding + length += 4 + } else { + length += 4 - rem + } + so.sdesChunkLen = length + return true } // makeByeData creates a by data block after the BYE RTCP header field. // Currently only one SSRC for bye data supported. Additional CSRCs requiere addiitonal data structure in output stream. func (so *SsrcStream) makeByeData(rc *CtrlPacket, reason string) (newOffset int) { - length := 4 - if len(reason) > 0 { - length += (len(reason) + 3 + 1) & ^3 // plus one is the length field - } - bye, newOffset := rc.newByeData(length) - bye.setSsrc(0, so.ssrc) - if len(reason) > 0 { - bye.setReason(reason, 1) - } - return + length := 4 + if len(reason) > 0 { + length += (len(reason) + 3 + 1) & ^3 // plus one is the length field + } + bye, newOffset := rc.newByeData(length) + bye.setSsrc(0, so.ssrc) + if len(reason) > 0 { + bye.setReason(reason, 1) + } + return } -/* +/* * ***************************************************************** * Processing for input streams * ***************************************************************** */ -// newSsrcStreamIn creates a new input stream and sets the variables required for +// newSsrcStreamIn creates a new input stream and sets the variables required for // RTP and RTCP processing to well defined initial values. func newSsrcStreamIn(from *Address, ssrc uint32) (si *SsrcStream) { - si = new(SsrcStream) - si.streamType = InputStream - si.ssrc = ssrc - si.IpAddr = from.IpAddr - si.DataPort = from.DataPort - si.CtrlPort = from.CtrlPort - si.SdesItems = make(SdesItemMap, 2) - si.initStats() - return + si = new(SsrcStream) + si.streamType = InputStream + si.ssrc = ssrc + si.IpAddr = from.IpAddr + si.DataPort = from.DataPort + si.CtrlPort = from.CtrlPort + si.SdesItems = make(SdesItemMap, 2) + si.initStats() + return } // checkSsrcIncomingData checks for collision or loops on incoming data packets. -// Implements th algorithm found in chap 8.2 in RFC 3550 +// Implements th algorithm found in chap 8.2 in RFC 3550 func (si *SsrcStream) checkSsrcIncomingData(existingStream bool, rs *Session, rp *DataPacket) (result bool) { - result = true - - // Test if the source is new and its SSRC is not already used in an output stream. - // Thus a new input stream without collision. - if !existingStream && !rs.isOutputSsrc(si.ssrc) { - return result - } - - // Found an existing input stream. Check if it is still same address/port. - // if yes, no conflicts, no further checks required. - if si.DataPort != rp.fromAddr.DataPort || !si.IpAddr.Equal(rp.fromAddr.IpAddr) { - // SSRC collision or a loop has happened - strOut, _, localSsrc := rs.lookupSsrcMapOut(si.ssrc) - if !localSsrc { // Not a SSRC in use for own output (local SSRC) - // TODO: Optional error counter: Known SSRC stream changed address or port - // Note this differs from the default in the RFC. Discard packet only when the collision is - // repeating (to avoid flip-flopping) - if si.prevConflictAddr != nil && - si.prevConflictAddr.IpAddr.Equal(rp.fromAddr.IpAddr) && - si.prevConflictAddr.DataPort == rp.fromAddr.DataPort { - result = false // discard packet and do not flip-flop - } else { - // Record who has collided so that in the future we can know if the collision repeats. - si.prevConflictAddr = &Address{rp.fromAddr.IpAddr, rp.fromAddr.DataPort, 0} - // Change sync source transport address - si.IpAddr = rp.fromAddr.IpAddr - si.DataPort = rp.fromAddr.DataPort - } - } else { - // Collision or loop of own packets. In this case si was found in ouput stream map, - // thus cannot be in input stream map - if rs.checkConflictData(&rp.fromAddr) { - // Optional error counter. - result = false - } else { - // New collision - // dispatch a BYE for a new confilicting output stream, using old SSRC - // renew the output stream's SSRC - rs.WriteCtrl(rs.buildRtcpByePkt(strOut, "SSRC collision detected when receiving RTCP packet.")) - rs.replaceStream(strOut) - si.IpAddr = rp.fromAddr.IpAddr - si.DataPort = rp.fromAddr.DataPort - si.CtrlPort = 0 - si.initStats() - } - } - } - return + result = true + + // Test if the source is new and its SSRC is not already used in an output stream. + // Thus a new input stream without collision. + if !existingStream && !rs.isOutputSsrc(si.ssrc) { + return result + } + + // Found an existing input stream. Check if it is still same address/port. + // if yes, no conflicts, no further checks required. + if si.DataPort != rp.fromAddr.DataPort || !si.IpAddr.Equal(rp.fromAddr.IpAddr) { + // SSRC collision or a loop has happened + strOut, _, localSsrc := rs.lookupSsrcMapOut(si.ssrc) + if !localSsrc { // Not a SSRC in use for own output (local SSRC) + // TODO: Optional error counter: Known SSRC stream changed address or port + // Note this differs from the default in the RFC. Discard packet only when the collision is + // repeating (to avoid flip-flopping) + if si.prevConflictAddr != nil && + si.prevConflictAddr.IpAddr.Equal(rp.fromAddr.IpAddr) && + si.prevConflictAddr.DataPort == rp.fromAddr.DataPort { + result = false // discard packet and do not flip-flop + } else { + // Record who has collided so that in the future we can know if the collision repeats. + si.prevConflictAddr = &Address{rp.fromAddr.IpAddr, rp.fromAddr.DataPort, 0} + // Change sync source transport address + si.IpAddr = rp.fromAddr.IpAddr + si.DataPort = rp.fromAddr.DataPort + } + } else { + // Collision or loop of own packets. In this case si was found in ouput stream map, + // thus cannot be in input stream map + if rs.checkConflictData(&rp.fromAddr) { + // Optional error counter. + result = false + } else { + // New collision + // dispatch a BYE for a new confilicting output stream, using old SSRC + // renew the output stream's SSRC + rs.WriteCtrl(rs.buildRtcpByePkt(strOut, "SSRC collision detected when receiving RTCP packet.")) + rs.replaceStream(strOut) + si.IpAddr = rp.fromAddr.IpAddr + si.DataPort = rp.fromAddr.DataPort + si.CtrlPort = 0 + si.initStats() + } + } + } + return } // recordReceptionData checks validity (probation), sequence numbers, computes jitter, and records the statistics for incoming data packets. // See algorithms in chapter A.1 (sequence number handling) and A.8 (jitter computation) func (si *SsrcStream) recordReceptionData(rp *DataPacket, rs *Session, recvTime int64) (result bool) { - result = true - - seq := rp.Sequence() - - if si.statistics.probation != 0 { - // source is not yet valid. - if seq == si.statistics.maxSeqNum+1 { - // packet in sequence. - si.statistics.probation-- - if si.statistics.probation == 0 { - si.statistics.seqNumAccum = 0 - } else { - result = false - } - } else { - // packet not in sequence. - si.statistics.probation = minSequential - 1 - result = false - } - si.statistics.maxSeqNum = seq - } else { - // source was already valid. - step := seq - si.statistics.maxSeqNum - if step < maxDropout { - // Ordered, with not too high step. - if seq < si.statistics.maxSeqNum { - // sequene number wrapped. - si.statistics.seqNumAccum += seqNumMod - } - si.statistics.maxSeqNum = seq - } else if step <= (seqNumMod - maxMisorder) { - // too high step of the sequence number. - // TODO: check usage of baseSeqNum - if uint32(seq) == si.statistics.badSeqNum { - // Here we saw two sequential packets - assume other side restarted, so just re-sync - // and treat this packet as first packet - si.statistics.maxSeqNum = seq - si.statistics.baseSeqNum = seq - si.statistics.seqNumAccum = 0 - si.statistics.badSeqNum = seqNumMod + 1 - } else { - si.statistics.badSeqNum = uint32((seq + 1) & (seqNumMod - 1)) - // This additional check avoids that the very first packet from a source be discarded. - if si.statistics.packetCount > 0 { - result = false - } else { - si.statistics.maxSeqNum = seq - } - } - } else { - // duplicate or reordered packet - } - } - - if result { - si.sequenceNumber = si.statistics.maxSeqNum - // the packet is considered valid. - si.statistics.packetCount++ - si.statistics.octetCount += uint32(len(rp.Payload())) - if si.statistics.packetCount == 1 { - si.statistics.initialDataTimestamp = rp.Timestamp() - si.statistics.baseSeqNum = seq - } - si.streamMutex.Lock() - si.statistics.lastPacketTime = recvTime - if !si.sender && rs.rtcpCtrlChan != nil { - rs.rtcpCtrlChan <- rtcpIncrementSender - } - si.sender = true // Stream is sender. If it was false new stream or no RTP packets for some time - si.dataAfterLastReport = true - si.streamMutex.Unlock() - - // compute the interarrival jitter estimation. - pt := int(rp.PayloadType()) - // compute lastPacketTime to ms and clockrate as kHz - arrival := uint32(si.statistics.lastPacketTime / 1e6 * int64(PayloadFormatMap[pt].ClockRate/1e3)) - transitTime := arrival - rp.Timestamp() - if si.statistics.lastPacketTransitTime != 0 { - delta := int32(transitTime - si.statistics.lastPacketTransitTime) - if delta < 0 { - delta = -delta - } - si.statistics.jitter += uint32(delta) - ((si.statistics.jitter + 8) >> 4) - } - si.statistics.lastPacketTransitTime = transitTime - } - return + result = true + + seq := rp.Sequence() + + if si.statistics.probation != 0 { + // source is not yet valid. + if seq == si.statistics.maxSeqNum+1 { + // packet in sequence. + si.statistics.probation-- + if si.statistics.probation == 0 { + si.statistics.seqNumAccum = 0 + } else { + result = false + } + } else { + // packet not in sequence. + si.statistics.probation = minSequential - 1 + result = false + } + si.statistics.maxSeqNum = seq + } else { + // source was already valid. + step := seq - si.statistics.maxSeqNum + if step < maxDropout { + // Ordered, with not too high step. + if seq < si.statistics.maxSeqNum { + // sequene number wrapped. + si.statistics.seqNumAccum += seqNumMod + } + si.statistics.maxSeqNum = seq + } else if step <= (seqNumMod - maxMisorder) { + // too high step of the sequence number. + // TODO: check usage of baseSeqNum + if uint32(seq) == si.statistics.badSeqNum { + // Here we saw two sequential packets - assume other side restarted, so just re-sync + // and treat this packet as first packet + si.statistics.maxSeqNum = seq + si.statistics.baseSeqNum = seq + si.statistics.seqNumAccum = 0 + si.statistics.badSeqNum = seqNumMod + 1 + } else { + si.statistics.badSeqNum = uint32((seq + 1) & (seqNumMod - 1)) + // This additional check avoids that the very first packet from a source be discarded. + if si.statistics.packetCount > 0 { + result = false + } else { + si.statistics.maxSeqNum = seq + } + } + } else { + // duplicate or reordered packet + } + } + + if result { + si.sequenceNumber = si.statistics.maxSeqNum + // the packet is considered valid. + si.statistics.packetCount++ + si.statistics.octetCount += uint32(len(rp.Payload())) + if si.statistics.packetCount == 1 { + si.statistics.initialDataTimestamp = rp.Timestamp() + si.statistics.baseSeqNum = seq + } + si.streamMutex.Lock() + si.statistics.lastPacketTime = recvTime + if !si.sender && rs.rtcpCtrlChan != nil { + rs.rtcpCtrlChan <- rtcpIncrementSender + } + si.sender = true // Stream is sender. If it was false new stream or no RTP packets for some time + si.dataAfterLastReport = true + si.streamMutex.Unlock() + + // compute the interarrival jitter estimation. + pt := int(rp.PayloadType()) + // compute lastPacketTime to ms and clockrate as kHz + arrival := uint32(si.statistics.lastPacketTime / 1e6 * int64(PayloadFormatMap[pt].ClockRate/1e3)) + transitTime := arrival - rp.Timestamp() + if si.statistics.lastPacketTransitTime != 0 { + delta := int32(transitTime - si.statistics.lastPacketTransitTime) + if delta < 0 { + delta = -delta + } + si.statistics.jitter += uint32(delta) - ((si.statistics.jitter + 8) >> 4) + } + si.statistics.lastPacketTransitTime = transitTime + } + return } // checkSsrcIncomingData checks for collision or loops on incoming data packets. -// Implements th algorithm found in chap 8.2 in RFC 3550 +// Implements th algorithm found in chap 8.2 in RFC 3550 func (si *SsrcStream) checkSsrcIncomingCtrl(existingStream bool, rs *Session, from *Address) (result bool) { - result = true - - // Test if the source is new and its SSRC is not already used in an output stream. - // Thus a new input stream without collision. - if !existingStream && !rs.isOutputSsrc(si.ssrc) { - return result - } - // Found an existing input stream. Check if it is still same address/port. - // if yes, no conflicts, no further checks required. - if si.CtrlPort != from.CtrlPort || !si.IpAddr.Equal(from.IpAddr) { - // SSRC collision or a loop has happened - strOut, _, localSsrc := rs.lookupSsrcMapOut(si.ssrc) - if !localSsrc { // Not a SSRC in use for own output (local SSRC) - // TODO: Optional error counter: Know SSRC stream changed address or port - // Note this differs from the default in the RFC. Discard packet only when the collision is - // repeating (to avoid flip-flopping) - if si.prevConflictAddr != nil && - si.prevConflictAddr.IpAddr.Equal(from.IpAddr) && - si.prevConflictAddr.CtrlPort == from.CtrlPort { - result = false // discard packet and do not flip-flop - } else { - // Record who has collided so that in the future we can know if the collision repeats. - si.prevConflictAddr = &Address{from.IpAddr, 0, from.CtrlPort} - // Change sync source transport address - si.IpAddr = from.IpAddr - si.CtrlPort = from.CtrlPort - } - } else { - // Collision or loop of own packets. In this case strOut == si. - if rs.checkConflictCtrl(from) { - // Optional error counter. - result = false - } else { - // New collision, dispatch a BYE using old SSRC, renew the output stream's SSRC - rs.WriteCtrl(rs.buildRtcpByePkt(strOut, "SSRC collision detected when receiving RTCP packet.")) - rs.replaceStream(strOut) - si.IpAddr = from.IpAddr - si.DataPort = 0 - si.CtrlPort = from.CtrlPort - si.initStats() - } - } - } - return + result = true + + // Test if the source is new and its SSRC is not already used in an output stream. + // Thus a new input stream without collision. + if !existingStream && !rs.isOutputSsrc(si.ssrc) { + return result + } + // Found an existing input stream. Check if it is still same address/port. + // if yes, no conflicts, no further checks required. + if si.CtrlPort != from.CtrlPort || !si.IpAddr.Equal(from.IpAddr) { + // SSRC collision or a loop has happened + strOut, _, localSsrc := rs.lookupSsrcMapOut(si.ssrc) + if !localSsrc { // Not a SSRC in use for own output (local SSRC) + // TODO: Optional error counter: Know SSRC stream changed address or port + // Note this differs from the default in the RFC. Discard packet only when the collision is + // repeating (to avoid flip-flopping) + if si.prevConflictAddr != nil && + si.prevConflictAddr.IpAddr.Equal(from.IpAddr) && + si.prevConflictAddr.CtrlPort == from.CtrlPort { + result = false // discard packet and do not flip-flop + } else { + // Record who has collided so that in the future we can know if the collision repeats. + si.prevConflictAddr = &Address{from.IpAddr, 0, from.CtrlPort} + // Change sync source transport address + si.IpAddr = from.IpAddr + si.CtrlPort = from.CtrlPort + } + } else { + // Collision or loop of own packets. In this case strOut == si. + if rs.checkConflictCtrl(from) { + // Optional error counter. + result = false + } else { + // New collision, dispatch a BYE using old SSRC, renew the output stream's SSRC + rs.WriteCtrl(rs.buildRtcpByePkt(strOut, "SSRC collision detected when receiving RTCP packet.")) + rs.replaceStream(strOut) + si.IpAddr = from.IpAddr + si.DataPort = 0 + si.CtrlPort = from.CtrlPort + si.initStats() + } + } + } + return } // makeRecvReport fills a receiver report at the current inUse position and returns offset that points after the report. @@ -574,116 +573,116 @@ func (si *SsrcStream) checkSsrcIncomingCtrl(existingStream bool, rs *Session, fr // func (si *SsrcStream) makeRecvReport(rp *CtrlPacket) (newOffset int) { - report, newOffset := rp.newRecvReport() - - extMaxSeq := si.statistics.seqNumAccum + uint32(si.statistics.maxSeqNum) - expected := extMaxSeq - uint32(si.statistics.baseSeqNum) + 1 - lost := expected - si.statistics.packetCount - if si.statistics.packetCount == 0 { - lost = 0 - } - expectedDelta := expected - si.statistics.expectedPrior - si.statistics.expectedPrior = expected - - receivedDelta := si.statistics.packetCount - si.statistics.receivedPrior - si.statistics.receivedPrior = si.statistics.packetCount - - lostDelta := expectedDelta - receivedDelta - - var fracLost byte - if expectedDelta != 0 && lostDelta > 0 { - fracLost = byte((lostDelta << 8) / expectedDelta) - } - - var lsr, dlsr uint32 - if si.statistics.lastRtcpSrTime != 0 { - sec, frac := toNtpStamp(si.statistics.lastRtcpSrTime) - ntp := (sec << 32) | frac - lsr = ntp >> 16 - sec, frac = toNtpStamp(time.Now().UnixNano() - si.statistics.lastRtcpSrTime) - ntp = (sec << 32) | frac - dlsr = ntp >> 16 - } - - report.setSsrc(si.ssrc) - report.setPacketsLost(lost) - report.setPacketsLostFrac(fracLost) - report.setHighestSeq(extMaxSeq) - report.setJitter(si.statistics.jitter >> 4) - report.setLsr(lsr) - report.setDlsr(dlsr) - - return + report, newOffset := rp.newRecvReport() + + extMaxSeq := si.statistics.seqNumAccum + uint32(si.statistics.maxSeqNum) + expected := extMaxSeq - uint32(si.statistics.baseSeqNum) + 1 + lost := expected - si.statistics.packetCount + if si.statistics.packetCount == 0 { + lost = 0 + } + expectedDelta := expected - si.statistics.expectedPrior + si.statistics.expectedPrior = expected + + receivedDelta := si.statistics.packetCount - si.statistics.receivedPrior + si.statistics.receivedPrior = si.statistics.packetCount + + lostDelta := expectedDelta - receivedDelta + + var fracLost byte + if expectedDelta != 0 && lostDelta > 0 { + fracLost = byte((lostDelta << 8) / expectedDelta) + } + + var lsr, dlsr uint32 + if si.statistics.lastRtcpSrTime != 0 { + sec, frac := toNtpStamp(si.statistics.lastRtcpSrTime) + ntp := (sec << 32) | frac + lsr = ntp >> 16 + sec, frac = toNtpStamp(time.Now().UnixNano() - si.statistics.lastRtcpSrTime) + ntp = (sec << 32) | frac + dlsr = ntp >> 16 + } + + report.setSsrc(si.ssrc) + report.setPacketsLost(lost) + report.setPacketsLostFrac(fracLost) + report.setHighestSeq(extMaxSeq) + report.setJitter(si.statistics.jitter >> 4) + report.setLsr(lsr) + report.setDlsr(dlsr) + + return } func (si *SsrcStream) readSenderInfo(info senderInfo) { - seconds, fraction := info.ntpTimeStamp() - si.NtpTime = fromNtp(seconds, fraction) - si.RtpTimestamp = info.rtpTimeStamp() - si.SenderPacketCnt = info.packetCount() - si.SenderOctectCnt = info.octetCount() + seconds, fraction := info.ntpTimeStamp() + si.NtpTime = fromNtp(seconds, fraction) + si.RtpTimestamp = info.rtpTimeStamp() + si.SenderPacketCnt = info.packetCount() + si.SenderOctectCnt = info.octetCount() } // goodBye marks this source as having sent a BYE control packet. func (si *SsrcStream) goodbye() bool { - if !si.statistics.flag { - return false - } - si.statistics.flag = false - return true + if !si.statistics.flag { + return false + } + si.statistics.flag = false + return true } // hello marks this source as having sent some packet. func (si *SsrcStream) hello() bool { - if si.statistics.flag { - return false - } - si.statistics.flag = true - return true + if si.statistics.flag { + return false + } + si.statistics.flag = true + return true } -// initStats initializes all RTCP statistic counters and other relevant data. +// initStats initializes all RTCP statistic counters and other relevant data. func (si *SsrcStream) initStats() { - si.statistics.lastPacketTime = 0 - si.statistics.lastRtcpPacketTime = 0 - si.statistics.lastRtcpSrTime = 0 - - si.statistics.packetCount = 0 - si.statistics.octetCount = 0 - si.statistics.maxSeqNum = 0 - si.statistics.extendedMaxSeqNum = 0 - si.statistics.cumulativePacketLost = 0 - si.statistics.fractionLost = 0 - si.statistics.jitter = 0 - si.statistics.initialDataTimestamp = 0 - si.statistics.initialDataTime = 0 - si.statistics.flag = false - - si.statistics.badSeqNum = seqNumMod + 1 - si.statistics.probation = minSequential - si.statistics.baseSeqNum = 0 - si.statistics.expectedPrior = 0 - si.statistics.receivedPrior = 0 - si.statistics.seqNumAccum = 0 + si.statistics.lastPacketTime = 0 + si.statistics.lastRtcpPacketTime = 0 + si.statistics.lastRtcpSrTime = 0 + + si.statistics.packetCount = 0 + si.statistics.octetCount = 0 + si.statistics.maxSeqNum = 0 + si.statistics.extendedMaxSeqNum = 0 + si.statistics.cumulativePacketLost = 0 + si.statistics.fractionLost = 0 + si.statistics.jitter = 0 + si.statistics.initialDataTimestamp = 0 + si.statistics.initialDataTime = 0 + si.statistics.flag = false + + si.statistics.badSeqNum = seqNumMod + 1 + si.statistics.probation = minSequential + si.statistics.baseSeqNum = 0 + si.statistics.expectedPrior = 0 + si.statistics.receivedPrior = 0 + si.statistics.seqNumAccum = 0 } func (si *SsrcStream) parseSdesChunk(sc sdesChunk) { - offset := 4 // points after SSRC field of this chunk - - for { - itemType := sc.getItemType(offset) - if itemType <= SdesEnd || itemType >= sdesMax { - return - } - - txtLen := sc.getItemLen(offset) - itemTxt := sc.getItemText(offset, txtLen) - offset += 2 + txtLen - if name, ok := si.SdesItems[itemType]; ok && name == itemTxt { - continue - } - txt := make([]byte, len(itemTxt)) - copy(txt, itemTxt) - si.SdesItems[itemType] = string(txt) - } + offset := 4 // points after SSRC field of this chunk + + for { + itemType := sc.getItemType(offset) + if itemType <= SdesEnd || itemType >= sdesMax { + return + } + + txtLen := sc.getItemLen(offset) + itemTxt := sc.getItemText(offset, txtLen) + offset += 2 + txtLen + if name, ok := si.SdesItems[itemType]; ok && name == itemTxt { + continue + } + txt := make([]byte, len(itemTxt)) + copy(txt, itemTxt) + si.SdesItems[itemType] = string(txt) + } } diff --git a/src/net/rtp/transport.go b/src/net/rtp/transport.go index 66ee44d..75749d3 100644 --- a/src/net/rtp/transport.go +++ b/src/net/rtp/transport.go @@ -1,43 +1,43 @@ -// Copyright (C) 2011 Werner Dittmann -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Authors: Werner Dittmann -// +// Copyright (C) 2011 Werner Dittmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Authors: Werner Dittmann +// package rtp -type TransportRecv interface { - ListenOnTransports() error - OnRecvData(rp *DataPacket) bool - OnRecvCtrl(rp *CtrlPacket) bool - SetCallUpper(upper TransportRecv) - CloseRecv() - SetEndChannel(ch TransportEnd) +type TransportRecv interface { + ListenOnTransports() error + OnRecvData(rp *DataPacket) bool + OnRecvCtrl(rp *CtrlPacket) bool + SetCallUpper(upper TransportRecv) + CloseRecv() + SetEndChannel(ch TransportEnd) } type TransportWrite interface { - WriteDataTo(rp *DataPacket, addr *Address) (n int, err error) - WriteCtrlTo(rp *CtrlPacket, addr *Address) (n int, err error) - SetToLower(lower TransportWrite) - CloseWrite() + WriteDataTo(rp *DataPacket, addr *Address) (n int, err error) + WriteCtrlTo(rp *CtrlPacket, addr *Address) (n int, err error) + SetToLower(lower TransportWrite) + CloseWrite() } type TransportCommon struct { - transportEnd TransportEnd - dataRecvStop, - ctrlRecvStop, - dataWriteStop, - ctrlWriteStop bool + transportEnd TransportEnd + dataRecvStop, + ctrlRecvStop, + dataWriteStop, + ctrlWriteStop bool } diff --git a/src/net/rtp/transportUDP.go b/src/net/rtp/transportUDP.go index 569a911..2852e19 100644 --- a/src/net/rtp/transportUDP.go +++ b/src/net/rtp/transportUDP.go @@ -1,15 +1,15 @@ // Copyright (C) 2011 Werner Dittmann -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . // @@ -19,59 +19,59 @@ package rtp import ( - "fmt" - "net" - "time" + "fmt" + "net" + "time" ) // RtpTransportUDP implements the interfaces RtpTransportRecv and RtpTransportWrite for RTP transports. type TransportUDP struct { - TransportCommon - callUpper TransportRecv - toLower TransportWrite - dataConn, ctrlConn *net.UDPConn - localAddrRtp, localAddrRtcp *net.UDPAddr + TransportCommon + callUpper TransportRecv + toLower TransportWrite + dataConn, ctrlConn *net.UDPConn + localAddrRtp, localAddrRtcp *net.UDPAddr } // NewRtpTransportUDP creates a new RTP transport for UPD. // // addr - The UPD socket's local IP address -// +// // port - The port number of the RTP data port. This must be an even port number. -// The following odd port number is the control (RTCP) port. +// The following odd port number is the control (RTCP) port. // func NewTransportUDP(addr *net.IPAddr, port int) (*TransportUDP, error) { - tp := new(TransportUDP) - tp.callUpper = tp - tp.localAddrRtp = &net.UDPAddr{addr.IP, port} - tp.localAddrRtcp = &net.UDPAddr{addr.IP, port + 1} - return tp, nil + tp := new(TransportUDP) + tp.callUpper = tp + tp.localAddrRtp = &net.UDPAddr{IP: addr.IP, Port: port} + tp.localAddrRtcp = &net.UDPAddr{IP: addr.IP, Port: port + 1} + return tp, nil } // ListenOnTransports listens for incoming RTP and RTCP packets addressed -// to this transport. +// to this transport. // func (tp *TransportUDP) ListenOnTransports() (err error) { - tp.dataConn, err = net.ListenUDP(tp.localAddrRtp.Network(), tp.localAddrRtp) - if err != nil { - return - } - tp.ctrlConn, err = net.ListenUDP(tp.localAddrRtcp.Network(), tp.localAddrRtcp) - if err != nil { - tp.dataConn.Close() - tp.dataConn = nil - return - } - go tp.readDataPacket() - go tp.readCtrlPacket() - return nil + tp.dataConn, err = net.ListenUDP(tp.localAddrRtp.Network(), tp.localAddrRtp) + if err != nil { + return + } + tp.ctrlConn, err = net.ListenUDP(tp.localAddrRtcp.Network(), tp.localAddrRtcp) + if err != nil { + tp.dataConn.Close() + tp.dataConn = nil + return + } + go tp.readDataPacket() + go tp.readCtrlPacket() + return nil } // *** The following methods implement the rtp.TransportRecv interface. // SetCallUpper implements the rtp.TransportRecv SetCallUpper method. func (tp *TransportUDP) SetCallUpper(upper TransportRecv) { - tp.callUpper = upper + tp.callUpper = upper } // OnRecvRtp implements the rtp.TransportRecv OnRecvRtp method. @@ -79,8 +79,8 @@ func (tp *TransportUDP) SetCallUpper(upper TransportRecv) { // TransportUDP does not implement any processing because it is the lowest // layer and expects an upper layer to receive data. func (tp *TransportUDP) OnRecvData(rp *DataPacket) bool { - fmt.Printf("TransportUDP: no registered upper layer RTP packet handler\n") - return false + fmt.Printf("TransportUDP: no registered upper layer RTP packet handler\n") + return false } // OnRecvRtcp implements the rtp.TransportRecv OnRecvRtcp method. @@ -88,30 +88,30 @@ func (tp *TransportUDP) OnRecvData(rp *DataPacket) bool { // TransportUDP does not implement any processing because it is the lowest // layer and expects an upper layer to receive data. func (tp *TransportUDP) OnRecvCtrl(rp *CtrlPacket) bool { - fmt.Printf("TransportUDP: no registered upper layer RTCP packet handler\n") - return false + fmt.Printf("TransportUDP: no registered upper layer RTCP packet handler\n") + return false } // CloseRecv implements the rtp.TransportRecv CloseRecv method. func (tp *TransportUDP) CloseRecv() { - // - // The correct way to do it is to close the UDP connection after setting the - // stop flags to true. However, until issue 2116 is solved just set the flags - // and rely on the read timeout in the read packet functions - // - tp.dataRecvStop = true - tp.ctrlRecvStop = true - - // err := tp.rtpConn.Close() - // if err != nil { - // fmt.Printf("Close failed: %s\n", err.String()) - // } - // tp.rtcpConn.Close() + // + // The correct way to do it is to close the UDP connection after setting the + // stop flags to true. However, until issue 2116 is solved just set the flags + // and rely on the read timeout in the read packet functions + // + tp.dataRecvStop = true + tp.ctrlRecvStop = true + + // err := tp.rtpConn.Close() + // if err != nil { + // fmt.Printf("Close failed: %s\n", err.String()) + // } + // tp.rtcpConn.Close() } -// setEndChannel receives and set the channel to signal back after network socket was closed and receive loop terminated. +// setEndChannel receives and set the channel to signal back after network socket was closed and receive loop terminated. func (tp *TransportUDP) SetEndChannel(ch TransportEnd) { - tp.transportEnd = ch + tp.transportEnd = ch } // *** The following methods implement the rtp.TransportWrite interface. @@ -120,17 +120,17 @@ func (tp *TransportUDP) SetEndChannel(ch TransportEnd) { // // Usually TransportUDP is already the lowest layer. func (tp *TransportUDP) SetToLower(lower TransportWrite) { - tp.toLower = lower + tp.toLower = lower } // WriteRtpTo implements the rtp.TransportWrite WriteRtpTo method. func (tp *TransportUDP) WriteDataTo(rp *DataPacket, addr *Address) (n int, err error) { - return tp.dataConn.WriteToUDP(rp.buffer[0:rp.inUse], &net.UDPAddr{addr.IpAddr, addr.DataPort}) + return tp.dataConn.WriteToUDP(rp.buffer[0:rp.inUse], &net.UDPAddr{IP: addr.IpAddr, Port: addr.DataPort}) } // WriteRtcpTo implements the rtp.TransportWrite WriteRtcpTo method. func (tp *TransportUDP) WriteCtrlTo(rp *CtrlPacket, addr *Address) (n int, err error) { - return tp.ctrlConn.WriteToUDP(rp.buffer[0:rp.inUse], &net.UDPAddr{addr.IpAddr, addr.CtrlPort}) + return tp.ctrlConn.WriteToUDP(rp.buffer[0:rp.inUse], &net.UDPAddr{IP: addr.IpAddr, Port: addr.CtrlPort}) } // CloseWrite implements the rtp.TransportWrite CloseWrite method. @@ -148,63 +148,63 @@ func (tp *TransportUDP) CloseWrite() { // if callback is not nil func (tp *TransportUDP) readDataPacket() { - var buf [defaultBufferSize]byte - - tp.dataRecvStop = false - for { - tp.dataConn.SetReadDeadline(time.Now().Add(20 * time.Millisecond)) // 20 ms, re-test and remove after Go issue 2116 is solved - n, addr, err := tp.dataConn.ReadFromUDP(buf[0:]) - if tp.dataRecvStop { - break - } - if e, ok := err.(net.Error); ok && e.Timeout() { - continue - } - if err != nil { - break - } - rp := newDataPacket() - rp.fromAddr.IpAddr = addr.IP - rp.fromAddr.DataPort = addr.Port - rp.fromAddr.CtrlPort = 0 - rp.inUse = n - copy(rp.buffer, buf[0:n]) - - if tp.callUpper != nil { - tp.callUpper.OnRecvData(rp) - } - } - tp.dataConn.Close() - tp.transportEnd <- DataTransportRecvStopped + var buf [defaultBufferSize]byte + + tp.dataRecvStop = false + for { + tp.dataConn.SetReadDeadline(time.Now().Add(20 * time.Millisecond)) // 20 ms, re-test and remove after Go issue 2116 is solved + n, addr, err := tp.dataConn.ReadFromUDP(buf[0:]) + if tp.dataRecvStop { + break + } + if e, ok := err.(net.Error); ok && e.Timeout() { + continue + } + if err != nil { + break + } + rp := newDataPacket() + rp.fromAddr.IpAddr = addr.IP + rp.fromAddr.DataPort = addr.Port + rp.fromAddr.CtrlPort = 0 + rp.inUse = n + copy(rp.buffer, buf[0:n]) + + if tp.callUpper != nil { + tp.callUpper.OnRecvData(rp) + } + } + tp.dataConn.Close() + tp.transportEnd <- DataTransportRecvStopped } func (tp *TransportUDP) readCtrlPacket() { - var buf [defaultBufferSize]byte - - tp.ctrlRecvStop = false - for { - tp.dataConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) // 100 ms, re-test and remove after Go issue 2116 is solved - n, addr, err := tp.ctrlConn.ReadFromUDP(buf[0:]) - if tp.ctrlRecvStop { - break - } - if e, ok := err.(net.Error); ok && e.Timeout() { - continue - } - if err != nil { - break - } - rp, _ := newCtrlPacket() - rp.fromAddr.IpAddr = addr.IP - rp.fromAddr.CtrlPort = addr.Port - rp.fromAddr.DataPort = 0 - rp.inUse = n - copy(rp.buffer, buf[0:n]) - - if tp.callUpper != nil { - tp.callUpper.OnRecvCtrl(rp) - } - } - tp.ctrlConn.Close() - tp.transportEnd <- CtrlTransportRecvStopped + var buf [defaultBufferSize]byte + + tp.ctrlRecvStop = false + for { + tp.dataConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) // 100 ms, re-test and remove after Go issue 2116 is solved + n, addr, err := tp.ctrlConn.ReadFromUDP(buf[0:]) + if tp.ctrlRecvStop { + break + } + if e, ok := err.(net.Error); ok && e.Timeout() { + continue + } + if err != nil { + break + } + rp, _ := newCtrlPacket() + rp.fromAddr.IpAddr = addr.IP + rp.fromAddr.CtrlPort = addr.Port + rp.fromAddr.DataPort = 0 + rp.inUse = n + copy(rp.buffer, buf[0:n]) + + if tp.callUpper != nil { + tp.callUpper.OnRecvCtrl(rp) + } + } + tp.ctrlConn.Close() + tp.transportEnd <- CtrlTransportRecvStopped }