mirror of https://bitbucket.org/ausocean/av.git
encoding: restructure packages handling stream encoding
This commit is contained in:
parent
9e28fd45fd
commit
c0f9f7bf7b
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
NAME
|
||||
generator.go
|
||||
encoding.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
@ -9,7 +9,7 @@ AUTHOR
|
|||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
generator.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
encoding.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
|
||||
|
@ -25,9 +25,9 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package generator
|
||||
package encoding
|
||||
|
||||
type Generator interface {
|
||||
type Encoder interface {
|
||||
InputChan() chan []byte
|
||||
OutputChan() <-chan []byte
|
||||
Start()
|
|
@ -6,6 +6,7 @@ DESCRIPTION
|
|||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Dan Kortschak <dan@ausocean.org>
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
|
@ -22,15 +23,11 @@ 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 http://www.gnu.org/licenses.
|
||||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
package generator
|
||||
package flv
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/flv"
|
||||
)
|
||||
import "time"
|
||||
|
||||
const (
|
||||
inputChanLength = 500
|
||||
|
@ -61,7 +58,7 @@ type flvGenerator struct {
|
|||
audio bool
|
||||
video bool
|
||||
lastTagSize int
|
||||
header flv.Header
|
||||
header Header
|
||||
startTime time.Time
|
||||
firstTag bool
|
||||
isGenerating bool
|
||||
|
@ -105,7 +102,7 @@ func (g *flvGenerator) Stop() {
|
|||
// GenHeader generates the flv header and sends it down the output chan for use
|
||||
// This will generally be called once at the start of file writing/transmission.
|
||||
func (g *flvGenerator) GenHeader() {
|
||||
header := flv.Header{
|
||||
header := Header{
|
||||
HasAudio: g.audio,
|
||||
HasVideo: g.video,
|
||||
}
|
||||
|
@ -227,23 +224,23 @@ func (g *flvGenerator) generate() {
|
|||
// Do we have video to send off?
|
||||
if g.video {
|
||||
if isKeyFrame(frame) {
|
||||
frameType = flv.KeyFrameType
|
||||
frameType = KeyFrameType
|
||||
} else {
|
||||
frameType = flv.InterFrameType
|
||||
frameType = InterFrameType
|
||||
}
|
||||
if isSequenceHeader(frame) {
|
||||
packetType = flv.SequenceHeader
|
||||
packetType = SequenceHeader
|
||||
} else {
|
||||
packetType = flv.AVCNALU
|
||||
packetType = AVCNALU
|
||||
}
|
||||
|
||||
tag := flv.VideoTag{
|
||||
TagType: uint8(flv.VideoTagType),
|
||||
DataSize: uint32(len(frame)) + flv.DataHeaderLength,
|
||||
tag := VideoTag{
|
||||
TagType: uint8(VideoTagType),
|
||||
DataSize: uint32(len(frame)) + DataHeaderLength,
|
||||
Timestamp: timeStamp,
|
||||
TimestampExtended: flv.NoTimestampExtension,
|
||||
TimestampExtended: NoTimestampExtension,
|
||||
FrameType: frameType,
|
||||
Codec: flv.H264,
|
||||
Codec: H264,
|
||||
PacketType: packetType,
|
||||
CompositionTime: 0,
|
||||
Data: frame,
|
||||
|
@ -255,12 +252,12 @@ func (g *flvGenerator) generate() {
|
|||
if g.audio {
|
||||
// Not sure why but we need two audio tags for dummy silent audio
|
||||
// TODO: create constants or SoundSize and SoundType parameters
|
||||
tag := flv.AudioTag{
|
||||
TagType: uint8(flv.AudioTagType),
|
||||
tag := AudioTag{
|
||||
TagType: uint8(AudioTagType),
|
||||
DataSize: 7,
|
||||
Timestamp: timeStamp,
|
||||
TimestampExtended: flv.NoTimestampExtension,
|
||||
SoundFormat: flv.AACAudioFormat,
|
||||
TimestampExtended: NoTimestampExtension,
|
||||
SoundFormat: AACAudioFormat,
|
||||
SoundRate: 3,
|
||||
SoundSize: true,
|
||||
SoundType: true,
|
||||
|
@ -269,12 +266,12 @@ func (g *flvGenerator) generate() {
|
|||
}
|
||||
g.outputChan <- tag.Bytes()
|
||||
|
||||
tag = flv.AudioTag{
|
||||
TagType: uint8(flv.AudioTagType),
|
||||
tag = AudioTag{
|
||||
TagType: uint8(AudioTagType),
|
||||
DataSize: 21,
|
||||
Timestamp: timeStamp,
|
||||
TimestampExtended: flv.NoTimestampExtension,
|
||||
SoundFormat: flv.AACAudioFormat,
|
||||
TimestampExtended: NoTimestampExtension,
|
||||
SoundFormat: AACAudioFormat,
|
||||
SoundRate: 3,
|
||||
SoundSize: true,
|
||||
SoundType: true,
|
|
@ -26,7 +26,7 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package generator
|
||||
package mts
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
@ -34,8 +34,7 @@ import (
|
|||
"math/bits"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/mpegts"
|
||||
"bitbucket.org/ausocean/av/pes"
|
||||
"bitbucket.org/ausocean/av/encoding/mts/pes"
|
||||
)
|
||||
|
||||
const psiPacketSize = 184
|
||||
|
@ -221,7 +220,7 @@ func (g *tsGenerator) generate() {
|
|||
nalu := <-g.nalInputChan
|
||||
|
||||
// Write PAT
|
||||
patPkt := mpegts.Packet{
|
||||
patPkt := Packet{
|
||||
PUSI: true,
|
||||
PID: patPid,
|
||||
CC: g.ccFor(patPid),
|
||||
|
@ -231,7 +230,7 @@ func (g *tsGenerator) generate() {
|
|||
g.outputChan <- patPkt.Bytes()
|
||||
|
||||
// Write PMT.
|
||||
pmtPkt := mpegts.Packet{
|
||||
pmtPkt := Packet{
|
||||
PUSI: true,
|
||||
PID: pmtPid,
|
||||
CC: g.ccFor(pmtPid),
|
||||
|
@ -252,7 +251,7 @@ func (g *tsGenerator) generate() {
|
|||
|
||||
pusi := true
|
||||
for len(buf) != 0 {
|
||||
pkt := mpegts.Packet{
|
||||
pkt := Packet{
|
||||
PUSI: pusi,
|
||||
PID: videoPid,
|
||||
RAI: pusi,
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
NAME
|
||||
MpegTs.go - provides a data structure intended to encapsulate the properties
|
||||
mpegts.go - provides a data structure intended to encapsulate the properties
|
||||
of an MpegTs packet and also functions to allow manipulation of these packets.
|
||||
|
||||
DESCRIPTION
|
||||
|
@ -10,7 +10,7 @@ AUTHOR
|
|||
Saxon A. Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
MpegTs.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
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
|
||||
|
@ -26,7 +26,7 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package mpegts
|
||||
package mts
|
||||
|
||||
const (
|
||||
mpegTsSize = 188
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
NAME
|
||||
mpegts_test.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Dan Kortschak <dan@ausocean.org>
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
mpegts_test.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 http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package pes
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
dataLength = 3 // bytes
|
||||
)
|
||||
|
||||
func TestPesToByteSlice(t *testing.T) {
|
||||
pkt := Packet{
|
||||
StreamID: 0xE0, // StreamID
|
||||
PDI: byte(2),
|
||||
PTS: 100000,
|
||||
HeaderLength: byte(10),
|
||||
Stuff: []byte{0xFF, 0xFF},
|
||||
Data: []byte{0xEA, 0x4B, 0x12},
|
||||
}
|
||||
got := pkt.Bytes()
|
||||
want := []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
|
||||
0x00, // PES packet length byte 2
|
||||
0x80, // Marker bits,ScramblingControl, Priority, DAI, Copyright, Original
|
||||
0x80, // PDI, ESCR, ESRate, DSMTrickMode, ACI, CRC, Ext
|
||||
10, // header length
|
||||
0x21, // PCR byte 1
|
||||
0x00, // pcr byte 2
|
||||
0x07, // pcr byte 3
|
||||
0x0D, // pcr byte 4
|
||||
0x41, // pcr byte 5
|
||||
0xFF, // Stuffing byte 1
|
||||
0xFF, // stuffing byte 3
|
||||
0xEA, // data byte 1
|
||||
0x4B, // data byte 2
|
||||
0x12, // data byte 3
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected packet encoding:\ngot: %#v\nwant:%#v", got, want)
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
RtpToTsConverter.go - provides utilities for the conversion of Rtp packets
|
||||
to equivalent MpegTs packets.
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
RtpToTsConverter.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 itut
|
||||
|
||||
func StartCode1() []byte { return []byte{0x00, 0x00, 0x01} }
|
||||
func StartCode2() []byte { return []byte{0x00, 0x00, 0x00, 0x01} }
|
||||
func AUD() []byte { return []byte{0x09, 0xF0} }
|
|
@ -1,69 +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 mpegts
|
||||
|
||||
import "testing"
|
||||
|
||||
// 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!: %v", err)
|
||||
}
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
139
parser/h264.go
139
parser/h264.go
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
h264.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
h264.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 http://www.gnu.org/licenses.
|
||||
*/
|
||||
package parser
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/itut"
|
||||
)
|
||||
|
||||
const (
|
||||
inputChanSize = 100000
|
||||
outputBufferSize = 10000
|
||||
)
|
||||
|
||||
// H264 provides properties and methods to allow for the parsing of a
|
||||
// h264 stream - i.e. to allow extraction of the individual access units
|
||||
type H264 struct {
|
||||
inputBuffer []byte
|
||||
isParsing bool
|
||||
parserOutputChanRef chan []byte
|
||||
userOutputChanRef chan []byte
|
||||
inputChan chan byte
|
||||
delay uint
|
||||
}
|
||||
|
||||
// NewH264Parser returns an instance of the H264 struct
|
||||
func NewH264Parser() (p *H264) {
|
||||
p = new(H264)
|
||||
p.isParsing = true
|
||||
p.inputChan = make(chan byte, inputChanSize)
|
||||
p.delay = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Stop simply sets the isParsing flag to false to indicate to the parser that
|
||||
// we don't want to interpret incoming data anymore - this will also make the
|
||||
// parser jump out of the parse func
|
||||
func (p *H264) Stop() {
|
||||
p.isParsing = false
|
||||
}
|
||||
|
||||
// Start starts the parse func as a goroutine so that incoming data is interpreted
|
||||
func (p *H264) Start() {
|
||||
p.isParsing = true
|
||||
go p.parse()
|
||||
}
|
||||
|
||||
// SetDelay sets a delay inbetween each buffer output. Useful if we're parsing
|
||||
// a file but want to replicate the speed of incoming video frames from a
|
||||
// camera
|
||||
func (p *H264) SetDelay(delay uint) {
|
||||
p.delay = delay
|
||||
}
|
||||
|
||||
// InputChan returns a handle to the input channel of the parser
|
||||
func (p *H264) InputChan() chan byte {
|
||||
return p.inputChan
|
||||
}
|
||||
|
||||
// OutputChan returns a handle to the output chan of the parser
|
||||
func (p *H264) OutputChan() <-chan []byte {
|
||||
return p.userOutputChanRef
|
||||
}
|
||||
|
||||
// SetOutputChan sets the parser output chan to the passed output chan. This is
|
||||
// useful if we want the parser output to go directly to a generator of some sort
|
||||
// for packetization.
|
||||
func (p *H264) SetOutputChan(o chan []byte) {
|
||||
p.parserOutputChanRef = o
|
||||
p.userOutputChanRef = o
|
||||
}
|
||||
|
||||
// parse interprets an incoming h264 stream and extracts individual frames
|
||||
// aka access units
|
||||
func (p *H264) parse() {
|
||||
outputBuffer := make([]byte, 0, outputBufferSize)
|
||||
searchingForEnd := false
|
||||
for p.isParsing {
|
||||
var aByte uint8
|
||||
if p.isParsing {
|
||||
aByte = <-p.inputChan
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
outputBuffer = append(outputBuffer, aByte)
|
||||
for i := 1; aByte == 0x00 && i != 4; i++ {
|
||||
if p.isParsing {
|
||||
aByte = <-p.inputChan
|
||||
} else {
|
||||
return
|
||||
}
|
||||
outputBuffer = append(outputBuffer, aByte)
|
||||
if (aByte == 0x01 && i == 2) || (aByte == 0x01 && i == 3) {
|
||||
if searchingForEnd {
|
||||
output := append(append(itut.StartCode1(), itut.AUD()...), outputBuffer[:len(outputBuffer)-(i+1)]...)
|
||||
time.Sleep(time.Duration(p.delay) * time.Millisecond)
|
||||
p.parserOutputChanRef <- output
|
||||
outputBuffer = outputBuffer[len(outputBuffer)-1-i:]
|
||||
searchingForEnd = false
|
||||
}
|
||||
if p.isParsing {
|
||||
aByte = <-p.inputChan
|
||||
} else {
|
||||
return
|
||||
}
|
||||
outputBuffer = append(outputBuffer, aByte)
|
||||
if nalType := aByte & 0x1F; nalType == 1 || nalType == 5 || nalType == 8 || nalType == 7 {
|
||||
searchingForEnd = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
mjpeg.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
mjpeg.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 http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package parser
|
||||
|
||||
const frameStartCode = 0xD8
|
||||
|
||||
type MJPEG struct {
|
||||
inputBuffer []byte
|
||||
isParsing bool
|
||||
parserOutputChanRef chan []byte
|
||||
userOutputChanRef chan []byte
|
||||
inputChan chan byte
|
||||
delay uint
|
||||
}
|
||||
|
||||
func NewMJPEGParser(inputChanLen int) (p *MJPEG) {
|
||||
p = new(MJPEG)
|
||||
p.isParsing = true
|
||||
p.inputChan = make(chan byte, inputChanLen)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *MJPEG) Stop() {
|
||||
p.isParsing = false
|
||||
}
|
||||
|
||||
func (p *MJPEG) Start() {
|
||||
go p.parse()
|
||||
}
|
||||
|
||||
func (p *MJPEG) SetDelay(delay uint) {
|
||||
p.delay = delay
|
||||
}
|
||||
|
||||
func (p *MJPEG) InputChan() chan byte {
|
||||
return p.inputChan
|
||||
}
|
||||
|
||||
func (p *MJPEG) OutputChan() <-chan []byte {
|
||||
return p.userOutputChanRef
|
||||
}
|
||||
|
||||
func (p *MJPEG) SetOutputChan(o chan []byte) {
|
||||
p.parserOutputChanRef = o
|
||||
p.userOutputChanRef = o
|
||||
}
|
||||
|
||||
func (p *MJPEG) parse() {
|
||||
var outputBuffer []byte
|
||||
for p.isParsing {
|
||||
aByte := <-p.inputChan
|
||||
outputBuffer = append(outputBuffer, aByte)
|
||||
if aByte == 0xFF && len(outputBuffer) != 0 {
|
||||
aByte := <-p.inputChan
|
||||
outputBuffer = append(outputBuffer, aByte)
|
||||
if aByte == frameStartCode {
|
||||
p.parserOutputChanRef <- outputBuffer[:len(outputBuffer)-2]
|
||||
outputBuffer = outputBuffer[len(outputBuffer)-2:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
parser.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon.milton@gmail.com>
|
||||
|
||||
LICENSE
|
||||
parser.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 http://www.gnu.org/licensesx).
|
||||
*/
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// h264 consts
|
||||
const acceptedLength = 1000
|
||||
|
||||
var (
|
||||
Info *log.Logger
|
||||
mutex *sync.Mutex
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
Stop()
|
||||
Start()
|
||||
InputChan() chan byte
|
||||
OutputChan() <-chan []byte
|
||||
SetOutputChan(achan chan []byte)
|
||||
SetDelay(delay uint)
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
NAME
|
||||
parser_test.go
|
||||
|
||||
DESCRIPTION
|
||||
See Readme.md
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
parser_test.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 http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
mjpegInputFileName = "testInput/testInput.avi"
|
||||
h264fName = "../../test/test-data/av/input/betterInput.h264"
|
||||
nOfFrames = 4
|
||||
outChanSize = 100
|
||||
)
|
||||
|
||||
func TestH264Parser(t *testing.T) {
|
||||
log.SetOutput(os.Stderr)
|
||||
log.Println("Opening input file!")
|
||||
inputFile, err := os.Open(h264fName)
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got error opening file!")
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Reading data from file!")
|
||||
data, err := ioutil.ReadAll(inputFile)
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got read error!")
|
||||
return
|
||||
}
|
||||
|
||||
// Create 'parser' and start it up
|
||||
log.Println("Creating parser!")
|
||||
parser := NewH264Parser()
|
||||
parser.SetOutputChan(make(chan []byte, outChanSize))
|
||||
parser.Start()
|
||||
|
||||
for i, n := 0, 0; n <= nOfFrames; i++ {
|
||||
select {
|
||||
case parser.InputChan() <- data[i]:
|
||||
case frame := <-parser.OutputChan():
|
||||
path := fmt.Sprintf("testOutput/" + strconv.Itoa(n) + "h264_frame")
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error creating %q: %v", path, err)
|
||||
return
|
||||
}
|
||||
out.Write(frame)
|
||||
out.Close()
|
||||
n++
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestMJPEGParser(t *testing.T) {
|
||||
log.Println("Opening input file!")
|
||||
// Open the input file
|
||||
inputFile, err := os.Open(testInputFileName)
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got error opening file!")
|
||||
}
|
||||
log.Println("Getting file stats!")
|
||||
stats, err := inputFile.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not get input file stats!")
|
||||
return
|
||||
}
|
||||
log.Println("Creating space for file data!")
|
||||
data := make([]byte, stats.Size())
|
||||
_, err = inputFile.Read(data)
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got read error!")
|
||||
return
|
||||
}
|
||||
log.Println("Creating parser!")
|
||||
parser := NewMJPEGParser(len(data) + 1)
|
||||
parser.SetOutputChan(make(chan []byte, 10000))
|
||||
parser.Start()
|
||||
log.Printf("len(data): %v\n", len(data))
|
||||
for i := range data {
|
||||
parser.GetInputChan() <- data[i]
|
||||
}
|
||||
log.Println("Writing jpegs to files!")
|
||||
for i := 0; len(parser.GetOutputChan()) > 0; i++ {
|
||||
// Open a new output file
|
||||
out, err := os.Create("testOutput/image" + strconv.Itoa(i) + ".jpeg")
|
||||
if err != nil {
|
||||
t.Errorf("Should not have got error creating output file!")
|
||||
return
|
||||
}
|
||||
out.Write(<-parser.GetOutputChan())
|
||||
out.Close()
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,76 +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 http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package pes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
dataLength = 3 // bytes
|
||||
)
|
||||
|
||||
func TestPesToByteSlice(t *testing.T) {
|
||||
pesPkt := PESPacket{
|
||||
StreamID: 0xE0, // StreamID
|
||||
PDI: byte(2),
|
||||
PTS: 100000,
|
||||
HeaderLength: byte(10),
|
||||
Stuff: []byte{0xFF, 0xFF},
|
||||
Data: []byte{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
|
||||
0x00, // PES packet length byte 2
|
||||
0x80, // Marker bits,ScramblingControl, Priority, DAI, Copyright, Original
|
||||
0x80, // PDI, ESCR, ESRate, DSMTrickMode, ACI, CRC, Ext
|
||||
byte(10), // header length
|
||||
0x21, // PCR byte 1
|
||||
0x00, // pcr byte 2
|
||||
0x07, // pcr byte 3
|
||||
0x0D, // pcr byte 4
|
||||
0x41, // pcr byte 5
|
||||
0xFF, // Stuffing byte 1
|
||||
0xFF, // stuffing byte 3
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,9 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/generator"
|
||||
"bitbucket.org/ausocean/av/encoding"
|
||||
"bitbucket.org/ausocean/av/encoding/flv"
|
||||
"bitbucket.org/ausocean/av/encoding/mts"
|
||||
"bitbucket.org/ausocean/av/parse"
|
||||
"bitbucket.org/ausocean/av/rtmp"
|
||||
"bitbucket.org/ausocean/iot/pi/netsender"
|
||||
|
@ -88,7 +90,7 @@ type Revid struct {
|
|||
ringBuffer *ring.Buffer
|
||||
config Config
|
||||
isRunning bool
|
||||
generator generator.Generator
|
||||
encoder encoding.Encoder
|
||||
parse func(dst io.Writer, src io.Reader, delay time.Duration) error
|
||||
cmd *exec.Cmd
|
||||
inputReader io.ReadCloser
|
||||
|
@ -209,11 +211,11 @@ func (r *Revid) reset(config Config) error {
|
|||
case Mpegts:
|
||||
r.Log(Info, "Using MPEGTS packetisation")
|
||||
frameRate, _ := strconv.Atoi(r.config.FrameRate)
|
||||
r.generator = generator.NewTsGenerator(float64(frameRate))
|
||||
r.encoder = mts.NewTsGenerator(float64(frameRate))
|
||||
case Flv:
|
||||
r.Log(Info, "Using FLV packetisation")
|
||||
frameRate, _ := strconv.Atoi(r.config.FrameRate)
|
||||
r.generator = generator.NewFlvGenerator(true, true, frameRate)
|
||||
r.encoder = flv.NewFlvGenerator(true, true, frameRate)
|
||||
}
|
||||
// We have packetization of some sort, so we want to send data to Generator
|
||||
// to perform packetization
|
||||
|
@ -255,8 +257,8 @@ func (r *Revid) Start() {
|
|||
go r.outputClips()
|
||||
r.Log(Info, "Starting clip packing routine")
|
||||
go r.packClips()
|
||||
r.Log(Info, "Starting packetisation generator")
|
||||
r.generator.Start()
|
||||
r.Log(Info, "Starting packetisation encoder")
|
||||
r.encoder.Start()
|
||||
r.Log(Info, "Setting up input and receiving content")
|
||||
go r.setupInput()
|
||||
}
|
||||
|
@ -271,9 +273,9 @@ func (r *Revid) Stop() {
|
|||
r.Log(Info, "Stopping revid!")
|
||||
r.isRunning = false
|
||||
|
||||
r.Log(Info, "Stopping generator!")
|
||||
if r.generator != nil {
|
||||
r.generator.Stop()
|
||||
r.Log(Info, "Stopping encoder!")
|
||||
if r.encoder != nil {
|
||||
r.encoder.Stop()
|
||||
}
|
||||
|
||||
r.Log(Info, "Killing input proccess!")
|
||||
|
@ -284,15 +286,15 @@ func (r *Revid) Stop() {
|
|||
}
|
||||
|
||||
// getFrameNoPacketization gets a frame directly from the revid output chan
|
||||
// as we don't need to go through the generator with no packetization settings
|
||||
// as we don't need to go through the encoder with no packetization settings
|
||||
func (r *Revid) getFrameNoPacketization() []byte {
|
||||
return <-r.outputChan
|
||||
}
|
||||
|
||||
// getFramePacketization gets a frame from the generators output chan - the
|
||||
// the generator being an mpegts or flv generator depending on the config
|
||||
// the encoder being an mpegts or flv encoder depending on the config
|
||||
func (r *Revid) getFramePacketization() []byte {
|
||||
return <-r.generator.OutputChan()
|
||||
return <-r.encoder.OutputChan()
|
||||
}
|
||||
|
||||
// packClips takes data segments; whether that be tsPackets or mjpeg frames and
|
||||
|
@ -304,7 +306,7 @@ func (r *Revid) packClips() {
|
|||
select {
|
||||
// TODO: This is temporary, need to work out how to make this work
|
||||
// for cases when there is not packetisation.
|
||||
case frame := <-r.generator.OutputChan():
|
||||
case frame := <-r.encoder.OutputChan():
|
||||
lenOfFrame := len(frame)
|
||||
if lenOfFrame > ringBufferElementSize {
|
||||
r.Log(Warning, fmt.Sprintf("Frame was too big: %v bytes, getting another one!", lenOfFrame))
|
||||
|
@ -475,7 +477,7 @@ func (r *Revid) startRaspivid() error {
|
|||
r.inputReader = stdout
|
||||
go func() {
|
||||
r.Log(Info, "Reading camera data!")
|
||||
r.parse(chunkWriter(r.generator.InputChan()), r.inputReader, 0)
|
||||
r.parse(chunkWriter(r.encoder.InputChan()), r.inputReader, 0)
|
||||
r.Log(Info, "Not trying to read from camera anymore!")
|
||||
}()
|
||||
return nil
|
||||
|
@ -498,7 +500,7 @@ func (r *Revid) setupInputForFile() error {
|
|||
defer f.Close()
|
||||
|
||||
// TODO(kortschak): Maybe we want a context.Context-aware parser that we can stop.
|
||||
return r.parse(chunkWriter(r.generator.InputChan()), f, delay)
|
||||
return r.parse(chunkWriter(r.encoder.InputChan()), f, delay)
|
||||
}
|
||||
|
||||
// chunkWriter is a shim between the new function-based approach
|
||||
|
|
Loading…
Reference in New Issue