mirror of https://bitbucket.org/ausocean/av.git
Merge branch 'master' into revid-audio
This commit is contained in:
commit
e3ba1e43f3
|
@ -111,7 +111,6 @@ func handleFlags() revid.Config {
|
|||
inputCodecPtr = flag.String("InputCodec", "", "The codec of the input: H264, Mjpeg, PCM, ADPCM")
|
||||
inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP")
|
||||
rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.")
|
||||
rtmpMethodPtr = flag.String("RtmpMethod", "", "The method used to send over rtmp: Ffmpeg, Librtmp")
|
||||
quantizePtr = flag.Bool("Quantize", false, "Quantize input (non-variable bitrate)")
|
||||
verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal")
|
||||
rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: <IP>:<port> (port is generally 6970-6999)")
|
||||
|
@ -121,7 +120,6 @@ func handleFlags() revid.Config {
|
|||
outputPathPtr = flag.String("OutputPath", "", "The directory of the output file")
|
||||
inputFilePtr = flag.String("InputPath", "", "The directory of the input file")
|
||||
httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts")
|
||||
sendRetryPtr = flag.Bool("retry", false, "Specify whether a failed send should be retried.")
|
||||
verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No")
|
||||
horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No")
|
||||
framesPerClipPtr = flag.Uint("FramesPerClip", 0, "Number of frames per clip sent")
|
||||
|
@ -223,27 +221,17 @@ func handleFlags() revid.Config {
|
|||
case "File":
|
||||
cfg.Outputs = append(cfg.Outputs, revid.File)
|
||||
case "Http":
|
||||
cfg.Outputs = append(cfg.Outputs, revid.Http)
|
||||
cfg.Outputs = append(cfg.Outputs, revid.HTTP)
|
||||
case "Rtmp":
|
||||
cfg.Outputs = append(cfg.Outputs, revid.Rtmp)
|
||||
cfg.Outputs = append(cfg.Outputs, revid.RTMP)
|
||||
case "Rtp":
|
||||
cfg.Outputs = append(cfg.Outputs, revid.Rtp)
|
||||
cfg.Outputs = append(cfg.Outputs, revid.RTP)
|
||||
case "":
|
||||
default:
|
||||
log.Log(logger.Error, pkg+"bad output argument", "arg", o)
|
||||
}
|
||||
}
|
||||
|
||||
switch *rtmpMethodPtr {
|
||||
case "Ffmpeg":
|
||||
cfg.RtmpMethod = revid.Ffmpeg
|
||||
case "LibRtmp":
|
||||
cfg.RtmpMethod = revid.LibRtmp
|
||||
case "":
|
||||
default:
|
||||
log.Log(logger.Error, pkg+"bad rtmp method argument")
|
||||
}
|
||||
|
||||
if *configFilePtr != "" {
|
||||
netsender.ConfigFile = *configFilePtr
|
||||
}
|
||||
|
@ -254,18 +242,17 @@ func handleFlags() revid.Config {
|
|||
cfg.FlipHorizontal = *horizontalFlipPtr
|
||||
cfg.FlipVertical = *verticalFlipPtr
|
||||
cfg.FramesPerClip = *framesPerClipPtr
|
||||
cfg.RtmpUrl = *rtmpUrlPtr
|
||||
cfg.RTMPURL = *rtmpUrlPtr
|
||||
cfg.Bitrate = *bitratePtr
|
||||
cfg.OutputPath = *outputPathPtr
|
||||
cfg.InputPath = *inputFilePtr
|
||||
cfg.Height = *heightPtr
|
||||
cfg.Width = *widthPtr
|
||||
cfg.FrameRate = *frameRatePtr
|
||||
cfg.HttpAddress = *httpAddressPtr
|
||||
cfg.HTTPAddress = *httpAddressPtr
|
||||
cfg.Quantization = *quantizationPtr
|
||||
cfg.IntraRefreshPeriod = *intraRefreshPeriodPtr
|
||||
cfg.RtpAddress = *rtpAddrPtr
|
||||
cfg.SendRetry = *sendRetryPtr
|
||||
cfg.RTPAddress = *rtpAddrPtr
|
||||
cfg.Brightness = *brightnessPtr
|
||||
cfg.Saturation = *saturationPtr
|
||||
cfg.Exposure = *exposurePtr
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
NAME
|
||||
extract.go
|
||||
|
||||
DESCRIPTION
|
||||
extract.go provides an extracter to get access units from an RTP stream.
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
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 h264
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/protocol/rtp"
|
||||
)
|
||||
|
||||
// NAL types (from https://tools.ietf.org/html/rfc6184#page-13)
|
||||
const (
|
||||
// Single nal units bounds.
|
||||
typeSingleNALULowBound = 1
|
||||
typeSingleNALUHighBound = 23
|
||||
|
||||
// Single-time aggregation packets.
|
||||
typeSTAPA = 24
|
||||
typeSTAPB = 25
|
||||
|
||||
// Multi-time aggregation packets.
|
||||
typeMTAP16 = 26
|
||||
typeMTAP24 = 27
|
||||
|
||||
// Fragmentation packets.
|
||||
typeFUA = 28
|
||||
typeFUB = 29
|
||||
)
|
||||
|
||||
// Min NAL lengths.
|
||||
const (
|
||||
minSingleNALLen = 1
|
||||
minSTAPALen = 4
|
||||
minFUALen = 2
|
||||
)
|
||||
|
||||
// Buffer sizes.
|
||||
const (
|
||||
maxAUSize = 100000 // Max access unit size in bytes.
|
||||
maxRTPSize = 1500 // Max ethernet transmission unit in bytes.
|
||||
)
|
||||
|
||||
// Extracter is an extracter for extracting H264 access units from RTP stream.
|
||||
type Extracter struct {
|
||||
buf *bytes.Buffer // Holds the current access unit.
|
||||
frag bool // Indicates if we're currently dealing with a fragmentation packet.
|
||||
}
|
||||
|
||||
// NewExtracter returns a new Extracter.
|
||||
func NewExtracter() *Extracter {
|
||||
return &Extracter{
|
||||
buf: bytes.NewBuffer(make([]byte, 0, maxAUSize))}
|
||||
}
|
||||
|
||||
// Extract extracts H264 access units from an RTP stream. This function
|
||||
// expects that each read from src will provide a single RTP packet.
|
||||
func (e *Extracter) Extract(dst io.Writer, src io.Reader, delay time.Duration) error {
|
||||
buf := make([]byte, maxRTPSize)
|
||||
for {
|
||||
n, err := src.Read(buf)
|
||||
switch err {
|
||||
case nil: // Do nothing.
|
||||
case io.EOF:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("source read error: %v\n", err)
|
||||
}
|
||||
|
||||
// Get payload from RTP packet.
|
||||
payload, err := rtp.Payload(buf[:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get RTP payload, failed with err: %v\n", err)
|
||||
}
|
||||
|
||||
nalType := payload[0] & 0x1f
|
||||
|
||||
// If not currently fragmented then we ignore current write.
|
||||
if e.frag && nalType != typeFUA {
|
||||
e.buf.Reset()
|
||||
e.frag = false
|
||||
continue
|
||||
}
|
||||
|
||||
if typeSingleNALULowBound <= nalType && nalType <= typeSingleNALUHighBound {
|
||||
// If len too small, ignore.
|
||||
if len(payload) < minSingleNALLen {
|
||||
continue
|
||||
}
|
||||
e.writeWithPrefix(payload)
|
||||
} else {
|
||||
switch nalType {
|
||||
case typeSTAPA:
|
||||
e.handleSTAPA(payload)
|
||||
case typeFUA:
|
||||
e.handleFUA(payload)
|
||||
case typeSTAPB:
|
||||
panic("STAP-B type unsupported")
|
||||
case typeMTAP16:
|
||||
panic("MTAP16 type unsupported")
|
||||
case typeMTAP24:
|
||||
panic("MTAP24 type unsupported")
|
||||
case typeFUB:
|
||||
panic("FU-B type unsupported")
|
||||
default:
|
||||
panic("unsupported type")
|
||||
}
|
||||
}
|
||||
|
||||
markerIsSet, err := rtp.Marker(buf[:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get marker bit, failed with err: %v\n", err)
|
||||
}
|
||||
|
||||
if markerIsSet {
|
||||
e.buf.WriteTo(dst)
|
||||
e.buf.Reset()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSTAPA parses NAL units from an aggregation packet and writes
|
||||
// them to the Extracter's buffer buf.
|
||||
func (e *Extracter) handleSTAPA(d []byte) {
|
||||
// If the length is too small, ignore.
|
||||
if len(d) < minSTAPALen {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 1; i < len(d); {
|
||||
size := int(binary.BigEndian.Uint16(d[i:]))
|
||||
|
||||
// Skip over NAL unit size.
|
||||
const sizeOfFieldLen = 2
|
||||
i += sizeOfFieldLen
|
||||
|
||||
// Get the NALU.
|
||||
nalu := d[i : i+size]
|
||||
i += size
|
||||
e.writeWithPrefix(nalu)
|
||||
}
|
||||
}
|
||||
|
||||
// handleFUA parses NAL units from fragmentation packets and writes
|
||||
// them to the Extracter's buf.
|
||||
func (e *Extracter) handleFUA(d []byte) {
|
||||
// If length is too small, ignore.
|
||||
if len(d) < minFUALen {
|
||||
return
|
||||
}
|
||||
|
||||
// Get start and end indiciators from FU header.
|
||||
const FUHeadIdx = 1
|
||||
start := d[FUHeadIdx]&0x80 != 0
|
||||
end := d[FUHeadIdx]&0x40 != 0
|
||||
|
||||
// If start, form new header, skip FU indicator only and set first byte to
|
||||
// new header. Otherwise, skip over both FU indicator and FU header.
|
||||
if start {
|
||||
newHead := (d[0] & 0xe0) | (d[1] & 0x1f)
|
||||
d = d[1:]
|
||||
d[0] = newHead
|
||||
if end {
|
||||
panic("bad fragmentation packet")
|
||||
}
|
||||
e.frag = true
|
||||
e.writeWithPrefix(d)
|
||||
} else {
|
||||
d = d[2:]
|
||||
if end {
|
||||
e.frag = false
|
||||
}
|
||||
e.writeNoPrefix(d)
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a NAL unit to the Extracter's buf in byte stream format using the
|
||||
// start code.
|
||||
func (e *Extracter) writeWithPrefix(d []byte) {
|
||||
const prefix = "\x00\x00\x00\x01"
|
||||
e.buf.Write([]byte(prefix))
|
||||
e.buf.Write(d)
|
||||
}
|
||||
|
||||
// writeNoPrefix writes data to the Extracter's buf. This is used for non start
|
||||
// fragmentations of a NALU.
|
||||
func (e *Extracter) writeNoPrefix(d []byte) {
|
||||
e.buf.Write(d)
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
NAME
|
||||
extract_test.go
|
||||
|
||||
DESCRIPTION
|
||||
extract_test.go provides tests for the extracter in extract.go
|
||||
|
||||
AUTHOR
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
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 h264
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// rtpReader provides an io.Reader for reading the test RTP stream.
|
||||
type rtpReader struct {
|
||||
packets [][]byte
|
||||
idx int
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (r *rtpReader) Read(p []byte) (int, error) {
|
||||
if r.idx == len(r.packets) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b := r.packets[r.idx]
|
||||
n := copy(p, b)
|
||||
if n < len(r.packets[r.idx]) {
|
||||
r.packets[r.idx] = r.packets[r.idx][n:]
|
||||
} else {
|
||||
r.idx++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// destination holds the access units extracted during the lexing process.
|
||||
type destination [][]byte
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (d *destination) Write(p []byte) (int, error) {
|
||||
tmp := make([]byte, len(p))
|
||||
copy(tmp, p)
|
||||
*d = append(*d, tmp)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// TestLex checks that the Lexer can correctly extract H264 access units from
|
||||
// h264 RTP stream in RTP payload format.
|
||||
func TestRTPLex(t *testing.T) {
|
||||
const rtpVer = 2
|
||||
|
||||
tests := []struct {
|
||||
packets [][]byte
|
||||
expect [][]byte
|
||||
}{
|
||||
{
|
||||
packets: [][]byte{
|
||||
{ // Single NAL unit.
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL Data.
|
||||
},
|
||||
{ // Fragmentation (start packet).
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeFUA, // FU indicator.
|
||||
0x80 | typeSingleNALULowBound, // FU header.
|
||||
0x01, 0x02, 0x03, // FU payload.
|
||||
},
|
||||
{ // Fragmentation (middle packet)
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeFUA, // NAL indicator.
|
||||
typeSingleNALULowBound, // FU header.
|
||||
0x04, 0x05, 0x06, // FU payload.
|
||||
},
|
||||
{ // Fragmentation (end packet)
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeFUA, // NAL indicator.
|
||||
0x40 | typeSingleNALULowBound, // FU header.
|
||||
0x07, 0x08, 0x09, // FU payload
|
||||
},
|
||||
|
||||
{ // Aggregation. Make last packet of access unit => marker bit true.
|
||||
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeSTAPA, // NAL header.
|
||||
0x00, 0x04, // NAL 1 size.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL 1 data.
|
||||
0x00, 0x04, // NAL 2 size.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL 2 data.
|
||||
},
|
||||
// Second access unit.
|
||||
{ // Single NAL unit.
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL Data.
|
||||
},
|
||||
{ // Single NAL. Make last packet of access unit => marker bit true.
|
||||
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTP header.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL data.
|
||||
},
|
||||
},
|
||||
expect: [][]byte{
|
||||
// First access unit.
|
||||
{
|
||||
// NAL 1
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL data.
|
||||
// NAL 2
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
typeSingleNALULowBound,
|
||||
0x01, 0x02, 0x03, // FU payload.
|
||||
0x04, 0x05, 0x06, // FU payload.
|
||||
0x07, 0x08, 0x09, // FU payload.
|
||||
// NAL 3
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL data.
|
||||
// NAL 4
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x01, 0x02, 0x03, 0x04, // NAL 2 data
|
||||
},
|
||||
// Second access unit.
|
||||
{
|
||||
// NAL 1
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // Data.
|
||||
// NAL 2
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
typeSingleNALULowBound, // NAL header.
|
||||
0x01, 0x02, 0x03, 0x04, // Data.
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testNum, test := range tests {
|
||||
r := &rtpReader{packets: test.packets}
|
||||
d := &destination{}
|
||||
err := NewExtracter().Extract(d, r, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("error lexing: %v\n", err)
|
||||
}
|
||||
|
||||
for i, accessUnit := range test.expect {
|
||||
for j, part := range accessUnit {
|
||||
if part != [][]byte(*d)[i][j] {
|
||||
t.Fatalf("did not get expected data for test: %v.\nGot: %v\nWant: %v\n", testNum, d, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ DESCRIPTION
|
|||
|
||||
AUTHOR
|
||||
Dan Kortschak <dan@ausocean.org>
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
lex.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
@ -25,8 +26,8 @@ LICENSE
|
|||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
// lex.go provides a lexer to lex h264 bytestream into access units.
|
||||
|
||||
// Package h264 provides a h264 bytestream lexer and RTP H264 access unit
|
||||
// extracter.
|
||||
package h264
|
||||
|
||||
import (
|
||||
|
@ -44,9 +45,9 @@ func init() {
|
|||
|
||||
var h264Prefix = [...]byte{0x00, 0x00, 0x01, 0x09, 0xf0}
|
||||
|
||||
// Lex lexes H.264 NAL units read from src into separate writes to dst with
|
||||
// successive writes being performed not earlier than the specified delay.
|
||||
// NAL units are split after type 1 (Coded slice of a non-IDR picture), 5
|
||||
// Lex lexes H.264 NAL units read from src into separate writes
|
||||
// to dst with successive writes being performed not earlier than the specified
|
||||
// delay. NAL units are split after type 1 (Coded slice of a non-IDR picture), 5
|
||||
// (Coded slice of a IDR picture) and 8 (Picture parameter set).
|
||||
func Lex(dst io.Writer, src io.Reader, delay time.Duration, n int) error {
|
||||
var tick <-chan time.Time
|
||||
|
|
|
@ -7,6 +7,7 @@ DESCRIPTION
|
|||
|
||||
AUTHOR
|
||||
Dan Kortschak <dan@ausocean.org>
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
lex_test.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
|
|
231
revid/config.go
231
revid/config.go
|
@ -32,56 +32,6 @@ import (
|
|||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
// Config provides parameters relevant to a revid instance. A new config must
|
||||
// be passed to the constructor.
|
||||
type Config struct {
|
||||
Logger Logger
|
||||
LogLevel int8
|
||||
|
||||
// IO
|
||||
Input uint8
|
||||
InputCodec uint8
|
||||
Outputs []uint8
|
||||
RtmpMethod uint8
|
||||
Packetization uint8
|
||||
Quantize bool // Determines whether input to revid will have constant or variable bitrate.
|
||||
RtmpUrl string
|
||||
RTSPURL string
|
||||
Bitrate uint
|
||||
OutputPath string
|
||||
InputPath string
|
||||
HttpAddress string
|
||||
Quantization uint
|
||||
IntraRefreshPeriod uint
|
||||
RtpAddress string
|
||||
SendRetry bool
|
||||
WriteRate float64 // How many times a second revid encoders will be written to.
|
||||
|
||||
// Video
|
||||
Height uint
|
||||
Width uint
|
||||
FrameRate uint
|
||||
FramesPerClip uint
|
||||
BurstPeriod uint
|
||||
|
||||
// Transformation
|
||||
FlipHorizontal bool
|
||||
FlipVertical bool
|
||||
Rotation uint
|
||||
|
||||
// Color correction
|
||||
Brightness uint
|
||||
Saturation int
|
||||
Exposure string
|
||||
AutoWhiteBalance string
|
||||
|
||||
// Audio
|
||||
SampleRate int // Samples a second (Hz).
|
||||
RecPeriod float64 // How many seconds to record at a time.
|
||||
Channels int // Number of audio channels, 1 for mono, 2 for stereo.
|
||||
BitDepth int // Sample bit depth.
|
||||
}
|
||||
|
||||
// Possible modes for raspivid --exposure parameter.
|
||||
var ExposureModes = [...]string{
|
||||
"auto",
|
||||
|
@ -112,36 +62,36 @@ var AutoWhiteBalanceModes = [...]string{
|
|||
"horizon",
|
||||
}
|
||||
|
||||
// Enums for config struct
|
||||
// Enums to define inputs, outputs and codecs.
|
||||
const (
|
||||
// Indicates no option has been set.
|
||||
NothingDefined = iota
|
||||
|
||||
// Input/Output.
|
||||
File
|
||||
|
||||
// Inputs.
|
||||
Raspivid
|
||||
V4L
|
||||
Audio
|
||||
File
|
||||
Http
|
||||
None
|
||||
Mpegts
|
||||
Ffmpeg
|
||||
Flv
|
||||
LibRtmp
|
||||
QuantizationOn
|
||||
QuantizationOff
|
||||
Yes
|
||||
No
|
||||
Rtmp
|
||||
FfmpegRtmp
|
||||
Udp
|
||||
MpegtsRtp
|
||||
Rtp
|
||||
RTSP
|
||||
Audio
|
||||
|
||||
// Outputs.
|
||||
RTMP
|
||||
RTP
|
||||
HTTP
|
||||
MPEGTS
|
||||
|
||||
// Codecs.
|
||||
H264
|
||||
H265
|
||||
MJPEG
|
||||
)
|
||||
|
||||
// Default config settings
|
||||
const (
|
||||
defaultInput = Raspivid
|
||||
defaultOutput = Http
|
||||
defaultPacketization = Flv
|
||||
defaultOutput = HTTP
|
||||
defaultFrameRate = 25
|
||||
defaultWriteRate = 25
|
||||
defaultWidth = 1280
|
||||
|
@ -150,7 +100,6 @@ const (
|
|||
defaultTimeout = 0
|
||||
defaultQuantization = 40
|
||||
defaultBitrate = 400000
|
||||
defaultQuantizationMode = QuantizationOff
|
||||
defaultFramesPerClip = 1
|
||||
httpFramesPerClip = 560
|
||||
defaultInputCodec = codecutil.H264
|
||||
|
@ -169,6 +118,129 @@ const (
|
|||
defaultRecPeriod = 1.0
|
||||
)
|
||||
|
||||
// Config provides parameters relevant to a revid instance. A new config must
|
||||
// be passed to the constructor. Default values for these fields are defined
|
||||
// as consts above.
|
||||
type Config struct {
|
||||
// LogLevel is the revid logging verbosity level.
|
||||
// Valid values are defined by enums from the logger package: logger.Debug,
|
||||
// logger.Info, logger.Warning logger.Error, logger.Fatal.
|
||||
LogLevel int8
|
||||
|
||||
// Input defines the input data source.
|
||||
//
|
||||
// Valid values are defined by enums:
|
||||
// Raspivid:
|
||||
// Read data from a Raspberry Pi Camera.
|
||||
// V4l:
|
||||
// Read from webcam.
|
||||
// File:
|
||||
// Location must be specified in InputPath field.
|
||||
// RTSP:
|
||||
// RTSPURL must also be defined.
|
||||
Input uint8
|
||||
|
||||
// InputCodec defines the input codec we wish to use, and therefore defines the
|
||||
// lexer for use in the pipeline. This defaults to H264, but H265 is also a
|
||||
// valid option if we expect this from the input.
|
||||
InputCodec uint8
|
||||
|
||||
// Outputs define the outputs we wish to output data too.
|
||||
//
|
||||
// Valid outputs are defined by enums:
|
||||
// File:
|
||||
// Location must be defined by the OutputPath field. MPEG-TS packetization
|
||||
// is used.
|
||||
// HTTP:
|
||||
// Destination is defined by the sh field located in /etc/netsender.conf.
|
||||
// MPEGT-TS packetization is used.
|
||||
// RTMP:
|
||||
// Destination URL must be defined in the RtmpUrl field. FLV packetization
|
||||
// is used.
|
||||
// RTP:
|
||||
// Destination is defined by RtpAddr field, otherwise it will default to
|
||||
// localhost:6970. MPEGT-TS packetization is used.
|
||||
Outputs []uint8
|
||||
|
||||
// Quantize specifies whether the input to revid will have constant or variable
|
||||
// bitrate, if configurable with the chosen input. Raspivid supports quantization.
|
||||
Quantize bool
|
||||
|
||||
// FramesPerClip defines the number of packetization units to pack into a clip
|
||||
// per HTTP send.
|
||||
FramesPerClip uint
|
||||
|
||||
// RTMPURL specifies the Rtmp output destination URL. This must be defined if
|
||||
// RTMP is to be used as an output.
|
||||
RTMPURL string
|
||||
|
||||
// RTSPURL specifies the RTSP server URL for RTSP input. This must be defined
|
||||
// when Input is RTSP.
|
||||
RTSPURL string
|
||||
|
||||
// OutputPath defines the output destination for File output. This must be
|
||||
// defined if File output is to be used.
|
||||
OutputPath string
|
||||
|
||||
// InputPath defines the input file location for File Input. This must be
|
||||
// defined if File input is to be used.
|
||||
InputPath string
|
||||
|
||||
// FrameRate defines the input frame rate if configurable by the chosen input.
|
||||
// Raspivid input supports custom framerate.
|
||||
FrameRate uint
|
||||
|
||||
// WriteRate is how many times a second revid encoders will be written to.
|
||||
WriteRate float64
|
||||
|
||||
// HTTPAddress defines a custom HTTP destination if we do not wish to use that
|
||||
// defined in /etc/netsender.conf.
|
||||
HTTPAddress string
|
||||
|
||||
// Quantization defines the quantization level, which may be a value between
|
||||
// 0-40. This will only take effect if the Quantize field is true and if we
|
||||
// are using Raspivid input.
|
||||
Quantization uint
|
||||
|
||||
// IntraRefreshPeriod defines the frequency of video parameter NAL units for
|
||||
// Raspivid input.
|
||||
IntraRefreshPeriod uint
|
||||
|
||||
// Logger holds an implementation of the Logger interface as defined in revid.go.
|
||||
// This must be set for revid to work correctly.
|
||||
Logger Logger
|
||||
|
||||
// Brightness and saturation define the brightness and saturation levels for
|
||||
// Raspivid input.
|
||||
Brightness uint
|
||||
Saturation int
|
||||
|
||||
// Exposure defines the exposure mode used by the Raspivid input. Valid modes
|
||||
// are defined in the exported []string ExposureModes defined at the start
|
||||
// of the file.
|
||||
Exposure string
|
||||
|
||||
// AutoWhiteBalance defines the auto white balance mode used by Raspivid input.
|
||||
// Valid modes are defined in the exported []string AutoWhiteBalanceModes
|
||||
// defined at the start of the file.
|
||||
AutoWhiteBalance string
|
||||
|
||||
// Audio
|
||||
SampleRate int // Samples a second (Hz).
|
||||
RecPeriod float64 // How many seconds to record at a time.
|
||||
Channels int // Number of audio channels, 1 for mono, 2 for stereo.
|
||||
BitDepth int // Sample bit depth.
|
||||
|
||||
RTPAddress string // RTPAddress defines the RTP output destination.
|
||||
BurstPeriod uint // BurstPeriod defines the revid burst period in seconds.
|
||||
Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input.
|
||||
Height uint // Height defines the input video height Raspivid input.
|
||||
Width uint // Width defines the input video width Raspivid input.
|
||||
Bitrate uint // Bitrate specifies the input bitrate for Raspivid input.
|
||||
FlipHorizontal bool // FlipHorizontal flips video horizontally for Raspivid input.
|
||||
FlipVertical bool // FlipVertial flips video vertically for Raspivid input.
|
||||
}
|
||||
|
||||
// Validate checks for any errors in the config fields and defaults settings
|
||||
// if particular parameters have not been defined.
|
||||
func (c *Config) Validate(r *Revid) error {
|
||||
|
@ -225,28 +297,23 @@ func (c *Config) Validate(r *Revid) error {
|
|||
if c.Outputs == nil {
|
||||
c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput)
|
||||
c.Outputs = append(c.Outputs, defaultOutput)
|
||||
c.Packetization = defaultPacketization
|
||||
} else {
|
||||
for i, o := range c.Outputs {
|
||||
switch o {
|
||||
case File:
|
||||
case Udp:
|
||||
case Rtmp, FfmpegRtmp:
|
||||
if c.RtmpUrl == "" {
|
||||
case RTMP:
|
||||
if c.RTMPURL == "" {
|
||||
c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP")
|
||||
c.Outputs[i] = Http
|
||||
c.Outputs[i] = HTTP
|
||||
// FIXME(kortschak): Does this want the same line as below?
|
||||
// c.FramesPerClip = httpFramesPerClip
|
||||
break
|
||||
}
|
||||
c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for rtmp out", "framesPerClip", defaultFramesPerClip)
|
||||
c.FramesPerClip = defaultFramesPerClip
|
||||
c.Packetization = Flv
|
||||
c.SendRetry = true
|
||||
case Http, Rtp:
|
||||
case HTTP, RTP:
|
||||
c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out", "framesPerClip", httpFramesPerClip)
|
||||
c.FramesPerClip = httpFramesPerClip
|
||||
c.Packetization = Mpegts
|
||||
default:
|
||||
return errors.New("bad output type defined in config")
|
||||
}
|
||||
|
@ -325,8 +392,8 @@ func (c *Config) Validate(r *Revid) error {
|
|||
return errors.New("quantisation is over threshold")
|
||||
}
|
||||
|
||||
if c.RtpAddress == "" {
|
||||
c.RtpAddress = defaultRtpAddr
|
||||
if c.RTPAddress == "" {
|
||||
c.RTPAddress = defaultRtpAddr
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
|
@ -233,11 +233,11 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
var w io.WriteCloser
|
||||
for _, out := range r.config.Outputs {
|
||||
switch out {
|
||||
case Http:
|
||||
case HTTP:
|
||||
w = newMtsSender(newHttpSender(r.ns, r.config.Logger.Log), r.config.Logger.Log, rbSize, rbElementSize, 0)
|
||||
mtsSenders = append(mtsSenders, w)
|
||||
case Rtp:
|
||||
w, err := newRtpSender(r.config.RtpAddress, r.config.Logger.Log, r.config.FrameRate)
|
||||
case RTP:
|
||||
w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate)
|
||||
if err != nil {
|
||||
r.config.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error())
|
||||
}
|
||||
|
@ -248,8 +248,8 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
return err
|
||||
}
|
||||
mtsSenders = append(mtsSenders, w)
|
||||
case Rtmp:
|
||||
w, err := newRtmpSender(r.config.RtmpUrl, rtmpConnectionTimeout, rtmpConnectionMaxTries, r.config.Logger.Log)
|
||||
case RTMP:
|
||||
w, err := newRtmpSender(r.config.RTMPURL, rtmpConnectionTimeout, rtmpConnectionMaxTries, r.config.Logger.Log)
|
||||
if err != nil {
|
||||
r.config.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error())
|
||||
}
|
||||
|
@ -289,7 +289,6 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
r.lexTo = h264.Lex
|
||||
case File:
|
||||
r.setupInput = r.setupInputForFile
|
||||
r.lexTo = h264.Lex
|
||||
case RTSP:
|
||||
r.setupInput = r.startRTSPCamera
|
||||
r.lexTo = h265.NewLexer(false).Lex
|
||||
|
@ -388,11 +387,11 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
case "File":
|
||||
r.config.Outputs[i] = File
|
||||
case "Http":
|
||||
r.config.Outputs[i] = Http
|
||||
r.config.Outputs[i] = HTTP
|
||||
case "Rtmp":
|
||||
r.config.Outputs[i] = Rtmp
|
||||
r.config.Outputs[i] = RTMP
|
||||
case "Rtp":
|
||||
r.config.Outputs[i] = Rtp
|
||||
r.config.Outputs[i] = RTP
|
||||
default:
|
||||
r.config.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value)
|
||||
continue
|
||||
|
@ -400,9 +399,9 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
}
|
||||
|
||||
case "RtmpUrl":
|
||||
r.config.RtmpUrl = value
|
||||
r.config.RTMPURL = value
|
||||
case "RtpAddress":
|
||||
r.config.RtpAddress = value
|
||||
r.config.RTPAddress = value
|
||||
case "Bitrate":
|
||||
v, err := strconv.ParseUint(value, 10, 0)
|
||||
if err != nil {
|
||||
|
@ -443,7 +442,7 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
}
|
||||
r.config.Rotation = uint(v)
|
||||
case "HttpAddress":
|
||||
r.config.HttpAddress = value
|
||||
r.config.HTTPAddress = value
|
||||
case "Quantization":
|
||||
q, err := strconv.ParseUint(value, 10, 0)
|
||||
if err != nil {
|
||||
|
|
|
@ -148,7 +148,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
encoders []encoder
|
||||
}{
|
||||
{
|
||||
outputs: []uint8{Http},
|
||||
outputs: []uint8{HTTP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: mtsEncoderStr,
|
||||
|
@ -157,7 +157,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
outputs: []uint8{Rtmp},
|
||||
outputs: []uint8{RTMP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: flvEncoderStr,
|
||||
|
@ -166,7 +166,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
outputs: []uint8{Rtp},
|
||||
outputs: []uint8{RTP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: mtsEncoderStr,
|
||||
|
@ -175,7 +175,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
outputs: []uint8{Http, Rtmp},
|
||||
outputs: []uint8{HTTP, RTMP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: mtsEncoderStr,
|
||||
|
@ -188,7 +188,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
outputs: []uint8{Http, Rtp, Rtmp},
|
||||
outputs: []uint8{HTTP, RTP, RTMP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: mtsEncoderStr,
|
||||
|
@ -201,7 +201,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
outputs: []uint8{Rtp, Rtmp},
|
||||
outputs: []uint8{RTP, RTMP},
|
||||
encoders: []encoder{
|
||||
{
|
||||
encoderType: mtsEncoderStr,
|
||||
|
@ -224,7 +224,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
|
|||
for testNum, test := range tests {
|
||||
// Create a new config and reset revid with it.
|
||||
const dummyURL = "rtmp://dummy"
|
||||
c := Config{Logger: &testLogger{}, Outputs: test.outputs, RtmpUrl: dummyURL}
|
||||
c := Config{Logger: &testLogger{}, Outputs: test.outputs, RTMPURL: dummyURL}
|
||||
err := rv.setConfig(c)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v for test %v", err, testNum)
|
||||
|
|
Loading…
Reference in New Issue