mirror of https://bitbucket.org/ausocean/av.git
av: fixed conflicts with master
This commit is contained in:
commit
9b48d22392
|
@ -127,7 +127,7 @@ func handleFlags() revid.Config {
|
||||||
frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video")
|
frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video")
|
||||||
quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40")
|
quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40")
|
||||||
intraRefreshPeriodPtr = flag.Uint("IntraRefreshPeriod", 0, "The IntraRefreshPeriod i.e. how many keyframes we send")
|
intraRefreshPeriodPtr = flag.Uint("IntraRefreshPeriod", 0, "The IntraRefreshPeriod i.e. how many keyframes we send")
|
||||||
rotationPtr = flag.Uint("Rotatation", 0, "Rotate video output. (0-359 degrees)")
|
rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)")
|
||||||
brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
|
brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
|
||||||
saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
|
saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
|
||||||
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")")
|
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")")
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
NAME
|
||||||
|
audio_test.go
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
Trek Hopton <trek@ausocean.org>
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
audio_test.go is Copyright (C) 2017-2019 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 in gpl.txt.
|
||||||
|
If not, see http://www.gnu.org/licenses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package mts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Comcast/gots/packet"
|
||||||
|
"github.com/Comcast/gots/pes"
|
||||||
|
|
||||||
|
"bitbucket.org/ausocean/av/container/mts/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestEncodePcm tests the mpegts encoder's ability to encode pcm audio data.
|
||||||
|
// It reads and encodes input pcm data into mpegts, then decodes the mpegts and compares the result to the input pcm.
|
||||||
|
func TestEncodePcm(t *testing.T) {
|
||||||
|
Meta = meta.New()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
sampleRate := 48000
|
||||||
|
sampleSize := 2
|
||||||
|
blockSize := 16000
|
||||||
|
writeFreq := float64(sampleRate*sampleSize) / float64(blockSize)
|
||||||
|
e := NewEncoder(&buf, writeFreq, Audio)
|
||||||
|
|
||||||
|
inPath := "../../../test/test-data/av/input/sweep_400Hz_20000Hz_-3dBFS_5s_48khz.pcm"
|
||||||
|
inPcm, err := ioutil.ReadFile(inPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break pcm into blocks and encode to mts and get the resulting bytes.
|
||||||
|
for i := 0; i < len(inPcm); i += blockSize {
|
||||||
|
if len(inPcm)-i < blockSize {
|
||||||
|
block := inPcm[i:]
|
||||||
|
_, err = e.Write(block)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to write block: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
block := inPcm[i : i+blockSize]
|
||||||
|
_, err = e.Write(block)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to write block: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clip := buf.Bytes()
|
||||||
|
|
||||||
|
// Get the first MTS packet to check
|
||||||
|
var pkt packet.Packet
|
||||||
|
pesPacket := make([]byte, 0, blockSize)
|
||||||
|
got := make([]byte, 0, len(inPcm))
|
||||||
|
i := 0
|
||||||
|
if i+PacketSize <= len(clip) {
|
||||||
|
copy(pkt[:], clip[i:i+PacketSize])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through MTS packets until all the audio data from PES packets has been retrieved
|
||||||
|
for i+PacketSize <= len(clip) {
|
||||||
|
|
||||||
|
// Check MTS packet
|
||||||
|
if !(pkt.PID() == audioPid) {
|
||||||
|
i += PacketSize
|
||||||
|
if i+PacketSize <= len(clip) {
|
||||||
|
copy(pkt[:], clip[i:i+PacketSize])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !pkt.PayloadUnitStartIndicator() {
|
||||||
|
i += PacketSize
|
||||||
|
if i+PacketSize <= len(clip) {
|
||||||
|
copy(pkt[:], clip[i:i+PacketSize])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Copy the first MTS payload
|
||||||
|
payload, err := pkt.Payload()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to get MTS payload: %v", err)
|
||||||
|
}
|
||||||
|
pesPacket = append(pesPacket, payload...)
|
||||||
|
|
||||||
|
i += PacketSize
|
||||||
|
if i+PacketSize <= len(clip) {
|
||||||
|
copy(pkt[:], clip[i:i+PacketSize])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the rest of the MTS payloads that are part of the same PES packet
|
||||||
|
for (!pkt.PayloadUnitStartIndicator()) && i+PacketSize <= len(clip) {
|
||||||
|
payload, err = pkt.Payload()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to get MTS payload: %v", err)
|
||||||
|
}
|
||||||
|
pesPacket = append(pesPacket, payload...)
|
||||||
|
|
||||||
|
i += PacketSize
|
||||||
|
if i+PacketSize <= len(clip) {
|
||||||
|
copy(pkt[:], clip[i:i+PacketSize])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the audio data from the current PES packet
|
||||||
|
pesHeader, err := pes.NewPESHeader(pesPacket)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read PES packet: %v", err)
|
||||||
|
}
|
||||||
|
got = append(got, pesHeader.Data()...)
|
||||||
|
pesPacket = pesPacket[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare data from MTS with original data.
|
||||||
|
if !bytes.Equal(got, inPcm) {
|
||||||
|
t.Error("data decoded from mts did not match input data")
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,6 @@
|
||||||
NAME
|
NAME
|
||||||
encoder.go
|
encoder.go
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
See Readme.md
|
|
||||||
|
|
||||||
AUTHOR
|
AUTHOR
|
||||||
Dan Kortschak <dan@ausocean.org>
|
Dan Kortschak <dan@ausocean.org>
|
||||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||||
|
@ -29,6 +26,7 @@ LICENSE
|
||||||
package mts
|
package mts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -101,11 +99,20 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sdtPid = 17
|
sdtPid = 17
|
||||||
patPid = 0
|
patPid = 0
|
||||||
pmtPid = 4096
|
pmtPid = 4096
|
||||||
videoPid = 256
|
videoPid = 256
|
||||||
streamID = 0xe0 // First video stream ID.
|
audioPid = 210
|
||||||
|
videoStreamID = 0xe0 // First video stream ID.
|
||||||
|
audioStreamID = 0xc0 // First audio stream ID.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Video and Audio constants are used to communicate which media type will be encoded when creating a
|
||||||
|
// new encoder with NewEncoder.
|
||||||
|
const (
|
||||||
|
Video = iota
|
||||||
|
Audio
|
||||||
)
|
)
|
||||||
|
|
||||||
// Time related constants.
|
// Time related constants.
|
||||||
|
@ -122,38 +129,55 @@ const (
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
dst io.Writer
|
dst io.Writer
|
||||||
|
|
||||||
clock time.Duration
|
clock time.Duration
|
||||||
lastTime time.Time
|
lastTime time.Time
|
||||||
frameInterval time.Duration
|
writePeriod time.Duration
|
||||||
ptsOffset time.Duration
|
ptsOffset time.Duration
|
||||||
tsSpace [PacketSize]byte
|
tsSpace [PacketSize]byte
|
||||||
pesSpace [pes.MaxPesSize]byte
|
pesSpace [pes.MaxPesSize]byte
|
||||||
|
|
||||||
continuity map[int]byte
|
continuity map[int]byte
|
||||||
|
|
||||||
timeBasedPsi bool
|
timeBasedPsi bool
|
||||||
pktCount int
|
pktCount int
|
||||||
psiSendCount int
|
psiSendCount int
|
||||||
|
mediaPid int
|
||||||
|
streamID byte
|
||||||
|
|
||||||
psiLastTime time.Time
|
psiLastTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns an Encoder with the specified frame rate.
|
// NewEncoder returns an Encoder with the specified media type and rate eg. if a video stream
|
||||||
func NewEncoder(dst io.Writer, fps float64) *Encoder {
|
// calls write for every frame, the rate will be the frame rate of the video.
|
||||||
|
func NewEncoder(dst io.Writer, rate float64, mediaType int) *Encoder {
|
||||||
|
var mPid int
|
||||||
|
var sid byte
|
||||||
|
switch mediaType {
|
||||||
|
case Audio:
|
||||||
|
mPid = audioPid
|
||||||
|
sid = audioStreamID
|
||||||
|
case Video:
|
||||||
|
mPid = videoPid
|
||||||
|
sid = videoStreamID
|
||||||
|
}
|
||||||
|
|
||||||
return &Encoder{
|
return &Encoder{
|
||||||
dst: dst,
|
dst: dst,
|
||||||
|
|
||||||
frameInterval: time.Duration(float64(time.Second) / fps),
|
writePeriod: time.Duration(float64(time.Second) / rate),
|
||||||
ptsOffset: ptsOffset,
|
ptsOffset: ptsOffset,
|
||||||
|
|
||||||
timeBasedPsi: true,
|
timeBasedPsi: true,
|
||||||
|
|
||||||
pktCount: 8,
|
pktCount: 8,
|
||||||
|
|
||||||
|
mediaPid: mPid,
|
||||||
|
streamID: sid,
|
||||||
|
|
||||||
continuity: map[int]byte{
|
continuity: map[int]byte{
|
||||||
patPid: 0,
|
patPid: 0,
|
||||||
pmtPid: 0,
|
pmtPid: 0,
|
||||||
videoPid: 0,
|
mPid: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +204,10 @@ func (e *Encoder) TimeBasedPsi(b bool, sendCount int) {
|
||||||
|
|
||||||
// Write implements io.Writer. Write takes raw h264 and encodes into mpegts,
|
// Write implements io.Writer. Write takes raw h264 and encodes into mpegts,
|
||||||
// then sending it to the encoder's io.Writer destination.
|
// then sending it to the encoder's io.Writer destination.
|
||||||
func (e *Encoder) Write(nalu []byte) (int, error) {
|
func (e *Encoder) Write(data []byte) (int, error) {
|
||||||
|
if len(data) > pes.MaxPesSize {
|
||||||
|
return 0, fmt.Errorf("data size too large (Max is %v): %v", pes.MaxPesSize, len(data))
|
||||||
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if (e.timeBasedPsi && (now.Sub(e.psiLastTime) > psiInterval)) || (!e.timeBasedPsi && (e.pktCount >= e.psiSendCount)) {
|
if (e.timeBasedPsi && (now.Sub(e.psiLastTime) > psiInterval)) || (!e.timeBasedPsi && (e.pktCount >= e.psiSendCount)) {
|
||||||
e.pktCount = 0
|
e.pktCount = 0
|
||||||
|
@ -193,21 +220,22 @@ func (e *Encoder) Write(nalu []byte) (int, error) {
|
||||||
|
|
||||||
// Prepare PES data.
|
// Prepare PES data.
|
||||||
pesPkt := pes.Packet{
|
pesPkt := pes.Packet{
|
||||||
StreamID: streamID,
|
StreamID: e.streamID,
|
||||||
PDI: hasPTS,
|
PDI: hasPTS,
|
||||||
PTS: e.pts(),
|
PTS: e.pts(),
|
||||||
Data: nalu,
|
Data: data,
|
||||||
HeaderLength: 5,
|
HeaderLength: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pesPkt.Bytes(e.pesSpace[:pes.MaxPesSize])
|
buf := pesPkt.Bytes(e.pesSpace[:pes.MaxPesSize])
|
||||||
|
|
||||||
pusi := true
|
pusi := true
|
||||||
for len(buf) != 0 {
|
for len(buf) != 0 {
|
||||||
pkt := Packet{
|
pkt := Packet{
|
||||||
PUSI: pusi,
|
PUSI: pusi,
|
||||||
PID: videoPid,
|
PID: uint16(e.mediaPid),
|
||||||
RAI: pusi,
|
RAI: pusi,
|
||||||
CC: e.ccFor(videoPid),
|
CC: e.ccFor(e.mediaPid),
|
||||||
AFC: hasAdaptationField | hasPayload,
|
AFC: hasAdaptationField | hasPayload,
|
||||||
PCRF: pusi,
|
PCRF: pusi,
|
||||||
}
|
}
|
||||||
|
@ -222,14 +250,14 @@ func (e *Encoder) Write(nalu []byte) (int, error) {
|
||||||
}
|
}
|
||||||
_, err := e.dst.Write(pkt.Bytes(e.tsSpace[:PacketSize]))
|
_, err := e.dst.Write(pkt.Bytes(e.tsSpace[:PacketSize]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return len(nalu), err
|
return len(data), err
|
||||||
}
|
}
|
||||||
e.pktCount++
|
e.pktCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
e.tick()
|
e.tick()
|
||||||
|
|
||||||
return len(nalu), nil
|
return len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePSI creates mpegts with pat and pmt tables - with pmt table having updated
|
// writePSI creates mpegts with pat and pmt tables - with pmt table having updated
|
||||||
|
@ -271,7 +299,7 @@ func (e *Encoder) writePSI() error {
|
||||||
|
|
||||||
// tick advances the clock one frame interval.
|
// tick advances the clock one frame interval.
|
||||||
func (e *Encoder) tick() {
|
func (e *Encoder) tick() {
|
||||||
e.clock += e.frameInterval
|
e.clock += e.writePeriod
|
||||||
}
|
}
|
||||||
|
|
||||||
// pts retuns the current presentation timestamp.
|
// pts retuns the current presentation timestamp.
|
||||||
|
|
|
@ -49,7 +49,7 @@ func TestMetaEncode1(t *testing.T) {
|
||||||
Meta = meta.New()
|
Meta = meta.New()
|
||||||
var b []byte
|
var b []byte
|
||||||
buf := bytes.NewBuffer(b)
|
buf := bytes.NewBuffer(b)
|
||||||
e := NewEncoder(buf, fps)
|
e := NewEncoder(buf, fps, Video)
|
||||||
Meta.Add("ts", "12345678")
|
Meta.Add("ts", "12345678")
|
||||||
if err := e.writePSI(); err != nil {
|
if err := e.writePSI(); err != nil {
|
||||||
t.Errorf(errUnexpectedErr, err.Error())
|
t.Errorf(errUnexpectedErr, err.Error())
|
||||||
|
@ -78,7 +78,7 @@ func TestMetaEncode2(t *testing.T) {
|
||||||
Meta = meta.New()
|
Meta = meta.New()
|
||||||
var b []byte
|
var b []byte
|
||||||
buf := bytes.NewBuffer(b)
|
buf := bytes.NewBuffer(b)
|
||||||
e := NewEncoder(buf, fps)
|
e := NewEncoder(buf, fps, Video)
|
||||||
Meta.Add("ts", "12345678")
|
Meta.Add("ts", "12345678")
|
||||||
Meta.Add("loc", "1234,4321,1234")
|
Meta.Add("loc", "1234,4321,1234")
|
||||||
if err := e.writePSI(); err != nil {
|
if err := e.writePSI(); err != nil {
|
||||||
|
|
|
@ -74,7 +74,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The below data struct encapsulates the fields of an MPEG-TS packet. Below is
|
Packet encapsulates the fields of an MPEG-TS packet. Below is
|
||||||
the formatting of an MPEG-TS packet for reference!
|
the formatting of an MPEG-TS packet for reference!
|
||||||
|
|
||||||
MPEG-TS Packet Formatting
|
MPEG-TS Packet Formatting
|
||||||
|
@ -196,7 +196,11 @@ func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) {
|
||||||
func (p *Packet) FillPayload(data []byte) int {
|
func (p *Packet) FillPayload(data []byte) int {
|
||||||
currentPktLen := 6 + asInt(p.PCRF)*6 + asInt(p.OPCRF)*6 +
|
currentPktLen := 6 + asInt(p.PCRF)*6 + asInt(p.OPCRF)*6 +
|
||||||
asInt(p.SPF)*1 + asInt(p.TPDF)*1 + len(p.TPD)
|
asInt(p.SPF)*1 + asInt(p.TPDF)*1 + len(p.TPD)
|
||||||
p.Payload = make([]byte, PayloadSize-currentPktLen)
|
if len(data) > PayloadSize-currentPktLen {
|
||||||
|
p.Payload = make([]byte, PayloadSize-currentPktLen)
|
||||||
|
} else {
|
||||||
|
p.Payload = make([]byte, len(data))
|
||||||
|
}
|
||||||
return copy(p.Payload, data)
|
return copy(p.Payload, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +218,7 @@ func asByte(b bool) byte {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToByteSlice interprets the fields of the ts packet instance and outputs a
|
// Bytes interprets the fields of the ts packet instance and outputs a
|
||||||
// corresponding byte slice
|
// corresponding byte slice
|
||||||
func (p *Packet) Bytes(buf []byte) []byte {
|
func (p *Packet) Bytes(buf []byte) []byte {
|
||||||
if buf == nil || cap(buf) != PacketSize {
|
if buf == nil || cap(buf) != PacketSize {
|
||||||
|
|
|
@ -26,7 +26,7 @@ LICENSE
|
||||||
|
|
||||||
package pes
|
package pes
|
||||||
|
|
||||||
const MaxPesSize = 10000
|
const MaxPesSize = 64 * 1 << 10
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The below data struct encapsulates the fields of an PES packet. Below is
|
The below data struct encapsulates the fields of an PES packet. Below is
|
||||||
|
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"bitbucket.org/ausocean/av/audio/pcm"
|
"bitbucket.org/ausocean/av/codec/pcm"
|
||||||
"github.com/yobert/alsa"
|
"github.com/yobert/alsa"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"bitbucket.org/ausocean/av/audio/pcm"
|
"bitbucket.org/ausocean/av/codec/pcm"
|
||||||
"github.com/yobert/alsa"
|
"github.com/yobert/alsa"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ func (pkt *packet) resize(size uint32, ht uint8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo writes a packet to the RTMP connection.
|
// writeTo writes a packet to the RTMP connection.
|
||||||
// Packets are written in chunks which are Session.chunkSize in length (128 bytes in length).
|
// Packets are written in chunks which are c.chunkSize in length (128 bytes by default).
|
||||||
// We defer sending small audio packets and combine consecutive small audio packets where possible to reduce I/O.
|
// We defer sending small audio packets and combine consecutive small audio packets where possible to reduce I/O.
|
||||||
// When queue is true, we expect a response to this request and cache the method on c.methodCalls.
|
// When queue is true, we expect a response to this request and cache the method on c.methodCalls.
|
||||||
func (pkt *packet) writeTo(c *Conn, queue bool) error {
|
func (pkt *packet) writeTo(c *Conn, queue bool) error {
|
||||||
|
|
|
@ -206,7 +206,7 @@ func TestFromFrame(t *testing.T) {
|
||||||
|
|
||||||
err = c.Close()
|
err = c.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Session.Close failed with error: %v", err)
|
t.Errorf("Conn.Close failed with error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +256,6 @@ func TestFromFile(t *testing.T) {
|
||||||
|
|
||||||
err = c.Close()
|
err = c.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Session.Close failed with error: %v", err)
|
t.Errorf("Conn.Close failed with error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
revid/Readme.md
112
revid/Readme.md
|
@ -1,112 +0,0 @@
|
||||||
# Readme
|
|
||||||
|
|
||||||
revid is a testbed for re-muxing and re-directing video streams as
|
|
||||||
MPEG-TS over various protocols.
|
|
||||||
|
|
||||||
# Description
|
|
||||||
|
|
||||||
The mode (-m) determine the mode of operation:
|
|
||||||
|
|
||||||
* h = send HTTP (as a POST)
|
|
||||||
* u = send UDP
|
|
||||||
* r = send RTP
|
|
||||||
* f = write to /tmp files
|
|
||||||
* d = inspect packets and dump to screen
|
|
||||||
|
|
||||||
Flags (-f) determine video filtering and other actions.
|
|
||||||
|
|
||||||
For example, to send as raw UDP to <PORT> on the current host, passing the video and audio as is:
|
|
||||||
|
|
||||||
revid -i <RTSP_URL> -m u -o udp://0.0.0.0:<PORT>
|
|
||||||
|
|
||||||
Or, to post as HTTP to <HTTP_URL>, fixing PTS and dropping the audio along the way:
|
|
||||||
|
|
||||||
revid -i <RTSP_URL> -m h -f 3 -o <HTTP_URL>
|
|
||||||
|
|
||||||
Note that revid appends the size of the video to the URL to supply a query param.
|
|
||||||
Append a ? to your <URL> if you don't need it
|
|
||||||
|
|
||||||
List of flags:
|
|
||||||
|
|
||||||
* filterFixPTS = 0x0001
|
|
||||||
* filterDropAudio = 0x0002
|
|
||||||
* filterScale640 = 0x0004
|
|
||||||
* filterScale320 = 0x0008
|
|
||||||
* filterFixContinuity = 0x0010
|
|
||||||
* dumpProgramInfo = 0x0100
|
|
||||||
* dumpPacketStats = 0x0200
|
|
||||||
* dumpPacketHeader = 0x0400
|
|
||||||
* dumpPacketPayload = 0x0800
|
|
||||||
|
|
||||||
Common flag combos:
|
|
||||||
|
|
||||||
* 3: fix pts and drop audio
|
|
||||||
* 7: fix pts, drop audo and scale 640
|
|
||||||
* 17: fix pts and fix continuity
|
|
||||||
* 256: dump program info
|
|
||||||
* 512: dump packet stats
|
|
||||||
* 513: fix pts, plus above
|
|
||||||
* 529: fix pts and fix continuity, plus above
|
|
||||||
|
|
||||||
# Errors
|
|
||||||
|
|
||||||
If you see "Error reading from ffmpeg: EOF" that means ffmpeg has
|
|
||||||
crashed for some reason, usually because of a bad parameter. Copy and
|
|
||||||
paste the ffmpeg command line into a terminal to see what is
|
|
||||||
happening.
|
|
||||||
|
|
||||||
RTSP feeds from certain cameras (such as TechView ones) do not
|
|
||||||
generate presentation timestamps (PTS), resulting in errors such as
|
|
||||||
the following:
|
|
||||||
|
|
||||||
* [mpegts @ 0xX] Timestamps are unset in a packet for stream 0...
|
|
||||||
* [mpegts @ 0xX] first pts value must be set
|
|
||||||
|
|
||||||
This can be fixed with an ffmpeg video filter (specified by flag 0x0001).
|
|
||||||
Another issue is that MPEG-TS continuity counters may not be continuous.
|
|
||||||
You can fix this with the fix continuity flag (0x0010).
|
|
||||||
|
|
||||||
FFmpeg will also complain if doesn't have the necessary audio codec
|
|
||||||
installed. If so, you can drop the audio (flag 0x0002).
|
|
||||||
|
|
||||||
# MPEG-TS Notes
|
|
||||||
|
|
||||||
MPEG2-TS stream clocks (PCR, PTS, and DTS) all have units of 1/90000
|
|
||||||
second and header fields are read as big endian (like most protocols).
|
|
||||||
|
|
||||||
* TEI = Transport Error Indicator
|
|
||||||
* PUSI = Payload Unit Start Indicator
|
|
||||||
* TP = Transport Priority
|
|
||||||
* TCS = Transport Scrambling Control
|
|
||||||
* AFC = Adapation Field Control
|
|
||||||
* CC = Continuity Counter (incremented per PID wen payload present)
|
|
||||||
* AFL = Adapation Field Length
|
|
||||||
* PCR = Program Clock Reference
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
|
|
||||||
revid uses ffmpeg for video remuxing.
|
|
||||||
See [Ffmepg filters](https://ffmpeg.org/ffmpeg-filters.html).
|
|
||||||
|
|
||||||
revid also uses [Comcast's gots package](https://github.com/Comcast/gots).
|
|
||||||
|
|
||||||
# Author
|
|
||||||
|
|
||||||
Alan Noble <anoble@gmail.com>
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
Revid 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
|
|
||||||
or 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/).
|
|
|
@ -268,7 +268,7 @@ func (r *Revid) setupPipeline(mtsEnc, flvEnc func(dst io.Writer, rate int) (io.W
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMtsEncoder(dst io.Writer, fps int) (io.Writer, error) {
|
func newMtsEncoder(dst io.Writer, fps int) (io.Writer, error) {
|
||||||
e := mts.NewEncoder(dst, float64(fps))
|
e := mts.NewEncoder(dst, float64(fps), mts.Video)
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ func TestMtsSenderSegment(t *testing.T) {
|
||||||
// Create ringBuffer, sender, sender and the MPEGTS encoder.
|
// Create ringBuffer, sender, sender and the MPEGTS encoder.
|
||||||
tstDst := &destination{t: t}
|
tstDst := &destination{t: t}
|
||||||
sender := newMtsSender(tstDst, dummyLogger(*t).log, ringBufferSize, ringBufferElementSize, writeTimeout)
|
sender := newMtsSender(tstDst, dummyLogger(*t).log, ringBufferSize, ringBufferElementSize, writeTimeout)
|
||||||
encoder := mts.NewEncoder(sender, 25)
|
encoder := mts.NewEncoder(sender, 25, mts.Video)
|
||||||
|
|
||||||
// Turn time based PSI writing off for encoder.
|
// Turn time based PSI writing off for encoder.
|
||||||
const psiSendCount = 10
|
const psiSendCount = 10
|
||||||
|
@ -198,7 +198,7 @@ func TestMtsSenderFailedSend(t *testing.T) {
|
||||||
const clipToFailAt = 3
|
const clipToFailAt = 3
|
||||||
tstDst := &destination{t: t, testFails: true, failAt: clipToFailAt}
|
tstDst := &destination{t: t, testFails: true, failAt: clipToFailAt}
|
||||||
sender := newMtsSender(tstDst, dummyLogger(*t).log, ringBufferSize, ringBufferElementSize, writeTimeout)
|
sender := newMtsSender(tstDst, dummyLogger(*t).log, ringBufferSize, ringBufferElementSize, writeTimeout)
|
||||||
encoder := mts.NewEncoder(sender, 25)
|
encoder := mts.NewEncoder(sender, 25, mts.Video)
|
||||||
|
|
||||||
// Turn time based PSI writing off for encoder and send PSI every 10 packets.
|
// Turn time based PSI writing off for encoder and send PSI every 10 packets.
|
||||||
const psiSendCount = 10
|
const psiSendCount = 10
|
||||||
|
@ -278,7 +278,7 @@ func TestMtsSenderDiscontinuity(t *testing.T) {
|
||||||
const clipToDelay = 3
|
const clipToDelay = 3
|
||||||
tstDst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay}
|
tstDst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay}
|
||||||
sender := newMtsSender(tstDst, dummyLogger(*t).log, 1, ringBufferElementSize, writeTimeout)
|
sender := newMtsSender(tstDst, dummyLogger(*t).log, 1, ringBufferElementSize, writeTimeout)
|
||||||
encoder := mts.NewEncoder(sender, 25)
|
encoder := mts.NewEncoder(sender, 25, mts.Video)
|
||||||
|
|
||||||
// Turn time based PSI writing off for encoder.
|
// Turn time based PSI writing off for encoder.
|
||||||
const psiSendCount = 10
|
const psiSendCount = 10
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
#!/bin/sh -e
|
|
||||||
#
|
|
||||||
# rc.local
|
|
||||||
#
|
|
||||||
# This script is executed at the end of each multiuser runlevel.
|
|
||||||
# Make sure that the script will "exit 0" on success or any other
|
|
||||||
# value on error.
|
|
||||||
#
|
|
||||||
# In order to enable or disable this script just change the execution
|
|
||||||
# bits.
|
|
||||||
#
|
|
||||||
# By default this script does nothing.
|
|
||||||
|
|
||||||
# Print the IP address
|
|
||||||
_IP=$(hostname -I) || true
|
|
||||||
if [ "$_IP" ]; then
|
|
||||||
printf "My IP address is %s\n" "$_IP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
Loading…
Reference in New Issue