mirror of https://bitbucket.org/ausocean/av.git
Cleaned up & tested
Cleaned up file/folder structure and got the new and improved mpegts stuff working. Now to looking at the pes stuff.
This commit is contained in:
parent
484f21e4d3
commit
862c3a67e2
|
@ -2,6 +2,10 @@
|
|||
|
||||
av is a collection of tools and packages written in Go for audio-video processing.
|
||||
|
||||
# Authors
|
||||
Alan Noble
|
||||
Saxon A. Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
# Description
|
||||
|
||||
* revid: a tool for re-muxing and re-directing video streams.
|
||||
|
|
|
@ -7,7 +7,7 @@ DESCRIPTION
|
|||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
Saxon A. Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
@ -26,7 +26,12 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package packets
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"../tools"
|
||||
"errors"
|
||||
)
|
||||
|
||||
/*
|
||||
The below data struct encapsulates the fields of an MPEG-TS packet. Below is
|
||||
|
@ -94,59 +99,58 @@ the formatting of an MPEG-TS packet for reference!
|
|||
----------------------------------------------------------------------------
|
||||
*/
|
||||
type MpegTsPacket struct {
|
||||
TEI bool // Transport Error Indicator
|
||||
PUSI bool // Payload Unit Start Indicator
|
||||
Priority bool // Tranposrt priority indicator
|
||||
PID uint16 // Packet identifier
|
||||
TSC byte // Transport Scrambling Control
|
||||
AFC byte // Adaption Field Control
|
||||
CC byte // Continuity Counter
|
||||
AFL byte // Adaptation field length
|
||||
DI bool // Discontinouty indicator
|
||||
RAI bool // random access indicator
|
||||
ESPI bool // Elementary stream priority indicator
|
||||
PCRF bool // PCR flag
|
||||
OPCRF bool // OPCR flag
|
||||
SPF bool // Splicing point flag
|
||||
TPDF bool // Transport private data flag
|
||||
AFEF bool // Adaptation field extension flag
|
||||
PCR uint64 // Program clock reference
|
||||
OPCR uint64 // Original program clock reference
|
||||
SC byte // Splice countdown
|
||||
TPDL byte // Tranposrt private data length
|
||||
TPD []byte // Private data
|
||||
Extension []byte // Adaptation field extension
|
||||
Stuffing []byte // Stuffing bytes
|
||||
Payload []byte // Mpeg ts payload
|
||||
TEI bool // Transport Error Indicator
|
||||
PUSI bool // Payload Unit Start Indicator
|
||||
Priority bool // Tranposrt priority indicator
|
||||
PID uint16 // Packet identifier
|
||||
TSC byte // Transport Scrambling Control
|
||||
AFC byte // Adaption Field Control
|
||||
CC byte // Continuity Counter
|
||||
AFL byte // Adaptation field length
|
||||
DI bool // Discontinouty indicator
|
||||
RAI bool // random access indicator
|
||||
ESPI bool // Elementary stream priority indicator
|
||||
PCRF bool // PCR flag
|
||||
OPCRF bool // OPCR flag
|
||||
SPF bool // Splicing point flag
|
||||
TPDF bool // Transport private data flag
|
||||
AFEF bool // Adaptation field extension flag
|
||||
PCR uint64 // Program clock reference
|
||||
OPCR uint64 // Original program clock reference
|
||||
SC byte // Splice countdown
|
||||
TPDL byte // Tranposrt private data length
|
||||
TPD []byte // Private data
|
||||
Ext []byte // Adaptation field extension
|
||||
Stuff []byte // Stuffing bytes
|
||||
Payload []byte // Mpeg ts payload
|
||||
}
|
||||
|
||||
func (p *MpegTsPacket) ToByteSlice() (output []byte) {
|
||||
output = make([]byte, 188)
|
||||
output[0] = 0x47
|
||||
output[1] = boolToByte(p.TEI) << 7 | boolToByte(p.PUSI) << 6 |
|
||||
boolToByte(p.Priority) << 5 | byte((p.PID&0xFF00) >> 8)
|
||||
output[2] = byte(p.PID & 0x00FF)
|
||||
output[3] = p.TSC << 6 | p.AFC << 4 | p.CC
|
||||
output[4] = p.AFL
|
||||
output[5] = boolToByte(p.DI) << 7 | boolToByte(p.RAI) << 6 | boolToByte(p.ESPI) << 5 |
|
||||
boolToByte(p.PCRF) << 4 | boolToByte(p.OPCRF) << 3 | boolToByte(p.SPF) << 2 |
|
||||
boolToByte(TPDF) << 1 | boolToByte(AFEF)
|
||||
currentIndex := 6
|
||||
for ; p.PCRf && currentIndex < 12; currentIndex++ {
|
||||
output[currentIndex] = p.PCR >> (22 - 2*currentIndex)
|
||||
func (p *MpegTsPacket) ToByteSlice() (output []byte, err error) {
|
||||
output = append(output, []byte{
|
||||
0x47,
|
||||
(tools.BoolToByte(p.TEI)<<7 | tools.BoolToByte(p.PUSI)<<6 | tools.BoolToByte(p.Priority)<<5 |
|
||||
byte((p.PID&0xFF00)>>8)),
|
||||
byte(p.PID & 0x00FF),
|
||||
(p.TSC<<6 | p.AFC<<4 | p.CC),
|
||||
p.AFL,
|
||||
(tools.BoolToByte(p.DI)<<7 | tools.BoolToByte(p.RAI)<<6 | tools.BoolToByte(p.ESPI)<<5 | tools.BoolToByte(p.PCRF)<<4 |
|
||||
tools.BoolToByte(p.OPCRF)<<3 | tools.BoolToByte(p.SPF)<<2 | tools.BoolToByte(p.TPDF)<<1 | tools.BoolToByte(p.AFEF)),
|
||||
}...)
|
||||
for i := 40; p.PCRF && i >= 0; i-=8 {
|
||||
output = append(output, byte(p.PCR>>uint(i)))
|
||||
}
|
||||
startIndex := currentIndex
|
||||
for ; p.OPCRF && ( currentIndex < ( startIndex + 6 )); currentIndex++ {
|
||||
output[currentIndex] = p.OPCR >> (10 - 2*(currentIndex-startIndex))
|
||||
for i := 40; p.OPCRF && i >= 0; i-=8 {
|
||||
output = append(output, byte(p.OPCR>>uint(i)))
|
||||
}
|
||||
if p.SPF {
|
||||
output[(currentIndex++)-1] = p.SC
|
||||
output = append(output, p.SC)
|
||||
}
|
||||
if p.TPDF {
|
||||
output[(currentIndex++)-1] = p.TPDL
|
||||
copy(output[currentIndex:len(p.TPD)],p.TPD[:]]
|
||||
currentIndex += len(p.TPD)
|
||||
output = append(output, append([]byte{p.TPDL}, p.TPD...)...)
|
||||
}
|
||||
copy()
|
||||
|
||||
output = append(output, append(p.Ext, append(p.Stuff, p.Payload...)...)...)
|
||||
if len(output) != 188 {
|
||||
err = errors.New("Length of MPEG-TS packet is not 188! Something is wrong!")
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
_"fmt"
|
||||
)
|
||||
|
||||
// Just ensure that we can create a byte slice with a mpegts packet correctly
|
||||
func TestMpegTsToByteSlice(t *testing.T){
|
||||
payload := []byte{0x56,0xA2,0x78,0x89,0x67}
|
||||
pcr := 100000 // => 100000
|
||||
stuffing := make([]byte,171)
|
||||
for i := range stuffing {
|
||||
stuffing[i] = 0xFF
|
||||
}
|
||||
tsPkt := MpegTsPacket{
|
||||
PUSI: true,
|
||||
PID: uint16(256),
|
||||
AFC: byte(3),
|
||||
AFL: 7+171,
|
||||
CC: byte(6),
|
||||
PCRF: true,
|
||||
PCR: uint64(pcr),
|
||||
Stuff: stuffing,
|
||||
Payload: payload,
|
||||
}
|
||||
expectedOutput := []byte{ 0x47, 0x41, 0x00, 0x36, byte(178),0x10}
|
||||
for i := 40; i >= 0; i-= 8 {
|
||||
expectedOutput = append(expectedOutput,byte(pcr>>uint(i)))
|
||||
}
|
||||
for i := 0; i < 171; i++ {
|
||||
expectedOutput = append(expectedOutput, 0xFF)
|
||||
}
|
||||
expectedOutput = append(expectedOutput,payload...)
|
||||
tsPktAsByteSlice, err := tsPkt.ToByteSlice()
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got error!")
|
||||
}
|
||||
for i := 0; i < 188; i++ {
|
||||
if tsPktAsByteSlice[i] != expectedOutput[i] {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v", i, expectedOutput[i], tsPktAsByteSlice[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
NAME
|
||||
PES.go -
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
PES.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package nal
|
||||
|
||||
type NalAccessUnit struct {
|
||||
SPS []byte
|
||||
PPS []byte
|
||||
SEI [][]byte
|
||||
Data [][]byte
|
||||
}
|
||||
|
||||
func (u *NalAccessUnit) AsAnnexB() (output []byte) {
|
||||
startCode := []byte{ 0x00,0x00,0x01}
|
||||
AUD := []byte{0x09, 0xF0}
|
||||
format := [][]byte{startCode, AUD, startCode, u.SPS, startCode, u.PPS }
|
||||
for i := range format {
|
||||
output = append(output,format[i]...)
|
||||
}
|
||||
for i := range u.SEI {
|
||||
output = append(output, append(startCode,u.SEI[i]...)...)
|
||||
}
|
||||
for i := range u.Data {
|
||||
output = append(output, append(startCode,u.Data[i]...)...)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -24,7 +24,7 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package packets
|
||||
package nal
|
||||
|
||||
type NALUnit interface {
|
||||
ToByteSlice() []byte
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package nal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var parseInput = []byte{
|
||||
0x6C, // 3NalUnitBits = 101(5), Fragment type = 1100 (type = 12 )
|
||||
0x94, // starbit = 1, endbit = 0, Reservedbit = 0, 5NalUnitBits = 10100 (20)
|
||||
0x8E, // 10001110 random frame byte
|
||||
0x26, // 00100110 random frame byte
|
||||
0xD0, // 11010000 random frame byte
|
||||
}
|
||||
|
||||
var expectedParsing = []interface{}{
|
||||
byte(3),
|
||||
byte(12),
|
||||
bool(true),
|
||||
bool(false),
|
||||
bool(false),
|
||||
byte(20),
|
||||
[]byte{0x8E, 0x26, 0xD0},
|
||||
}
|
||||
|
||||
const (
|
||||
nalTestType = 12
|
||||
)
|
||||
|
||||
func TestNalFragmentParsing(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
value := reflect.ValueOf(*nalUnit)
|
||||
length := value.NumField()
|
||||
fields := make([]interface{}, length)
|
||||
for ii := 0; ii < length; ii++ {
|
||||
fields[ii] = value.Field(ii).Interface()
|
||||
}
|
||||
for ii := range fields {
|
||||
if !reflect.DeepEqual(fields[ii], expectedParsing[ii]) {
|
||||
t.Errorf("Bad Parsing! Field: %v wanted: %v got: %v\n", ii, expectedParsing[ii],
|
||||
fields[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalFragmentToByteSlice(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
output := nalUnit.ToByteSlice()
|
||||
for ii := range output {
|
||||
if output[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad conversion to byte slice at %vth byte! wanted: %v got: %v",
|
||||
parseInput[ii], output[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalFragmentType(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
nalType := nalUnit.GetType()
|
||||
if nalType != nalTestType {
|
||||
t.Errorf("Returned wrong type!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsParsing(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
for ii := range parseInput {
|
||||
if nalSpsPps.Data[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad Parsing! Byte: %v wanted: %v got: %v\n", ii, parseInput[ii],
|
||||
nalSpsPps.Data[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsToByteSlice(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
nalSpsPpsByteSlice := nalSpsPps.ToByteSlice()
|
||||
for ii := range parseInput {
|
||||
if nalSpsPpsByteSlice[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad conversion to byte slice! Byte: %v wanted: %v got: %v\n", ii,
|
||||
parseInput[ii], nalSpsPpsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsType(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
if nalSpsPps.GetType() != nalTestType {
|
||||
t.Errorf("Returned wrong type!")
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package packets
|
||||
|
||||
type NalAccessUnit struct {
|
||||
SPS []byte
|
||||
PPS []byte
|
||||
SEI [][]byte
|
||||
Data [][]byte
|
||||
}
|
||||
|
||||
func (u *NalAccessUnit) AsAnnexB() (output []byte) {
|
||||
startCode := []byte{ 0x00,0x00,0x01}
|
||||
AUD := []byte{0x09, 0xF0}
|
||||
format := [][]byte{startCode, AUD, startCode, u.SPS, startCode, u.PPS }
|
||||
for i := range format {
|
||||
output = append(output,format[i]...)
|
||||
}
|
||||
for i := range u.SEI {
|
||||
output = append(output, append(startCode,u.SEI[i]...)...)
|
||||
}
|
||||
for i := range u.Data {
|
||||
output = append(output, append(startCode,u.Data[i]...)...)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,566 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package packets
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
_"math/rand"
|
||||
|
||||
"github.com/beatgammit/rtsp"
|
||||
)
|
||||
|
||||
/*******************************************************
|
||||
Testing stuff related to connection i.e. rtsp, rtp, rtcp
|
||||
********************************************************/
|
||||
const (
|
||||
rtpPort = 17300
|
||||
rtcpPort = 17319
|
||||
rtspUrl = "rtsp://192.168.0.50:8554/CH002.sdp"
|
||||
rtpUrl = "rtsp://192.168.0.50:8554/CH002.sdp/track1"
|
||||
)
|
||||
|
||||
/* Let's see if we can connect to an rtsp device then read an rtp stream,
|
||||
and then convert the rtp packets to mpegts packets and output. */
|
||||
func TestRTSP(t *testing.T) {
|
||||
sess := rtsp.NewSession()
|
||||
res, err := sess.Options(rtspUrl)
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
|
||||
res, err = sess.Describe(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength})
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Printf("%+v", p)
|
||||
res, err = sess.Setup(rtpUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
res, err = sess.Play(rtspUrl, res.Header.Get("Session"))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
}
|
||||
|
||||
func TestRTP(t *testing.T) {
|
||||
sess := rtsp.NewSession()
|
||||
res, err := sess.Options(rtspUrl)
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
res, err = sess.Describe(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength})
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Printf("%+v", p)
|
||||
res, err = sess.Setup(rtpUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
res, err = sess.Play(rtspUrl, res.Header.Get("Session"))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
// create udp connection for rtp stuff
|
||||
rtpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17300")
|
||||
if err != nil {
|
||||
t.Errorf("Local rtp addr not set! %v\n", err)
|
||||
}
|
||||
rtpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17300")
|
||||
if err != nil {
|
||||
t.Errorf("Resolving rtp address didn't work! %v\n", err)
|
||||
}
|
||||
rtpConn, err := net.DialUDP("udp", rtpLaddr, rtpAddr)
|
||||
if err != nil {
|
||||
t.Errorf("Conncection not established! %v\n", err)
|
||||
}
|
||||
// Create udp connection for rtcp stuff
|
||||
rtcpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17319")
|
||||
if err != nil {
|
||||
t.Errorf("Local RTCP address not resolved! %v\n", err)
|
||||
}
|
||||
rtcpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17301")
|
||||
if err != nil {
|
||||
t.Errorf("Remote RTCP address not resolved! %v\n", err)
|
||||
}
|
||||
rtcpConn, err := net.DialUDP("udp", rtcpLaddr, rtcpAddr)
|
||||
if err != nil {
|
||||
t.Errorf("Connection not established! %v\n", err)
|
||||
}
|
||||
// let's create a session that will store useful stuff from the connections
|
||||
rtpSession := NewSession(rtpConn, rtcpConn)
|
||||
time.Sleep(2 * time.Second)
|
||||
select {
|
||||
default:
|
||||
t.Errorf("Should have got rtpPacket!")
|
||||
case rtpPacket := <-rtpSession.RtpChan:
|
||||
fmt.Printf("RTP packet: %v\n", rtpPacket)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
Testing stuff related to the Nal.go file
|
||||
********************************************************/
|
||||
var parseInput = []byte{
|
||||
0x6C, // 3NalUnitBits = 101(5), Fragment type = 1100 (type = 12 )
|
||||
0x94, // starbit = 1, endbit = 0, Reservedbit = 0, 5NalUnitBits = 10100 (20)
|
||||
0x8E, // 10001110 random frame byte
|
||||
0x26, // 00100110 random frame byte
|
||||
0xD0, // 11010000 random frame byte
|
||||
}
|
||||
|
||||
var expectedParsing = []interface{}{
|
||||
byte(3),
|
||||
byte(12),
|
||||
bool(true),
|
||||
bool(false),
|
||||
bool(false),
|
||||
byte(20),
|
||||
[]byte{0x8E, 0x26, 0xD0},
|
||||
}
|
||||
|
||||
const (
|
||||
nalTestType = 12
|
||||
)
|
||||
|
||||
func TestNalFragmentParsing(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
value := reflect.ValueOf(*nalUnit)
|
||||
length := value.NumField()
|
||||
fields := make([]interface{}, length)
|
||||
for ii := 0; ii < length; ii++ {
|
||||
fields[ii] = value.Field(ii).Interface()
|
||||
}
|
||||
for ii := range fields {
|
||||
if !reflect.DeepEqual(fields[ii], expectedParsing[ii]) {
|
||||
t.Errorf("Bad Parsing! Field: %v wanted: %v got: %v\n", ii, expectedParsing[ii],
|
||||
fields[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalFragmentToByteSlice(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
output := nalUnit.ToByteSlice()
|
||||
for ii := range output {
|
||||
if output[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad conversion to byte slice at %vth byte! wanted: %v got: %v",
|
||||
parseInput[ii], output[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalFragmentType(t *testing.T) {
|
||||
nalUnit := ParseNALFragment(parseInput)
|
||||
nalType := nalUnit.GetType()
|
||||
if nalType != nalTestType {
|
||||
t.Errorf("Returned wrong type!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsParsing(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
for ii := range parseInput {
|
||||
if nalSpsPps.Data[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad Parsing! Byte: %v wanted: %v got: %v\n", ii, parseInput[ii],
|
||||
nalSpsPps.Data[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsToByteSlice(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
nalSpsPpsByteSlice := nalSpsPps.ToByteSlice()
|
||||
for ii := range parseInput {
|
||||
if nalSpsPpsByteSlice[ii] != parseInput[ii] {
|
||||
t.Errorf("Bad conversion to byte slice! Byte: %v wanted: %v got: %v\n", ii,
|
||||
parseInput[ii], nalSpsPpsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNalSpsPpsType(t *testing.T) {
|
||||
nalSpsPps := ParseNALSpsPps(parseInput)
|
||||
if nalSpsPps.GetType() != nalTestType {
|
||||
t.Errorf("Returned wrong type!")
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
Pes Packet testing!
|
||||
********************************************************/
|
||||
const (
|
||||
dataLength = 3 // bytes
|
||||
)
|
||||
|
||||
func TestPesToByteSlice(t *testing.T) {
|
||||
pesPkt := PESPacket{
|
||||
byte(0xE0), // StreamID
|
||||
uint16(6), // Length
|
||||
byte(0), // ScramblingControl
|
||||
bool(true), // Priority
|
||||
bool(false), // DAI
|
||||
bool(false), // copyright
|
||||
bool(true), // Original
|
||||
byte(0), // PDI
|
||||
bool(false), // Escr
|
||||
bool(false), // ESRate
|
||||
bool(false), // DSMTrickMode
|
||||
bool(false), // ACI
|
||||
bool(false), // CRC
|
||||
bool(false), // Ext
|
||||
byte(0), // header length
|
||||
[]byte{},
|
||||
[]byte{},
|
||||
[]byte{ // data
|
||||
0xEA,
|
||||
0x4B,
|
||||
0x12,
|
||||
},
|
||||
}
|
||||
pesExpectedOutput := []byte{
|
||||
0x00, // packet start code prefix byte 1
|
||||
0x00, // packet start code prefix byte 2
|
||||
0x01, // packet start code prefix byte 3
|
||||
0xE0, // stream ID
|
||||
0x00, // PES Packet length byte 1
|
||||
0x06, // PES packet length byte 2
|
||||
0x89, // Marker bits,ScramblingControl, Priority, DAI, Copyright, Original
|
||||
0x00, // PDI, ESCR, ESRate, DSMTrickMode, ACI, CRC, Ext
|
||||
0x00, // header length
|
||||
0xEA, // data byte 1
|
||||
0x4B, // data byte 2
|
||||
0x12, // data byte 3
|
||||
}
|
||||
pesPktAsByteSlice := pesPkt.ToByteSlice()
|
||||
for ii := range pesPktAsByteSlice {
|
||||
if pesPktAsByteSlice[ii] != pesExpectedOutput[ii] {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v",
|
||||
ii, pesExpectedOutput[ii], pesPktAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
Mpegts testing
|
||||
********************************************************/
|
||||
|
||||
func TestMpegTsToByteSlice(t *testing.T){
|
||||
afRemainderLength := 180
|
||||
afField := make([]byte, afRemainderLength+1)
|
||||
afField[0] = byte(afRemainderLength)
|
||||
afField[1] = byte(0)
|
||||
for i := 2; i < len(afField); i++ {
|
||||
afField[i] = 0xFF
|
||||
}
|
||||
tsPkt := MpegTsPacket{
|
||||
byte(0x47), // sync byte
|
||||
bool(false), // TEI
|
||||
bool(false), // PUSI
|
||||
bool(false), // Priority
|
||||
uint16(256), // PID
|
||||
byte(0), // TSC
|
||||
byte(3), // AFC
|
||||
byte(6), // CC
|
||||
afField, // AF
|
||||
[]byte{ // data
|
||||
0x67,
|
||||
0xB2,
|
||||
0xE3,
|
||||
},
|
||||
}
|
||||
expectedOutput := []byte{
|
||||
0x47,
|
||||
0x01,
|
||||
0x00,
|
||||
0x36,
|
||||
byte(afRemainderLength),
|
||||
0x00,
|
||||
// this is where stuffing is, expect that to be 0xFF
|
||||
0x67,
|
||||
0xB2,
|
||||
0xE3,
|
||||
}
|
||||
tsPktAsByteSlice := tsPkt.ToByteSlice()
|
||||
for ii := 0; ii < 6; ii++ {
|
||||
if tsPktAsByteSlice[ii] != expectedOutput[ii] {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v",
|
||||
ii, expectedOutput[ii], tsPktAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
// Check that the stuffing is all there
|
||||
for ii := 6; ii < 185; ii++ {
|
||||
if tsPktAsByteSlice[ii] != 0xFF {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v",
|
||||
ii, byte(0xFF), tsPktAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
for ii := 185; ii < 188; ii++ {
|
||||
if tsPktAsByteSlice[ii] != expectedOutput[ii-185+6] {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v",
|
||||
ii, expectedOutput[ii], tsPktAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
RtpToTsConverter testing
|
||||
********************************************************/
|
||||
/*
|
||||
func TestRtpToTsConverter(t *testing.T){
|
||||
converter := NewRtpToTsConverter()
|
||||
go converter.Convert()
|
||||
// Create first rtp packet
|
||||
rtpPacket1 := new(RtpPacket)
|
||||
rtpPacket1.Version = 2
|
||||
rtpPacket1.Padding = false
|
||||
rtpPacket1.Ext = false
|
||||
rtpPacket1.CC = 0
|
||||
rtpPacket1.Marker = true
|
||||
rtpPacket1.PayloadType = 0xE3
|
||||
rtpPacket1.SequenceNumber = 1
|
||||
rtpPacket1.Timestamp = 200
|
||||
rtpPacket1.SyncSource = 0
|
||||
rtpPacket1.CSRC = nil
|
||||
rtpPacket1.ExtHeader = 0
|
||||
rtpPacket1.ExtData = nil
|
||||
nalFragment := new(NALFragment)
|
||||
nalFragment.ThreeNUBs = 0x02
|
||||
nalFragment.FragmentType = byte(28)
|
||||
nalFragment.Start = true
|
||||
nalFragment.End = false
|
||||
nalFragment.Reserved = true
|
||||
nalFragment.FiveNUBs = 0x03
|
||||
nalFragment.Data = make([]byte,98)
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
for i := range nalFragment.Data {
|
||||
nalFragment.Data[i] = byte(rand.Intn(255))
|
||||
fmt.Printf(" %v ", nalFragment.Data[i])
|
||||
}
|
||||
rtpPacket1.Payload = make([]byte,100)
|
||||
copy(rtpPacket1.Payload[:], nalFragment.ToByteSlice())
|
||||
fmt.Println(rtpPacket1.Payload)
|
||||
converter.InputChan<-(*rtpPacket1)
|
||||
// Create second rtp packet
|
||||
rtpPacket2 := new(RtpPacket)
|
||||
rtpPacket2.Version = 2
|
||||
rtpPacket2.Padding = false
|
||||
rtpPacket2.Ext = false
|
||||
rtpPacket2.CC = 0
|
||||
rtpPacket2.Marker = false
|
||||
rtpPacket2.PayloadType = 0xE3
|
||||
rtpPacket2.SequenceNumber = 2
|
||||
rtpPacket2.Timestamp = 300
|
||||
rtpPacket2.SyncSource = 0
|
||||
rtpPacket2.CSRC = nil
|
||||
rtpPacket2.ExtHeader = 0
|
||||
rtpPacket2.ExtData = nil
|
||||
nalFragment = new(NALFragment)
|
||||
nalFragment.ThreeNUBs = 0x02
|
||||
nalFragment.FragmentType = byte(28)
|
||||
nalFragment.Start = false
|
||||
nalFragment.End = true
|
||||
nalFragment.Reserved = true
|
||||
nalFragment.FiveNUBs = 0x03
|
||||
nalFragment.Data = make([]byte,198)
|
||||
for i := range nalFragment.Data {
|
||||
nalFragment.Data[i] = byte(rand.Intn(255))
|
||||
}
|
||||
rtpPacket2.Payload = make([]byte,200)
|
||||
copy(rtpPacket2.Payload[:], nalFragment.ToByteSlice())
|
||||
converter.InputChan<-(*rtpPacket2)
|
||||
|
||||
// Create first expected tsPacket
|
||||
afField := make([]byte, 2)
|
||||
afField[0] = byte(1)
|
||||
afField[1] = byte(0)
|
||||
pesPkt := new(PESPacket)
|
||||
pesPkt.StreamID = 0xE0
|
||||
pesPkt.Length = uint16( 3 + 300 )
|
||||
pesPkt.ScramblingControl = 0
|
||||
pesPkt.Priority = true
|
||||
pesPkt.DAI = false
|
||||
pesPkt.Copyright = false
|
||||
pesPkt.Original = true
|
||||
pesPkt.PDI = 0
|
||||
pesPkt.ESCR = false
|
||||
pesPkt.ESRate = false
|
||||
pesPkt.DSMTrickMode = false
|
||||
pesPkt.ACI = false
|
||||
pesPkt.CRC = false
|
||||
pesPkt.Ext = false
|
||||
pesPkt.HeaderLength = 0
|
||||
pesPkt.Data = make([]byte,300)
|
||||
for ii:=0; ii<100; ii++ {
|
||||
pesPkt.Data[ii] = rtpPacket1.Payload[ii]
|
||||
}
|
||||
for ii:=100; ii <300; ii++ {
|
||||
pesPkt.Data[ii] = rtpPacket2.Payload[ii-100]
|
||||
}
|
||||
|
||||
pesPacketAsByteSlice := pesPkt.ToByteSlice()
|
||||
|
||||
data := make([]byte, 182)
|
||||
|
||||
copy(data[:],pesPacketAsByteSlice[:182])
|
||||
|
||||
expectedPkt1 := MpegTsPacket{
|
||||
byte(0x47), // sync byte
|
||||
bool(false), // TEI
|
||||
bool(true), // PUSI
|
||||
bool(false), // Priority
|
||||
uint16(256), // PID
|
||||
byte(0), // TSC
|
||||
byte(3), // AFC
|
||||
byte(0), // CC
|
||||
afField, // AF
|
||||
data,
|
||||
}
|
||||
|
||||
data = make([]byte, len(pesPacketAsByteSlice)-182)
|
||||
|
||||
copy(data[:], pesPacketAsByteSlice[182:])
|
||||
|
||||
afField = make([]byte, 2+(182-(len(pesPacketAsByteSlice)-182)))
|
||||
afField[0] = byte(1+(182-(len(pesPacketAsByteSlice)-182)))
|
||||
afField[1] = byte(0)
|
||||
for ii := 2; ii < len(afField); ii++ {
|
||||
afField[ii] = 0xFF
|
||||
}
|
||||
|
||||
expectedPkt2 := MpegTsPacket{
|
||||
byte(0x47), // sync byte
|
||||
bool(false), // TEI
|
||||
bool(false), // PUSI
|
||||
bool(false), // Priority
|
||||
uint16(256), // PID
|
||||
byte(0), // TSC
|
||||
byte(3), // AFC
|
||||
byte(1), // CC
|
||||
afField, // AF
|
||||
data,
|
||||
}
|
||||
// Now let's get our two Ts packets from the converter and see if they're G
|
||||
tsPacket := <-converter.TsChan
|
||||
expectedPkt1AsByteSlice := expectedPkt1.ToByteSlice()
|
||||
tsPacketAsByteSlice := tsPacket.ToByteSlice()
|
||||
for ii := range expectedPkt1AsByteSlice {
|
||||
if expectedPkt1AsByteSlice[ii] != tsPacketAsByteSlice[ii] {
|
||||
t.Errorf("Not equal! Byte: %v Exptected: %v Got: %v\n",ii,
|
||||
expectedPkt1AsByteSlice[ii],tsPacketAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
fmt.Printf("Expected packet: %v\n", expectedPkt1.ToByteSlice())
|
||||
fmt.Printf("Got packet: %v\n", tsPacket.ToByteSlice())
|
||||
tsPacket = <-converter.TsChan
|
||||
expectedPkt2AsByteSlice := expectedPkt2.ToByteSlice()
|
||||
tsPacketAsByteSlice = tsPacket.ToByteSlice()
|
||||
for ii := range expectedPkt2AsByteSlice {
|
||||
if expectedPkt2AsByteSlice[ii] != tsPacketAsByteSlice[ii] {
|
||||
t.Errorf("Not equal! Byte: %v Exptected: %v Got: %v\n",ii,
|
||||
expectedPkt2AsByteSlice[ii],tsPacketAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
fmt.Printf("Expected packet: %v\n", expectedPkt2.ToByteSlice())
|
||||
fmt.Printf("Got packet: %v\n", tsPacket.ToByteSlice())
|
||||
}
|
||||
*/
|
||||
|
||||
func TestH264Parsing(t *testing.T) {
|
||||
// Using file
|
||||
/*
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
panic("Could not open file!")
|
||||
return
|
||||
}
|
||||
stats, err := file.Stat()
|
||||
if err != nil {
|
||||
panic("Could not get file stats!")
|
||||
}
|
||||
buffer := make([]byte, stats.Size())
|
||||
_, err = file.Read(buffer)
|
||||
if err != nil {
|
||||
panic("Could not read file!")
|
||||
}
|
||||
*/
|
||||
// straight from buffer
|
||||
someData := []byte{
|
||||
0,0,1,7,59,100,45,82,93,0,0,1,8,23,78,65,0,0,1,6,45,34,23,3,2,0,0,1,5,3,4,5,
|
||||
56,76,4,234,78,65,34,34,43,0,0,1,7,67,10,45,8,93,0,0,1,8,23,7,5,0,0,1,6,
|
||||
4,34,2,3,2,0,0,1,1,3,4,5,5,76,4,234,78,65,34,34,43,45,
|
||||
}
|
||||
nalAccess1 := []byte{
|
||||
0,0,1,9,240,0,0,1,7,59,100,45,82,93,0,0,1,8,23,78,65,0,0,1,6,45,34,23,3,2,0,0,1,5,3,4,5,
|
||||
56,76,4,234,78,65,34,34,43,
|
||||
}
|
||||
nalAccess2 := []byte{
|
||||
0,0,1,9,240,0,0,1,7,67,10,45,8,93,0,0,1,8,23,7,5,0,0,1,6,
|
||||
4,34,2,3,2,0,0,1,1,3,4,5,5,76,4,234,78,65,34,34,43,45,
|
||||
}
|
||||
aChannel := make(chan []byte, 10)
|
||||
var nalAccessChan chan<- []byte
|
||||
nalAccessChan = aChannel
|
||||
go ParseH264Buffer(someData,nalAccessChan)
|
||||
anAccessUnit := <-aChannel
|
||||
for i := range anAccessUnit {
|
||||
if anAccessUnit[i] != nalAccess1[i] {
|
||||
t.Errorf("Should have been equal!")
|
||||
}
|
||||
}
|
||||
anAccessUnit = <-aChannel
|
||||
for i := range anAccessUnit {
|
||||
if anAccessUnit[i] != nalAccess2[i] {
|
||||
t.Errorf("Should have been equal!")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ DESCRIPTION
|
|||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
Saxon A. Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
PES.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
@ -24,8 +24,13 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package packets
|
||||
package pes
|
||||
|
||||
/*
|
||||
The below data struct encapsulates the fields of an PES packet. Below is
|
||||
the formatting of a PES packet for reference!
|
||||
|
||||
PES Packet Formatting
|
||||
============================================================================
|
||||
| octet no | bit 0 | bit 1 | bit 2 | bit 3 | bit 4 | bit 5 | bit 6 | bit 7 |
|
||||
============================================================================
|
||||
|
@ -61,52 +66,38 @@ package packets
|
|||
----------------------------------------------------------------------------
|
||||
*/
|
||||
type PESPacket struct {
|
||||
StreamID byte
|
||||
Length uint16
|
||||
SC byte // Scrambling control
|
||||
Priority bool // Priority Indicator
|
||||
DAI bool // Data alginment indicator
|
||||
Copyright bool // Copyright indicator
|
||||
Original bool // Original data indicator
|
||||
PDI byte // PTS DTS indicator
|
||||
ESCRF bool // Elementary stream clock reference flag
|
||||
ESRF bool // Elementary stream rate reference flag
|
||||
DSMTMF bool // Dsm trick mode flag
|
||||
ACI bool // Additional copy info flag
|
||||
CRC bool //
|
||||
EF bool // Extension flag
|
||||
HeaderLength byte // Pes header length
|
||||
OptFields []byte // Optional fields
|
||||
Stuffing []byte // Stuffing bytes
|
||||
Data []byte // Pes packet data
|
||||
StreamID byte // Type of stream
|
||||
Length uint16 // Pes packet length in bytes after this field
|
||||
SC byte // Scrambling control
|
||||
Priority bool // Priority Indicator
|
||||
DAI bool // Data alginment indicator
|
||||
Copyright bool // Copyright indicator
|
||||
Original bool // Original data indicator
|
||||
PDI byte // PTS DTS indicator
|
||||
ESCRF bool // Elementary stream clock reference flag
|
||||
ESRF bool // Elementary stream rate reference flag
|
||||
DSMTMF bool // Dsm trick mode flag
|
||||
ACIF bool // Additional copy info flag
|
||||
CRCF bool // Not sure
|
||||
EF bool // Extension flag
|
||||
HeaderLength byte // Pes header length
|
||||
OptFields []byte // Optional fields
|
||||
Stuffing []byte // Stuffing bytes
|
||||
Data []byte // Pes packet data
|
||||
}
|
||||
|
||||
func (p *PESPacket) ToByteSlice() (output []byte) {
|
||||
output = make([]byte, 6+p.Length)
|
||||
output[0] = 0x00
|
||||
output[1] = 0x00
|
||||
output[2] = 0x01
|
||||
output[3] = p.StreamID
|
||||
output[4] = byte(( p.Length & 0xFF00 ) >> 8)
|
||||
output[5] = byte( p.Length & 0x00FF )
|
||||
output[6] = 0x2 << 6 |
|
||||
p.ScramblingControl << 4 |
|
||||
boolToByte(p.Priority) << 3 |
|
||||
boolToByte(p.DAI) << 2 |
|
||||
boolToByte(p.Copyright) << 1 |
|
||||
boolToByte(p.Original)
|
||||
output[7] = p.PDI << 6 |
|
||||
boolToByte(p.ESCR) << 5 |
|
||||
boolToByte(p.ESRate) << 4 |
|
||||
boolToByte(p.DSMTrickMode) << 3 |
|
||||
boolToByte(p.ACI) << 2 |
|
||||
boolToByte(p.CRC) << 1 |
|
||||
boolToByte(p.Ext)
|
||||
output[8] = p.HeaderLength
|
||||
optFieldsOffset := 9+len(p.OptFields)
|
||||
copy(output[9:optFieldsOffset],p.OptFields)
|
||||
copy(output[optFieldsOffset:optFieldsOffset + len(p.Stuffing)],p.Stuffing)
|
||||
dataOffset := 9+int(p.HeaderLength)
|
||||
copy(output[dataOffset:dataOffset+len(p.Data)],p.Data)
|
||||
output = append(output, []byte{
|
||||
0x00, 0x00, 0x01,
|
||||
p.StreamID,
|
||||
byte((p.Length & 0xFF00) >> 8),
|
||||
byte(p.Length & 0x00FF),
|
||||
(0x2<<6 | p.SC<<4 | boolToByte(p.Priority)<<3 | boolToByte(p.DAI)<<2 |
|
||||
boolToByte(p.Copyright)<<1 | boolToByte(p.Original)),
|
||||
(p.PDI<<6 | boolToByte(p.ESCRF)<<5 | boolToByte(p.ESRF)<<4 | boolToByte(p.DSMTMF)<<3 |
|
||||
boolToByte(p.ACIF)<<2 | boolToByte(p.CRCF)<<1 | boolToByte(p.EF)),
|
||||
p.HeaderLength,
|
||||
}...)
|
||||
output = append(output, append(p.OptFields, append(p.Stuffing, p.Data...)...)...)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package pes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
dataLength = 3 // bytes
|
||||
)
|
||||
|
||||
func TestPesToByteSlice(t *testing.T) {
|
||||
pesPkt := PESPacket{
|
||||
byte(0xE0), // StreamID
|
||||
uint16(6), // Length
|
||||
byte(0), // ScramblingControl
|
||||
bool(true), // Priority
|
||||
bool(false), // DAI
|
||||
bool(false), // copyright
|
||||
bool(true), // Original
|
||||
byte(0), // PDI
|
||||
bool(false), // Escr
|
||||
bool(false), // ESRate
|
||||
bool(false), // DSMTrickMode
|
||||
bool(false), // ACI
|
||||
bool(false), // CRC
|
||||
bool(false), // Ext
|
||||
byte(0), // header length
|
||||
[]byte{},
|
||||
[]byte{},
|
||||
[]byte{ // data
|
||||
0xEA,
|
||||
0x4B,
|
||||
0x12,
|
||||
},
|
||||
}
|
||||
pesExpectedOutput := []byte{
|
||||
0x00, // packet start code prefix byte 1
|
||||
0x00, // packet start code prefix byte 2
|
||||
0x01, // packet start code prefix byte 3
|
||||
0xE0, // stream ID
|
||||
0x00, // PES Packet length byte 1
|
||||
0x06, // PES packet length byte 2
|
||||
0x89, // Marker bits,ScramblingControl, Priority, DAI, Copyright, Original
|
||||
0x00, // PDI, ESCR, ESRate, DSMTrickMode, ACI, CRC, Ext
|
||||
0x00, // header length
|
||||
0xEA, // data byte 1
|
||||
0x4B, // data byte 2
|
||||
0x12, // data byte 3
|
||||
}
|
||||
pesPktAsByteSlice := pesPkt.ToByteSlice()
|
||||
for ii := range pesPktAsByteSlice {
|
||||
if pesPktAsByteSlice[ii] != pesExpectedOutput[ii] {
|
||||
t.Errorf("Conversion to byte slice bad! Byte: %v Wanted: %v Got: %v",
|
||||
ii, pesExpectedOutput[ii], pesPktAsByteSlice[ii])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main(){
|
||||
data := []byte{ 2,176,18,0,1,193,0,0,225,0,240,0,27,225,0,240,0}
|
||||
|
||||
crc32 := 0xffffffff
|
||||
|
||||
for i := 1 + data[0]; i < len(data); i++ {
|
||||
b := data[i]
|
||||
for bit := 0; bit < 8; bit++ {
|
||||
if (crc32 >= 0x80000000) != (b >= 0x90) {
|
||||
crc32 = (crc32 << 1) ^ 0x04C11DB7
|
||||
} else {
|
||||
crc32 = crc32 << 1
|
||||
b <<= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println(crc32)
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
_"time"
|
||||
|
||||
"../packet"
|
||||
|
||||
"github.com/beatgammit/rtsp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(flag.Args()) >= 1 {
|
||||
rtspUrl := flag.Args()[0]
|
||||
rtpUrl := flag.Args()[1]
|
||||
|
||||
sess := rtsp.NewSession()
|
||||
res, err := sess.Options(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println("Options:")
|
||||
fmt.Println(res)
|
||||
|
||||
res, err = sess.Describe(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println("Describe:")
|
||||
fmt.Println(res)
|
||||
|
||||
p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("%+v", p)
|
||||
|
||||
fmt.Println("Setting up!")
|
||||
rtpPort, rtcpPort := 17300, 17319
|
||||
res, err = sess.Setup(rtpUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(res)
|
||||
|
||||
fmt.Println("Playing !")
|
||||
res, err = sess.Play(rtspUrl, res.Header.Get("Session"))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(res)
|
||||
|
||||
// create udp connection for rtp stuff
|
||||
rtpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17300")
|
||||
if err != nil {
|
||||
fmt.Println("Local rtp addr not set!")
|
||||
}
|
||||
rtpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17300")
|
||||
if err != nil {
|
||||
fmt.Println("Resolving rtp address didn't work!")
|
||||
}
|
||||
rtpConn, err := net.DialUDP("udp", rtpLaddr, rtpAddr)
|
||||
if err != nil {
|
||||
fmt.Println("Rtp dial didn't work!")
|
||||
}
|
||||
|
||||
// Create udp connection for rtcp stuff
|
||||
rtcpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17319")
|
||||
if err != nil {
|
||||
fmt.Println("Local ")
|
||||
}
|
||||
rtcpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17301")
|
||||
if err != nil {
|
||||
fmt.Println("resolving rtcp address didn't work!")
|
||||
}
|
||||
rtcpConn, err := net.DialUDP("udp", rtcpLaddr, rtcpAddr)
|
||||
if err != nil {
|
||||
fmt.Println("Rtcp dial didnt't work!")
|
||||
}
|
||||
|
||||
// let's create a session that will store useful stuff from the connections
|
||||
rtpSession := packet.NewSession(rtpConn, rtcpConn)
|
||||
converter := packet.NewRtpToTsConverter()
|
||||
go converter.Convert()
|
||||
for {
|
||||
select{
|
||||
default:
|
||||
case rtpPacket := <-rtpSession.RtpChan:
|
||||
converter.RtpChan<-rtpPacket
|
||||
case tsPacket:=<-converter.TsChan:
|
||||
fmt.Println(tsPacket.ToByteSlice())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package revid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
/*******************************************************
|
||||
Testing stuff related to connection i.e. rtsp, rtp, rtcp
|
||||
********************************************************/
|
||||
const (
|
||||
rtpPort = 17300
|
||||
rtcpPort = 17319
|
||||
rtspUrl = "rtsp://192.168.0.50:8554/CH002.sdp"
|
||||
rtpUrl = "rtsp://192.168.0.50:8554/CH002.sdp/track1"
|
||||
)
|
||||
|
||||
/* Let's see if we can connect to an rtsp device then read an rtp stream,
|
||||
and then convert the rtp packets to mpegts packets and output. */
|
||||
func TestRTSP(t *testing.T) {
|
||||
sess := rtsp.NewSession()
|
||||
res, err := sess.Options(rtspUrl)
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
|
||||
res, err = sess.Describe(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength})
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Printf("%+v", p)
|
||||
res, err = sess.Setup(rtpUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
res, err = sess.Play(rtspUrl, res.Header.Get("Session"))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
}
|
||||
|
||||
func TestRTP(t *testing.T) {
|
||||
sess := rtsp.NewSession()
|
||||
res, err := sess.Options(rtspUrl)
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
res, err = sess.Describe(rtspUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength})
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Printf("%+v", p)
|
||||
res, err = sess.Setup(rtpUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
res, err = sess.Play(rtspUrl, res.Header.Get("Session"))
|
||||
if err != nil {
|
||||
t.Errorf("Shouldn't have got error: %v\n", err)
|
||||
}
|
||||
log.Println(res)
|
||||
// create udp connection for rtp stuff
|
||||
rtpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17300")
|
||||
if err != nil {
|
||||
t.Errorf("Local rtp addr not set! %v\n", err)
|
||||
}
|
||||
rtpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17300")
|
||||
if err != nil {
|
||||
t.Errorf("Resolving rtp address didn't work! %v\n", err)
|
||||
}
|
||||
rtpConn, err := net.DialUDP("udp", rtpLaddr, rtpAddr)
|
||||
if err != nil {
|
||||
t.Errorf("Conncection not established! %v\n", err)
|
||||
}
|
||||
// Create udp connection for rtcp stuff
|
||||
rtcpLaddr, err := net.ResolveUDPAddr("udp", "192.168.0.109:17319")
|
||||
if err != nil {
|
||||
t.Errorf("Local RTCP address not resolved! %v\n", err)
|
||||
}
|
||||
rtcpAddr, err := net.ResolveUDPAddr("udp", "192.168.0.50:17301")
|
||||
if err != nil {
|
||||
t.Errorf("Remote RTCP address not resolved! %v\n", err)
|
||||
}
|
||||
rtcpConn, err := net.DialUDP("udp", rtcpLaddr, rtcpAddr)
|
||||
if err != nil {
|
||||
t.Errorf("Connection not established! %v\n", err)
|
||||
}
|
||||
// let's create a session that will store useful stuff from the connections
|
||||
rtpSession := NewSession(rtpConn, rtcpConn)
|
||||
time.Sleep(2 * time.Second)
|
||||
select {
|
||||
default:
|
||||
t.Errorf("Should have got rtpPacket!")
|
||||
case rtpPacket := <-rtpSession.RtpChan:
|
||||
fmt.Printf("RTP packet: %v\n", rtpPacket)
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISE
|
|||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package packets
|
||||
package rtp
|
||||
|
||||
import (
|
||||
"net"
|
|
@ -26,30 +26,31 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package packets
|
||||
package tools
|
||||
|
||||
import (
|
||||
_"os"
|
||||
_"fmt"
|
||||
"reflect"
|
||||
"../rtp"
|
||||
)
|
||||
|
||||
func boolToByte(in bool) (out uint8) {
|
||||
func BoolToByte(in bool) (out byte) {
|
||||
if in {
|
||||
out = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetOctectType(p *RtpPacket) byte {
|
||||
func GetOctectType(p *rtp.RtpPacket) byte {
|
||||
return p.Payload[0] & 0x1F
|
||||
}
|
||||
|
||||
func GetStartBit(p *RtpPacket) byte {
|
||||
func GetStartBit(p *rtp.RtpPacket) byte {
|
||||
return (p.Payload[1] & 0x80) >> 7
|
||||
}
|
||||
|
||||
func getEndBit(p *RtpPacket) byte {
|
||||
func getEndBit(p *rtp.RtpPacket) byte {
|
||||
return (p.Payload[1] & 0x40) >> 6
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
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.
|
||||
|
||||
It 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 revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestH264Parsing(t *testing.T){
|
||||
// Using file
|
||||
/*
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
panic("Could not open file!")
|
||||
return
|
||||
}
|
||||
stats, err := file.Stat()
|
||||
if err != nil {
|
||||
panic("Could not get file stats!")
|
||||
}
|
||||
buffer := make([]byte, stats.Size())
|
||||
_, err = file.Read(buffer)
|
||||
if err != nil {
|
||||
panic("Could not read file!")
|
||||
}
|
||||
*/
|
||||
// straight from buffer
|
||||
someData := []byte{
|
||||
0,0,1,7,59,100,45,82,93,0,0,1,8,23,78,65,0,0,1,6,45,34,23,3,2,0,0,1,5,3,4,5,
|
||||
56,76,4,234,78,65,34,34,43,0,0,1,7,67,10,45,8,93,0,0,1,8,23,7,5,0,0,1,6,
|
||||
4,34,2,3,2,0,0,1,1,3,4,5,5,76,4,234,78,65,34,34,43,45,
|
||||
}
|
||||
nalAccess1 := []byte{
|
||||
0,0,1,9,240,0,0,1,7,59,100,45,82,93,0,0,1,8,23,78,65,0,0,1,6,45,34,23,3,2,0,0,1,5,3,4,5,
|
||||
56,76,4,234,78,65,34,34,43,
|
||||
}
|
||||
nalAccess2 := []byte{
|
||||
0,0,1,9,240,0,0,1,7,67,10,45,8,93,0,0,1,8,23,7,5,0,0,1,6,
|
||||
4,34,2,3,2,0,0,1,1,3,4,5,5,76,4,234,78,65,34,34,43,45,
|
||||
}
|
||||
aChannel := make(chan []byte, 10)
|
||||
var nalAccessChan chan<- []byte
|
||||
nalAccessChan = aChannel
|
||||
go ParseH264Buffer(someData,nalAccessChan)
|
||||
anAccessUnit := <-aChannel
|
||||
for i := range anAccessUnit {
|
||||
if anAccessUnit[i] != nalAccess1[i] {
|
||||
t.Errorf("Should have been equal!")
|
||||
}
|
||||
}
|
||||
anAccessUnit = <-aChannel
|
||||
for i := range anAccessUnit {
|
||||
if anAccessUnit[i] != nalAccess2[i] {
|
||||
t.Errorf("Should have been equal!")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,15 +190,12 @@ func (c *rtpToTsConverter) Convert() {
|
|||
lengthOfByteChan := len(payloadByteChan)
|
||||
c.currentTsPacket = new(MpegTsPacket)
|
||||
c.currentTsPacket.SyncByte = 0x47
|
||||
c.currentTsPacket.TEI = false
|
||||
c.currentTsPacket.PUSI = false
|
||||
if firstPacket { // if it's the start of the payload
|
||||
c.currentTsPacket.PUSI = true
|
||||
firstPacket = false
|
||||
}
|
||||
c.currentTsPacket.Priority = false
|
||||
c.currentTsPacket.PID = 256
|
||||
c.currentTsPacket.TSC = 0
|
||||
c.currentTsPacket.CC = c.currentCC
|
||||
if c.currentCC++; c.currentCC > 15 {
|
||||
c.currentCC = 0
|
||||
|
@ -211,10 +208,6 @@ func (c *rtpToTsConverter) Convert() {
|
|||
stuffingLength := 182 - payloadLength
|
||||
c.currentTsPacket.AF = make([]byte, 2+stuffingLength) // adaptationfield flag length = 16
|
||||
c.currentTsPacket.AF[0] = byte(1 + stuffingLength)
|
||||
c.currentTsPacket.AF[1] = 0
|
||||
if c.currentTsPacket.PUSI {
|
||||
c.currentTsPacket.AF[1] = 0x00
|
||||
}
|
||||
for ii := 0; ii < stuffingLength; ii++ {
|
||||
c.currentTsPacket.AF[2+ii] = 0xFF
|
||||
}
|
Loading…
Reference in New Issue