Merge branch 'master' into audio-player

This commit is contained in:
Trek H 2019-09-06 13:07:45 +09:30
commit 328d63f623
29 changed files with 2139 additions and 504 deletions

View File

@ -106,34 +106,30 @@ func handleFlags() revid.Config {
var cfg revid.Config var cfg revid.Config
var ( var (
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM")
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")
inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.")
rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal")
quantizePtr = flag.Bool("Quantize", false, "Quantize input (non-variable bitrate)") rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: <IP>:<port> (port is generally 6970-6999)")
verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal") logPathPtr = flag.String("LogPath", defaultLogPath, "The log path")
rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: <IP>:<port> (port is generally 6970-6999)") configFilePtr = flag.String("ConfigFile", "", "NetSender config file")
logPathPtr = flag.String("LogPath", defaultLogPath, "The log path") rtmpUrlPtr = flag.String("RtmpUrl", "", "Url of rtmp endpoint")
configFilePtr = flag.String("ConfigFile", "", "NetSender config file") outputPathPtr = flag.String("OutputPath", "", "The directory of the output file")
rtmpUrlPtr = flag.String("RtmpUrl", "", "Url of rtmp endpoint") inputFilePtr = flag.String("InputPath", "", "The directory of the input file")
outputPathPtr = flag.String("OutputPath", "", "The directory of the output file") httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts")
inputFilePtr = flag.String("InputPath", "", "The directory of the input file") verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No")
httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No")
verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video")
horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") heightPtr = flag.Uint("Height", 0, "Height in pixels")
framesPerClipPtr = flag.Uint("FramesPerClip", 0, "Number of frames per clip sent") widthPtr = flag.Uint("Width", 0, "Width in pixels")
bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video")
heightPtr = flag.Uint("Height", 0, "Height in pixels") quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40")
widthPtr = flag.Uint("Width", 0, "Width in pixels") rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)")
frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video") brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40") saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
intraRefreshPeriodPtr = flag.Uint("IntraRefreshPeriod", 0, "The IntraRefreshPeriod i.e. how many keyframes we send") exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")")
rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)") autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")")
brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")")
autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")")
// Audio specific flags. // Audio specific flags.
sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio")
@ -204,7 +200,6 @@ func handleFlags() revid.Config {
cfg.InputCodec = codecutil.PCM cfg.InputCodec = codecutil.PCM
case "ADPCM": case "ADPCM":
cfg.InputCodec = codecutil.ADPCM cfg.InputCodec = codecutil.ADPCM
case "":
default: default:
log.Log(logger.Error, pkg+"bad input codec argument") log.Log(logger.Error, pkg+"bad input codec argument")
} }
@ -237,11 +232,9 @@ func handleFlags() revid.Config {
} }
cfg.RTSPURL = *rtspURLPtr cfg.RTSPURL = *rtspURLPtr
cfg.Quantize = *quantizePtr
cfg.Rotation = *rotationPtr cfg.Rotation = *rotationPtr
cfg.FlipHorizontal = *horizontalFlipPtr cfg.FlipHorizontal = *horizontalFlipPtr
cfg.FlipVertical = *verticalFlipPtr cfg.FlipVertical = *verticalFlipPtr
cfg.FramesPerClip = *framesPerClipPtr
cfg.RTMPURL = *rtmpUrlPtr cfg.RTMPURL = *rtmpUrlPtr
cfg.Bitrate = *bitratePtr cfg.Bitrate = *bitratePtr
cfg.OutputPath = *outputPathPtr cfg.OutputPath = *outputPathPtr
@ -251,7 +244,6 @@ func handleFlags() revid.Config {
cfg.FrameRate = *frameRatePtr cfg.FrameRate = *frameRatePtr
cfg.HTTPAddress = *httpAddressPtr cfg.HTTPAddress = *httpAddressPtr
cfg.Quantization = *quantizationPtr cfg.Quantization = *quantizationPtr
cfg.IntraRefreshPeriod = *intraRefreshPeriodPtr
cfg.RTPAddress = *rtpAddrPtr cfg.RTPAddress = *rtpAddrPtr
cfg.Brightness = *brightnessPtr cfg.Brightness = *brightnessPtr
cfg.Saturation = *saturationPtr cfg.Saturation = *saturationPtr

View File

@ -40,10 +40,11 @@ import (
const ( const (
byteDepth = 2 // We are working with 16-bit samples. TODO(Trek): make configurable. byteDepth = 2 // We are working with 16-bit samples. TODO(Trek): make configurable.
initSamps = 2 // Number of samples used to initialise the encoder. initSamps = 2 // Number of samples used to initialise the encoder.
initBytes = initSamps * byteDepth initSize = initSamps * byteDepth
headBytes = 4 // Number of bytes in the header of ADPCM. headSize = 8 // Number of bytes in the header of ADPCM.
samplesPerEnc = 2 // Number of sample encoded at a time eg. 2 16-bit samples get encoded into 1 byte. samplesPerEnc = 2 // Number of sample encoded at a time eg. 2 16-bit samples get encoded into 1 byte.
bytesPerEnc = samplesPerEnc * byteDepth bytesPerEnc = samplesPerEnc * byteDepth
chunkLenSize = 4 // Size of the chunk length in bytes, chunk length is a 32 bit number.
compFact = 4 // In general ADPCM compresses by a factor of 4. compFact = 4 // In general ADPCM compresses by a factor of 4.
) )
@ -176,7 +177,7 @@ func (e *Encoder) calcHead(sample []byte, pad bool) (int, error) {
// The suitable step size is the closest step size in the stepTable to half the absolute difference of the first two samples. // The suitable step size is the closest step size in the stepTable to half the absolute difference of the first two samples.
func (e *Encoder) init(samples []byte) { func (e *Encoder) init(samples []byte) {
int1 := int16(binary.LittleEndian.Uint16(samples[:byteDepth])) int1 := int16(binary.LittleEndian.Uint16(samples[:byteDepth]))
int2 := int16(binary.LittleEndian.Uint16(samples[byteDepth:initBytes])) int2 := int16(binary.LittleEndian.Uint16(samples[byteDepth:initSize]))
e.est = int1 e.est = int1
halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2) halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2)
@ -197,8 +198,8 @@ func (e *Encoder) init(samples []byte) {
func (e *Encoder) Write(b []byte) (int, error) { func (e *Encoder) Write(b []byte) (int, error) {
// Check that pcm has enough data to initialize Decoder. // Check that pcm has enough data to initialize Decoder.
pcmLen := len(b) pcmLen := len(b)
if pcmLen < initBytes { if pcmLen < initSize {
return 0, fmt.Errorf("length of given byte array must be >= %v", initBytes) return 0, fmt.Errorf("length of given byte array must be >= %v", initSize)
} }
// Determine if there will be a byte that won't contain two full nibbles and will need padding. // Determine if there will be a byte that won't contain two full nibbles and will need padding.
@ -207,8 +208,18 @@ func (e *Encoder) Write(b []byte) (int, error) {
pad = true pad = true
} }
e.init(b[:initBytes]) // Write the first 4 bytes of the adpcm chunk, which represent its length, ie. the number of bytes following the chunk length.
n, err := e.calcHead(b[:byteDepth], pad) chunkLen := EncBytes(pcmLen)
chunkLenBytes := make([]byte, chunkLenSize)
binary.LittleEndian.PutUint32(chunkLenBytes, uint32(chunkLen))
n, err := e.dst.Write(chunkLenBytes)
if err != nil {
return n, err
}
e.init(b[:initSize])
_n, err := e.calcHead(b[:byteDepth], pad)
n += _n
if err != nil { if err != nil {
return n, err return n, err
} }
@ -284,47 +295,59 @@ func (d *Decoder) decodeSample(nibble byte) int16 {
// It writes its output to the Decoder's dst. // It writes its output to the Decoder's dst.
// The number of bytes written out is returned along with any error that occured. // The number of bytes written out is returned along with any error that occured.
func (d *Decoder) Write(b []byte) (int, error) { func (d *Decoder) Write(b []byte) (int, error) {
// Initialize Decoder with first 4 bytes of b. // Iterate over each chunk and decode it.
d.est = int16(binary.LittleEndian.Uint16(b[:byteDepth])) var n int
d.idx = int16(b[byteDepth]) var chunkLen int
d.step = stepTable[d.idx] for off := 0; off+headSize <= len(b); off += chunkLen {
n, err := d.dst.Write(b[:byteDepth]) // Read length of chunk and check if whole chunk exists.
if err != nil { chunkLen = int(binary.LittleEndian.Uint32(b[off : off+chunkLenSize]))
return n, err if off+chunkLen > len(b) {
} break
}
// For each byte, seperate it into two nibbles (each nibble is a compressed sample), // Initialize Decoder with header of b.
// then decode each nibble and output the resulting 16-bit samples. d.est = int16(binary.LittleEndian.Uint16(b[off+chunkLenSize : off+chunkLenSize+byteDepth]))
// If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately. d.idx = int16(b[off+chunkLenSize+byteDepth])
for i := headBytes; i < len(b)-int(b[3]); i++ { d.step = stepTable[d.idx]
twoNibs := b[i] _n, err := d.dst.Write(b[off+chunkLenSize : off+chunkLenSize+byteDepth])
nib2 := byte(twoNibs >> 4)
nib1 := byte((nib2 << 4) ^ twoNibs)
firstBytes := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
_n, err := d.dst.Write(firstBytes)
n += _n n += _n
if err != nil { if err != nil {
return n, err return n, err
} }
secondBytes := make([]byte, byteDepth) // For each byte, seperate it into two nibbles (each nibble is a compressed sample),
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) // then decode each nibble and output the resulting 16-bit samples.
_n, err = d.dst.Write(secondBytes) // If padding flag is true only decode up until the last byte, then decode that separately.
n += _n for i := off + headSize; i < off+chunkLen-int(b[off+chunkLenSize+3]); i++ {
if err != nil { twoNibs := b[i]
return n, err nib2 := byte(twoNibs >> 4)
nib1 := byte((nib2 << 4) ^ twoNibs)
firstBytes := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1)))
_n, err := d.dst.Write(firstBytes)
n += _n
if err != nil {
return n, err
}
secondBytes := make([]byte, byteDepth)
binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2)))
_n, err = d.dst.Write(secondBytes)
n += _n
if err != nil {
return n, err
}
} }
} if b[off+chunkLenSize+3] == 0x01 {
if b[3] == 0x01 { padNib := b[off+chunkLen-1]
padNib := b[len(b)-1] samp := make([]byte, byteDepth)
samp := make([]byte, byteDepth) binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib)))
binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib))) _n, err := d.dst.Write(samp)
_n, err := d.dst.Write(samp) n += _n
n += _n if err != nil {
if err != nil { return n, err
return n, err }
} }
} }
return n, nil return n, nil
@ -349,7 +372,7 @@ func EncBytes(n int) int {
// and a start index and padding-flag byte are added. // and a start index and padding-flag byte are added.
// Also if there are an even number of samples, there will be half a byte of padding added to the last byte. // Also if there are an even number of samples, there will be half a byte of padding added to the last byte.
if n%bytesPerEnc == 0 { if n%bytesPerEnc == 0 {
return (n-byteDepth)/compFact + headBytes + 1 return (n-byteDepth)/compFact + headSize + 1
} }
return (n-byteDepth)/compFact + headBytes return (n-byteDepth)/compFact + headSize
} }

View File

@ -51,7 +51,7 @@ func TestEncodeBlock(t *testing.T) {
} }
// Read expected adpcm file. // Read expected adpcm file.
exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded_8kHz_adpcm_test.adpcm") exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded_8kHz_adpcm_test2.adpcm")
if err != nil { if err != nil {
t.Errorf("Unable to read expected ADPCM file: %v", err) t.Errorf("Unable to read expected ADPCM file: %v", err)
} }
@ -65,7 +65,7 @@ func TestEncodeBlock(t *testing.T) {
// resulting PCM with the expected decoded PCM. // resulting PCM with the expected decoded PCM.
func TestDecodeBlock(t *testing.T) { func TestDecodeBlock(t *testing.T) {
// Read adpcm. // Read adpcm.
comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded_8kHz_adpcm_test.adpcm") comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded_8kHz_adpcm_test2.adpcm")
if err != nil { if err != nil {
t.Errorf("Unable to read input ADPCM file: %v", err) t.Errorf("Unable to read input ADPCM file: %v", err)
} }
@ -79,7 +79,7 @@ func TestDecodeBlock(t *testing.T) {
} }
// Read expected pcm file. // Read expected pcm file.
exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded_8kHz_adpcm_test.pcm") exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded_8kHz_adpcm_test2.pcm")
if err != nil { if err != nil {
t.Errorf("Unable to read expected PCM file: %v", err) t.Errorf("Unable to read expected PCM file: %v", err)
} }

View File

@ -1,3 +1,30 @@
/*
DESCRIPTION
cabac.go provides utilities for context-adaptive binary artihmetic decoding
for the parsing of H.264 syntax structure fields.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
Bruce McMoran <mcmoranbjr@gmail.com>
Shawn Smith <shawnpsmith@gmail.com>
LICENSE
Copyright (C) 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 h264dec package h264dec
import ( import (
@ -100,13 +127,7 @@ func CondTermFlag(mbAddr, mbSkipFlag int) int {
} }
// s9.3.3 p 278: Returns the value of the syntax element // s9.3.3 p 278: Returns the value of the syntax element
func (bin *Binarization) Decode(sliceContext *SliceContext, b *bits.BitReader, rbsp []byte) { func (bin *Binarization) Decode(sliceContext *SliceContext, b *bits.BitReader, rbsp []byte) {}
if bin.SyntaxElement == "MbType" {
bin.binString = binIdxMbMap[sliceContext.Slice.Data.SliceTypeName][sliceContext.Slice.Data.MbType]
} else {
logger.Printf("TODO: no means to find binString for %s\n", bin.SyntaxElement)
}
}
// 9.3.3.1.1 : returns ctxIdxInc // 9.3.3.1.1 : returns ctxIdxInc
func Decoder9_3_3_1_1_1(condTermFlagA, condTermFlagB int) int { func Decoder9_3_3_1_1_1(condTermFlagA, condTermFlagB int) int {
@ -178,133 +199,109 @@ func initCabac(binarization *Binarization, context *SliceContext) *CABAC {
} }
} }
// Table 9-36, 9-37 // Binarizations for macroblock types in slice types.
// func BinIdx(mbType int, sliceTypeName string) []int {
// Map of SliceTypeName[MbType][]int{binString}
// {"SliceTypeName": {MbTypeCode: []BinString}}
var ( var (
binIdxMbMap = map[string]map[int][]int{ // binOfIMBTypes provides binarization strings for values of macroblock
"I": { // type in I slices as defined in table 9-36 of the specifications.
0: {0}, binOfIMBTypes = [numOfIMBTypes][]int{
1: {1, 0, 0, 0, 0, 0}, 0: {0},
2: {1, 0, 0, 0, 0, 1}, 1: {1, 0, 0, 0, 0, 0},
3: {1, 0, 0, 0, 1, 0}, 2: {1, 0, 0, 0, 0, 1},
4: {1, 0, 0, 0, 1, 1}, 3: {1, 0, 0, 0, 1, 0},
5: {1, 0, 0, 1, 0, 0, 0}, 4: {1, 0, 0, 0, 1, 1},
6: {1, 0, 0, 1, 0, 0, 1}, 5: {1, 0, 0, 1, 0, 0, 0},
7: {1, 0, 0, 1, 0, 1, 0}, 6: {1, 0, 0, 1, 0, 0, 1},
8: {1, 0, 0, 1, 0, 1, 1}, 7: {1, 0, 0, 1, 0, 1, 0},
9: {1, 0, 0, 1, 1, 0, 0}, 8: {1, 0, 0, 1, 0, 1, 1},
10: {1, 0, 0, 1, 1, 0, 1}, 9: {1, 0, 0, 1, 1, 0, 0},
11: {1, 0, 0, 1, 1, 1, 0}, 10: {1, 0, 0, 1, 1, 0, 1},
12: {1, 0, 0, 1, 1, 1, 1}, 11: {1, 0, 0, 1, 1, 1, 0},
13: {1, 0, 1, 0, 0, 0}, 12: {1, 0, 0, 1, 1, 1, 1},
14: {1, 0, 1, 0, 0, 1}, 13: {1, 0, 1, 0, 0, 0},
15: {1, 0, 1, 0, 1, 0}, 14: {1, 0, 1, 0, 0, 1},
16: {1, 0, 1, 0, 1, 1}, 15: {1, 0, 1, 0, 1, 0},
17: {1, 0, 1, 1, 0, 0, 0}, 16: {1, 0, 1, 0, 1, 1},
18: {1, 0, 1, 1, 0, 0, 1}, 17: {1, 0, 1, 1, 0, 0, 0},
19: {1, 0, 1, 1, 0, 1, 0}, 18: {1, 0, 1, 1, 0, 0, 1},
20: {1, 0, 1, 1, 0, 1, 1}, 19: {1, 0, 1, 1, 0, 1, 0},
21: {1, 0, 1, 1, 1, 0, 0}, 20: {1, 0, 1, 1, 0, 1, 1},
22: {1, 0, 1, 1, 1, 0, 1}, 21: {1, 0, 1, 1, 1, 0, 0},
23: {1, 0, 1, 1, 1, 1, 0}, 22: {1, 0, 1, 1, 1, 0, 1},
24: {1, 0, 1, 1, 1, 1, 1}, 23: {1, 0, 1, 1, 1, 1, 0},
25: {1, 1}, 24: {1, 0, 1, 1, 1, 1, 1},
}, 25: {1, 1},
// Table 9-37
"P": {
0: {0, 0, 0},
1: {0, 1, 1},
2: {0, 1, 0},
3: {0, 0, 1},
4: {},
5: {1},
6: {1},
7: {1},
8: {1},
9: {1},
10: {1},
11: {1},
12: {1},
13: {1},
14: {1},
15: {1},
16: {1},
17: {1},
18: {1},
19: {1},
20: {1},
21: {1},
22: {1},
23: {1},
24: {1},
25: {1},
26: {1},
27: {1},
28: {1},
29: {1},
30: {1},
},
// Table 9-37
"SP": {
0: {0, 0, 0},
1: {0, 1, 1},
2: {0, 1, 0},
3: {0, 0, 1},
4: {},
5: {1},
6: {1},
7: {1},
8: {1},
9: {1},
10: {1},
11: {1},
12: {1},
13: {1},
14: {1},
15: {1},
16: {1},
17: {1},
18: {1},
19: {1},
20: {1},
21: {1},
22: {1},
23: {1},
24: {1},
25: {1},
26: {1},
27: {1},
28: {1},
29: {1},
30: {1},
},
// TODO: B Slice table 9-37
} }
// Map of SliceTypeName[SubMbType][]int{binString} // binOfPOrSPMBTypes provides binarization strings for values of macroblock
binIdxSubMbMap = map[string]map[int][]int{ // type in P or SP slices as defined in table 9-37 of the specifications.
"P": { // NB: binarization of macroblock types 5 to 30 is 1 and not included here.
0: {1}, binOfPOrSPMBTypes = [5][]int{
1: {0, 0}, 0: {0, 0, 0},
2: {0, 1, 1}, 1: {0, 1, 1},
3: {0, 1, 0}, 2: {0, 1, 0},
}, 3: {0, 0, 1},
"SP": { 4: {},
0: {1},
1: {0, 0},
2: {0, 1, 1},
3: {0, 1, 0},
},
// TODO: B slice table 9-38
} }
// Table 9-36, 9-37 // binOfBMBTypes provides binarization strings for values of macroblock
MbBinIdx = []int{1, 2, 3, 4, 5, 6} // type in B slice as defined in table 9-37 of the specifications.
// NB: binarization of macroblock types 23 to 48 is 111101 and is not
// included here.
binOfBMBTypes = [23][]int{
0: {0},
1: {1, 0, 0},
2: {1, 0, 1},
3: {1, 1, 0, 0, 0, 0},
4: {1, 1, 0, 0, 0, 1},
5: {1, 1, 0, 0, 1, 0},
6: {1, 1, 0, 0, 1, 1},
7: {1, 1, 0, 1, 0, 0},
8: {1, 1, 0, 1, 0, 1},
9: {1, 1, 0, 1, 1, 0},
10: {1, 1, 0, 1, 1, 1},
11: {1, 1, 1, 1, 1, 0},
12: {1, 1, 1, 0, 0, 0, 0},
13: {1, 1, 1, 0, 0, 0, 1},
14: {1, 1, 1, 0, 0, 1, 0},
15: {1, 1, 1, 0, 0, 1, 1},
16: {1, 1, 1, 0, 1, 0, 0},
17: {1, 1, 1, 0, 1, 0, 1},
18: {1, 1, 1, 0, 1, 1, 0},
19: {1, 1, 1, 0, 1, 1, 1},
20: {1, 1, 1, 1, 0, 0, 0},
21: {1, 1, 1, 1, 0, 0, 1},
22: {1, 1, 1, 1, 1, 1},
}
)
// Table 9-38 // Binarizations for sub-macroblock types in slice types.
SubMbBinIdx = []int{0, 1, 2, 3, 4, 5} var (
// binOfPorSPSubMBTypes provides binarization strings for values of sub-macroblock
// type in P or SP slices as defined in table 9-38 of the specifications.
binOfPOrSPSubMBTypes = [4][]int{
0: {1},
1: {0, 0},
2: {0, 1, 1},
3: {0, 1, 0},
}
// binOfBSubMBTypes provides binarization strings for values of sub-macroblock
// type in B slices as defined in table 9-38 of the specifications.
binOfBSubMBTypes = [numOfBSubMBTypes][]int{
0: {1},
1: {1, 0, 0},
2: {1, 0, 1},
3: {1, 1, 0, 0, 0},
4: {1, 1, 0, 0, 1},
5: {1, 1, 0, 1, 0},
6: {1, 1, 0, 1, 1},
7: {1, 1, 1, 0, 0, 0},
8: {1, 1, 1, 0, 0, 1},
9: {1, 1, 1, 0, 1, 0},
10: {1, 1, 1, 0, 1, 1},
11: {1, 1, 1, 1, 0},
12: {1, 1, 1, 1, 1},
}
) )
// Table 9-34 // Table 9-34

View File

@ -1,6 +1,33 @@
/*
DESCRIPTION
cabac_test.go provides testing for functionality found in cabac.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
Shawn Smith <shawnpsmith@gmail.com>
LICENSE
Copyright (C) 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 h264dec package h264dec
import "testing" import (
"testing"
)
var ctxIdxTests = []struct { var ctxIdxTests = []struct {
binIdx int binIdx int

View File

@ -0,0 +1,243 @@
/*
TODO: this file should really be in a 'h264enc' package.
DESCRIPTION
cabacenc.go provides functionality for CABAC encoding.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 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 h264dec
import (
"errors"
"fmt"
"math"
)
// Error used by unaryBinString.
var errNegativeSyntaxVal = errors.New("cannot get unary binary string of negative value")
// unaryBinString returns the unary binary string of a syntax element having
// value v, as specified by setion 9.3.2.1 in the specifications.
func unaryBinString(v int) ([]int, error) {
if v < 0 {
return nil, errNegativeSyntaxVal
}
r := make([]int, v+1)
for i := 0; i <= v; i++ {
if i < v {
r[i] = 1
}
}
return r, nil
}
// Error used by truncUnaryBinString.
var errInvalidSyntaxVal = errors.New("syntax value cannot be greater than cMax")
// truncUnaryBinString returns the truncated unary binary string of a syntax
// element v given a cMax as specified in section 9.3.2.2 of the specifications.
func truncUnaryBinString(v, cMax int) ([]int, error) {
if v < 0 {
return nil, errNegativeSyntaxVal
}
if v > cMax {
return nil, errInvalidSyntaxVal
}
if v == cMax {
b, _ := unaryBinString(v)
return b[:len(b)-1], nil
}
return unaryBinString(v)
}
// Error used by unaryExpGolombBinString.
var errInvalidUCoff = errors.New("uCoff cannot be less than or equal to zero")
// unaryExpGolombBinString returns the concatendated unary/k-th order
// Exp-Golomb (UEGk) binary string of a syntax element using the process defined
// in section 9.3.2.3 of the specifications.
func unaryExpGolombBinString(v, uCoff, k int, signedValFlag bool) ([]int, error) {
if uCoff <= 0 {
return nil, errInvalidUCoff
}
prefix, err := truncUnaryBinString(mini(uCoff, absi(v)), uCoff)
if err != nil {
return nil, err
}
return append(prefix, suffix(v, uCoff, k, signedValFlag)...), nil
}
// suffix returns the suffix part of a unary k-th Exp-Golomb binar string
// using the the algorithm as described by pseudo code 9-6 in section 9.3.2.3.
// TODO: could probably reduce allocations.
func suffix(v, uCoff, k int, signedValFlag bool) []int {
var s []int
if absi(v) >= uCoff {
sufS := absi(v) - uCoff
var stop bool
for {
if sufS >= (1 << uint(k)) {
s = append(s, 1)
sufS = sufS - (1 << uint(k))
k++
} else {
s = append(s, 0)
for k = k - 1; k >= 0; k-- {
s = append(s, (sufS>>uint(k))&1)
}
stop = true
}
if stop {
break
}
}
}
if signedValFlag && v != 0 {
if v > 0 {
s = append(s, 0)
} else {
s = append(s, 1)
}
}
return s
}
// Error used by fixedLenBinString.
var errNegativeValue = errors.New("cannot get fixed length binary string of negative value")
// fixedLenBinString returns the fixed-length (FL) binary string of the syntax
// element v, given cMax to determine bin length, as specified by section 9.3.2.4
// of the specifications.
func fixedLenBinString(v, cMax int) ([]int, error) {
if v < 0 {
return nil, errNegativeValue
}
l := int(math.Ceil(math.Log2(float64(cMax + 1))))
r := make([]int, l)
for i := l - 1; i >= 0; i-- {
r[i] = v % 2
v = v / 2
}
return r, nil
}
// Errors used by mbTypeBinString.
var (
errBadMbType = errors.New("macroblock type outside of valid range")
errBadMbSliceType = errors.New("bad slice type for macroblock")
)
// mbTypeBinString returns the macroblock type binary string for the given
// macroblock type value and slice type using the process defined in section
// 9.3.2.5 of the specifications.
func mbTypeBinString(v, slice int) ([]int, error) {
switch slice {
case sliceTypeI:
if v < minIMbType || v > maxIMbType {
return nil, errBadMbType
}
return binOfIMBTypes[v], nil
case sliceTypeSI:
if v < minSIMbType || v > maxSIMbType {
return nil, errBadMbType
}
if v == sliceTypeSI {
return []int{0}, nil
}
return append([]int{1}, binOfIMBTypes[v-1]...), nil
case sliceTypeP, sliceTypeSP:
if v < minPOrSPMbType || v > maxPOrSPMbType || v == P8x8ref0 {
return nil, errBadMbType
}
if v < 5 {
return binOfPOrSPMBTypes[v], nil
}
return append([]int{1}, binOfIMBTypes[v-5]...), nil
case sliceTypeB:
if v < minBMbType || v > maxBMbType {
return nil, errBadMbType
}
if v < 23 {
return binOfBMBTypes[v], nil
}
return append([]int{1, 1, 1, 1, 0, 1}, binOfIMBTypes[v-23]...), nil
default:
return nil, errBadMbSliceType
}
}
// Error used by subMbTypeBinString.
var errBadSubMbSliceType = errors.New("bad slice type for sub-macroblock")
// subMbTypeBinString returns the binary string of a sub-macroblock type
// given the slice in which it is in using the process defined in section
// 9.3.2.5 of the specifications.
func subMbTypeBinString(v, slice int) ([]int, error) {
switch slice {
case sliceTypeP, sliceTypeSP:
if v < minPOrSPSubMbType || v > maxPOrSPSubMbType {
return nil, errBadMbType
}
return binOfPOrSPSubMBTypes[v], nil
case sliceTypeB:
if v < minBSubMbType || v > maxBSubMbType {
return nil, errBadMbType
}
return binOfBSubMBTypes[v], nil
default:
return nil, errBadSubMbSliceType
}
}
// codedBlockPatternBinString returns the binarization for the syntax element
// coded_block_pattern as defined by section 9.3.2.6 in specifications.
func codedBlockPatternBinString(luma, chroma, arrayType int) ([]int, error) {
p, err := fixedLenBinString(luma, 15)
if err != nil {
return nil, fmt.Errorf("fixed length binarization failed with error: %v", err)
}
if arrayType == 0 || arrayType == 3 {
return p, nil
}
s, err := truncUnaryBinString(chroma, 2)
if err != nil {
return nil, fmt.Errorf("truncated unary binarization failed with error: %v", err)
}
return append(p, s...), nil
}

View File

@ -0,0 +1,237 @@
/*
TODO: this file should really be in a 'h264enc' package.
DESCRIPTION
cabacenc_test.go provides testing for functionality found in cabacenc.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 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 h264dec
import (
"reflect"
"testing"
)
func TestMbTypeBinString(t *testing.T) {
tests := []struct {
v, slice int
want []int
err error
}{
{v: 6, slice: sliceTypeI, want: []int{1, 0, 0, 1, 0, 0, 1}},
{v: 26, slice: sliceTypeI, err: errBadMbType},
{v: -1, slice: sliceTypeI, err: errBadMbType},
{v: 4, slice: sliceTypeSI, want: []int{0}},
{v: 6, slice: sliceTypeSI, want: []int{1, 1, 0, 0, 1, 0, 0, 0}},
{v: 0, slice: sliceTypeSI, err: errBadMbType},
{v: 27, slice: sliceTypeSI, err: errBadMbType},
{v: 2, slice: sliceTypeP, want: []int{0, 1, 0}},
{v: 3, slice: sliceTypeSP, want: []int{0, 0, 1}},
{v: 7, slice: sliceTypeP, want: []int{1, 1, 0, 0, 0, 0, 1}},
{v: 7, slice: sliceTypeSP, want: []int{1, 1, 0, 0, 0, 0, 1}},
{v: -1, slice: sliceTypeP, err: errBadMbType},
{v: 31, slice: sliceTypeP, err: errBadMbType},
{v: 8, slice: sliceTypeB, want: []int{1, 1, 0, 1, 0, 1}},
{v: 30, slice: sliceTypeB, want: []int{1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}},
{v: -1, slice: sliceTypeB, err: errBadMbType},
{v: 49, slice: sliceTypeB, err: errBadMbType},
{v: 6, slice: 20, err: errBadMbSliceType},
}
for i, test := range tests {
got, err := mbTypeBinString(test.v, test.slice)
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want)
}
}
}
func TestSubMbTypeBinString(t *testing.T) {
tests := []struct {
v, slice int
want []int
err error
}{
{v: 2, slice: sliceTypeP, want: []int{0, 1, 1}},
{v: 2, slice: sliceTypeSP, want: []int{0, 1, 1}},
{v: -1, slice: sliceTypeSP, err: errBadMbType},
{v: 4, slice: sliceTypeSP, err: errBadMbType},
{v: 9, slice: sliceTypeB, want: []int{1, 1, 1, 0, 1, 0}},
{v: 9, slice: 40, err: errBadSubMbSliceType},
}
for i, test := range tests {
got, err := subMbTypeBinString(test.v, test.slice)
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want)
}
}
}
func TestUnaryBinString(t *testing.T) {
// Test data has been extracted from table 9-35 of the specifications.
tests := []struct {
in int
want []int
err error
}{
{in: 0, want: []int{0}, err: nil},
{in: 1, want: []int{1, 0}, err: nil},
{in: 2, want: []int{1, 1, 0}, err: nil},
{in: 3, want: []int{1, 1, 1, 0}, err: nil},
{in: 4, want: []int{1, 1, 1, 1, 0}, err: nil},
{in: 5, want: []int{1, 1, 1, 1, 1, 0}, err: nil},
{in: -3, want: nil, err: errNegativeSyntaxVal},
}
for i, test := range tests {
got, err := unaryBinString(test.in)
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v", i, err, test.err)
}
if !reflect.DeepEqual(test.want, got) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestFixedLengthBinString(t *testing.T) {
tests := []struct {
v int
cMax int
want []int
err error
}{
{v: 0, cMax: 7, want: []int{0, 0, 0}},
{v: 1, cMax: 7, want: []int{0, 0, 1}},
{v: 2, cMax: 7, want: []int{0, 1, 0}},
{v: 3, cMax: 7, want: []int{0, 1, 1}},
{v: 4, cMax: 7, want: []int{1, 0, 0}},
{v: 5, cMax: 7, want: []int{1, 0, 1}},
{v: 6, cMax: 7, want: []int{1, 1, 0}},
{v: 7, cMax: 7, want: []int{1, 1, 1}},
{v: -1, cMax: 7, want: nil, err: errNegativeValue},
}
for i, test := range tests {
got, err := fixedLenBinString(test.v, test.cMax)
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
if !reflect.DeepEqual(test.want, got) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestTruncUnaryBinString(t *testing.T) {
tests := []struct {
v int
cMax int
want []int
err error
}{
{v: 0, cMax: 10, want: []int{0}, err: nil},
{v: 1, cMax: 10, want: []int{1, 0}, err: nil},
{v: 2, cMax: 10, want: []int{1, 1, 0}, err: nil},
{v: 0, cMax: 0, want: []int{}, err: nil},
{v: 4, cMax: 4, want: []int{1, 1, 1, 1}, err: nil},
{v: 1, cMax: 10, want: []int{1, 0}, err: nil},
{v: 2, cMax: 10, want: []int{1, 1, 0}, err: nil},
{v: -3, cMax: 10, want: nil, err: errNegativeSyntaxVal},
{v: 5, cMax: 4, want: nil, err: errInvalidSyntaxVal},
}
for i, test := range tests {
got, err := truncUnaryBinString(test.v, test.cMax)
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v", i, err, test.err)
}
if !reflect.DeepEqual(test.want, got) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestUEGkSuffix(t *testing.T) {
// Data from https://patents.google.com/patent/US20070092150
tests := []struct {
v, uCoff, k int
signedValFlag bool
want []int
}{
0: {v: 14, uCoff: 14, want: []int{0}},
1: {v: 15, uCoff: 14, want: []int{1, 0, 0}},
2: {v: 16, uCoff: 14, want: []int{1, 0, 1}},
3: {v: 17, uCoff: 14, want: []int{1, 1, 0, 0, 0}},
4: {v: 18, uCoff: 14, want: []int{1, 1, 0, 0, 1}},
5: {v: 19, uCoff: 14, want: []int{1, 1, 0, 1, 0}},
6: {v: 20, uCoff: 14, want: []int{1, 1, 0, 1, 1}},
7: {v: 21, uCoff: 14, want: []int{1, 1, 1, 0, 0, 0, 0}},
8: {v: 22, uCoff: 14, want: []int{1, 1, 1, 0, 0, 0, 1}},
9: {v: 23, uCoff: 14, want: []int{1, 1, 1, 0, 0, 1, 0}},
10: {v: 24, uCoff: 14, want: []int{1, 1, 1, 0, 0, 1, 1}},
11: {v: 25, uCoff: 14, want: []int{1, 1, 1, 0, 1, 0, 0}},
}
for i, test := range tests {
got := suffix(test.v, test.uCoff, test.k, test.signedValFlag)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestUnaryExpGolombBinString(t *testing.T) {
tests := []struct {
v, uCoff, k int
signedValFlag bool
want []int
}{
0: {v: 7, uCoff: 14, want: []int{1, 1, 1, 1, 1, 1, 1, 0}},
1: {v: 17, uCoff: 14, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}},
2: {v: 15, uCoff: 14, signedValFlag: true, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}},
3: {v: -15, uCoff: 14, signedValFlag: true, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1}},
}
for i, test := range tests {
got, err := unaryExpGolombBinString(test.v, test.uCoff, test.k, test.signedValFlag)
if err != nil {
t.Errorf("did not expect error %v for test %d", err, i)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}

View File

@ -0,0 +1,46 @@
/*
DESCRIPTION
cavlc.go provides utilities for context-adaptive variable-length coding
for the parsing of H.264 syntax structure fields.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 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 h264dec
import (
"fmt"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
// parseLevelPrefix parses the level_prefix variable as specified by the process
// outlined in section 9.2.2.1 in the specifications.
func parseLevelPrefix(br *bits.BitReader) (int, error) {
zeros := -1
for b := 0; b != 1; zeros++ {
_b, err := br.ReadBits(1)
if err != nil {
return -1, fmt.Errorf("could not read bit, failed with error: %v", err)
}
b = int(_b)
}
return zeros, nil
}

View File

@ -0,0 +1,55 @@
/*
DESCRIPTION
cavlc_test.go provides testing for functionality in cavlc.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 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 h264dec
import (
"bytes"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
func TestParseLevelPrefix(t *testing.T) {
tests := []struct {
in string
want int
}{
{in: "00001", want: 4},
{in: "0000001", want: 6},
{in: "1", want: 0},
}
for i, test := range tests {
s, _ := binToSlice(test.in)
l, err := parseLevelPrefix(bits.NewBitReader(bytes.NewReader(s)))
if err != nil {
t.Errorf("did not expect error: %v, for test %d", err, i)
}
if l != test.want {
t.Errorf("did not get expected result for test %d\nGot: %d\nWant: %d\n", i, l, test.want)
}
}
}

View File

@ -9,7 +9,7 @@ const (
naluTypeSlicePartC naluTypeSlicePartC
naluTypeSliceIDRPicture naluTypeSliceIDRPicture
naluTypeSEI naluTypeSEI
naluTypeSPS NALTypeSPS
naluTypePPS naluTypePPS
naluTypeAccessUnitDelimiter naluTypeAccessUnitDelimiter
naluTypeEndOfSequence naluTypeEndOfSequence

View File

@ -7,7 +7,9 @@ AUTHORS
*/ */
package h264dec package h264dec
import "errors" import (
"errors"
)
// binToSlice is a helper function to convert a string of binary into a // binToSlice is a helper function to convert a string of binary into a
// corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}. // corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}.
@ -19,7 +21,7 @@ func binToSlice(s string) ([]byte, error) {
bytes []byte bytes []byte
) )
for _, c := range s { for i, c := range s {
switch c { switch c {
case ' ': case ' ':
continue continue
@ -31,7 +33,7 @@ func binToSlice(s string) ([]byte, error) {
} }
a >>= 1 a >>= 1
if a == 0 { if a == 0 || i == (len(s)-1) {
bytes = append(bytes, cur) bytes = append(bytes, cur)
cur = 0 cur = 0
a = 0x80 a = 0x80
@ -39,3 +41,24 @@ func binToSlice(s string) ([]byte, error) {
} }
return bytes, nil return bytes, nil
} }
func maxi(a, b int) int {
if a > b {
return a
}
return b
}
func mini(a, b int) int {
if a < b {
return a
}
return b
}
func absi(a int) int {
if a < 0 {
return -a
}
return a
}

View File

@ -4,6 +4,42 @@ import (
"errors" "errors"
) )
// P slice macroblock types.
const (
P8x8ref0 = 4
)
// Number of macroblock types for each slice type.
const (
numOfIMBTypes = 26
numOfPMBTypes = 31
numOfSPMBTypes = 31
numOfBMBTypes = 49
)
// Number of sub macroblock types for each slice type.
const (
numOfPSubMBTypes = 4
numOfSPSubMBTypes = 4
numOfBSubMBTypes = 13
)
// Min and max macroblock types for slice types.
const (
minIMbType = 0
maxIMbType = 25
minPOrSPMbType = 0
maxPOrSPMbType = 30
minBMbType = 0
maxBMbType = 48
minSIMbType = 1
maxSIMbType = 26
minPOrSPSubMbType = 0
maxPOrSPSubMbType = 3
minBSubMbType = 0
maxBSubMbType = 12
)
const MB_TYPE_INFERRED = 1000 const MB_TYPE_INFERRED = 1000
var ( var (

View File

@ -263,10 +263,10 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) {
next3Bytes, err := br.PeekBits(24) next3Bytes, err := br.PeekBits(24)
// If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in // If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in
// the source, we will get an io.EOF; we wish to ignore this and continue. // the source, we will get an io.ErrUnexpectedEOF; we wish to ignore this
// The call to moreRBSPData will determine when we have reached the end of // and continue. The call to moreRBSPData will determine when we have
// the NAL unit. // reached the end of the NAL unit.
if err != nil && errors.Cause(err) != io.EOF { if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF {
return nil, errors.Wrap(err, "could not Peek next 3 bytes") return nil, errors.Wrap(err, "could not Peek next 3 bytes")
} }

View File

@ -0,0 +1,284 @@
/*
DESCRIPTION
nalunit_test.go provides testing for functionality in nalunit.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
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 h264dec
import (
"bytes"
"reflect"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
func TestNewMVCExtension(t *testing.T) {
tests := []struct {
in string
want *MVCExtension
err error
}{
{
in: "0" + // u(1) non_idr_flag = false
"00 0010" + // u(6) priority_id = 2
"00 0001 1000" + // u(10) view_id = 24
"100" + // u(3) temporal_id = 4
"1" + // u(1) anchor_pic_flag = true
"0" + // u(1) inter_view_flag = false
"1" + // u(1) reserved_one_bit = 1
"0", // Some padding
want: &MVCExtension{
NonIdrFlag: false,
PriorityID: 2,
ViewID: 24,
TemporalID: 4,
AnchorPicFlag: true,
InterViewFlag: false,
ReservedOneBit: 1,
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("did not expect error %v from binToSlice for test %d", err, i)
}
got, err := NewMVCExtension(bits.NewBitReader(bytes.NewReader(inBytes)))
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want)
}
}
}
func TestNewThreeDAVCExtension(t *testing.T) {
tests := []struct {
in string
want *ThreeDAVCExtension
err error
}{
{
in: "0001 0000" + // u(8) view_idx = 16
"1" + // u(1) depth_flag = true
"0" + // u(1) non_idr_flag = false
"010" + // u(1) temporal_id = 2
"1" + // u(1) anchor_pic_flag = true
"1" + // u(1) inter_view_flag = true
"000", // Some padding
want: &ThreeDAVCExtension{
ViewIdx: 16,
DepthFlag: true,
NonIdrFlag: false,
TemporalID: 2,
AnchorPicFlag: true,
InterViewFlag: true,
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("did not expect error %v from binToSlice for test %d", err, i)
}
got, err := NewThreeDAVCExtension(bits.NewBitReader(bytes.NewReader(inBytes)))
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want)
}
}
}
func TestSVCExtension(t *testing.T) {
tests := []struct {
in string
want *SVCExtension
err error
}{
{
in: "0" + // u(1) idr_flag = false
"10 0000" + // u(6) priority_id = 32
"0" + // u(1) no_inter_layer_pred_flag = false
"001" + // u(3) dependency_id = 1
"1000" + // u(4) quality_id = 8
"010" + // u(3) temporal_id = 2
"1" + // u(1) use_ref_base_pic_flag = true
"0" + // u(1) discardable_flag = false
"0" + // u(1) output_flag = false
"11" + // ReservedThree2Bits
"0", // padding
want: &SVCExtension{
IdrFlag: false,
PriorityID: 32,
NoInterLayerPredFlag: false,
DependencyID: 1,
QualityID: 8,
TemporalID: 2,
UseRefBasePicFlag: true,
DiscardableFlag: false,
OutputFlag: false,
ReservedThree2Bits: 3,
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("did not expect error %v from binToSlice for test %d", err, i)
}
got, err := NewSVCExtension(bits.NewBitReader(bytes.NewReader(inBytes)))
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want)
}
}
}
func TestNewNALUnit(t *testing.T) {
tests := []struct {
in string
want *NALUnit
err error
}{
{
in: "0" + // f(1) forbidden_zero_bit = 0
"01" + // u(2) nal_ref_idc = 1
"0 1110" + // u(5) nal_unit_type = 14
"1" + // u(1) svc_extension_flag = true
// svc extension
"0" + // u(1) idr_flag = false
"10 0000" + // u(6) priority_id = 32
"0" + // u(1) no_inter_layer_pred_flag = false
"001" + // u(3) dependency_id = 1
"1000" + // u(4) quality_id = 8
"010" + // u(3) temporal_id = 2
"1" + // u(1) use_ref_base_pic_flag = true
"0" + // u(1) discardable_flag = false
"0" + // u(1) output_flag = false
"11" + // ReservedThree2Bits
// rbsp bytes
"0000 0001" +
"0000 0010" +
"0000 0100" +
"0000 1000" +
"1000 0000", // trailing bits
want: &NALUnit{
ForbiddenZeroBit: 0,
RefIdc: 1,
Type: 14,
SVCExtensionFlag: true,
SVCExtension: &SVCExtension{
IdrFlag: false,
PriorityID: 32,
NoInterLayerPredFlag: false,
DependencyID: 1,
QualityID: 8,
TemporalID: 2,
UseRefBasePicFlag: true,
DiscardableFlag: false,
OutputFlag: false,
ReservedThree2Bits: 3,
},
RBSP: []byte{
0x01,
0x02,
0x04,
0x08,
},
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("did not expect error %v from binToSlice for test %d", err, i)
}
got, err := NewNALUnit(bits.NewBitReader(bytes.NewReader(inBytes)))
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
if !nalEqual(got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want)
}
}
}
// nalEqual returns true if two NALUnits are equal.
func nalEqual(a, b *NALUnit) bool {
aCopy := nalWithoutExtensions(*a)
bCopy := nalWithoutExtensions(*b)
if !reflect.DeepEqual(aCopy, bCopy) {
return false
}
if (a.SVCExtension == nil || b.SVCExtension == nil) &&
(a.SVCExtension != b.SVCExtension) {
return false
}
if (a.MVCExtension == nil || b.MVCExtension == nil) &&
(a.MVCExtension != b.MVCExtension) {
return false
}
if (a.ThreeDAVCExtension == nil || b.ThreeDAVCExtension == nil) &&
(a.ThreeDAVCExtension != b.ThreeDAVCExtension) {
return false
}
if !reflect.DeepEqual(a.SVCExtension, b.SVCExtension) ||
!reflect.DeepEqual(a.MVCExtension, b.MVCExtension) ||
!reflect.DeepEqual(a.ThreeDAVCExtension, b.ThreeDAVCExtension) {
return false
}
return true
}
func nalWithoutExtensions(n NALUnit) NALUnit {
n.SVCExtension = nil
n.MVCExtension = nil
n.ThreeDAVCExtension = nil
return n
}

View File

@ -61,7 +61,7 @@ func (h *H264Reader) Start() {
// TODO: need to handle error from this. // TODO: need to handle error from this.
nalUnit, _, _ := h.readNalUnit() nalUnit, _, _ := h.readNalUnit()
switch nalUnit.Type { switch nalUnit.Type {
case naluTypeSPS: case NALTypeSPS:
// TODO: handle this error // TODO: handle this error
sps, _ := NewSPS(nalUnit.RBSP, false) sps, _ := NewSPS(nalUnit.RBSP, false)
h.VideoStreams = append( h.VideoStreams = append(

View File

@ -1,3 +1,12 @@
/*
DESCRIPTION
slice.go provides parsing functionality for slice raw byte sequence data.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
Bruce McMoran <mcmoranbjr@gmail.com>
*/
package h264dec package h264dec
import ( import (
@ -9,6 +18,15 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Slice types as defined by table 7-6 in specifications.
const (
sliceTypeP = 0
sliceTypeB = 1
sliceTypeI = 2
sliceTypeSP = 3
sliceTypeSI = 4
)
// Chroma formats as defined in section 6.2, tab 6-1. // Chroma formats as defined in section 6.2, tab 6-1.
const ( const (
chromaMonochrome = iota chromaMonochrome = iota
@ -21,6 +39,8 @@ type VideoStream struct {
SPS *SPS SPS *SPS
PPS *PPS PPS *PPS
Slices []*SliceContext Slices []*SliceContext
ChromaArrayType int
} }
type SliceContext struct { type SliceContext struct {
*NALUnit *NALUnit
@ -37,11 +57,10 @@ type Slice struct {
// (defined in 7.3.3.1 of specifications) and a ref_pic_list_mvc_modification // (defined in 7.3.3.1 of specifications) and a ref_pic_list_mvc_modification
// (defined in H.7.3.3.1.1 of specifications). // (defined in H.7.3.3.1.1 of specifications).
type RefPicListModification struct { type RefPicListModification struct {
RefPicListModificationFlagL0 bool RefPicListModificationFlag [2]bool
ModificationOfPicNums int ModificationOfPicNums [2][]int
AbsDiffPicNumMinus1 int AbsDiffPicNumMinus1 [2][]int
LongTermPicNum int LongTermPicNum [2][]int
RefPicListModificationFlagL1 bool
} }
// TODO: need to complete this. // TODO: need to complete this.
@ -55,43 +74,57 @@ func NewRefPicListMVCModifiation(br *bits.BitReader) (*RefPicListModification, e
// NewRefPicListModification parses elements of a ref_pic_list_modification // NewRefPicListModification parses elements of a ref_pic_list_modification
// following the syntax structure defined in section 7.3.3.1, and returns as // following the syntax structure defined in section 7.3.3.1, and returns as
// a new RefPicListModification. // a new RefPicListModification.
func NewRefPicListModification(br *bits.BitReader, h *SliceHeader) (*RefPicListModification, error) { func NewRefPicListModification(br *bits.BitReader, p *PPS, s *SliceHeader) (*RefPicListModification, error) {
ref := &RefPicListModification{} r := &RefPicListModification{}
r := newFieldReader(br) r.ModificationOfPicNums[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2)
r.ModificationOfPicNums[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2)
r.AbsDiffPicNumMinus1[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2)
r.AbsDiffPicNumMinus1[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2)
r.LongTermPicNum[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2)
r.LongTermPicNum[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2)
fr := newFieldReader(br)
// 7.3.3.1 // 7.3.3.1
if h.SliceType%5 != 2 && h.SliceType%5 != 4 { if s.SliceType%5 != 2 && s.SliceType%5 != 4 {
ref.RefPicListModificationFlagL0 = r.readBits(1) == 1 r.RefPicListModificationFlag[0] = fr.readBits(1) == 1
if ref.RefPicListModificationFlagL0 { if r.RefPicListModificationFlag[0] {
for ref.ModificationOfPicNums != 3 { for i := 0; ; i++ {
ref.ModificationOfPicNums = int(r.readUe()) r.ModificationOfPicNums[0][i] = int(fr.readUe())
if ref.ModificationOfPicNums == 0 || ref.ModificationOfPicNums == 1 { if r.ModificationOfPicNums[0][i] == 0 || r.ModificationOfPicNums[0][i] == 1 {
ref.AbsDiffPicNumMinus1 = int(r.readUe()) r.AbsDiffPicNumMinus1[0][i] = int(fr.readUe())
} else if ref.ModificationOfPicNums == 2 { } else if r.ModificationOfPicNums[0][i] == 2 {
ref.LongTermPicNum = int(r.readUe()) r.LongTermPicNum[0][i] = int(fr.readUe())
} }
}
}
} if r.ModificationOfPicNums[0][i] == 3 {
if h.SliceType%5 == 1 { break
ref.RefPicListModificationFlagL1 = r.readBits(1) == 1
if ref.RefPicListModificationFlagL1 {
for ref.ModificationOfPicNums != 3 {
ref.ModificationOfPicNums = int(r.readUe())
if ref.ModificationOfPicNums == 0 || ref.ModificationOfPicNums == 1 {
ref.AbsDiffPicNumMinus1 = int(r.readUe())
} else if ref.ModificationOfPicNums == 2 {
ref.LongTermPicNum = int(r.readUe())
} }
} }
} }
} }
// refPicListModification()
return nil, nil if s.SliceType%5 == 1 {
r.RefPicListModificationFlag[1] = fr.readBits(1) == 1
if r.RefPicListModificationFlag[1] {
for i := 0; ; i++ {
r.ModificationOfPicNums[1][i] = int(fr.readUe())
if r.ModificationOfPicNums[1][i] == 0 || r.ModificationOfPicNums[1][i] == 1 {
r.AbsDiffPicNumMinus1[1][i] = int(fr.readUe())
} else if r.ModificationOfPicNums[1][i] == 2 {
r.LongTermPicNum[1][i] = int(fr.readUe())
}
if r.ModificationOfPicNums[1][i] == 3 {
break
}
}
}
}
return r, nil
} }
// PredWeightTable provides elements of a pred_weight_table syntax structure // PredWeightTable provides elements of a pred_weight_table syntax structure
@ -99,7 +132,6 @@ func NewRefPicListModification(br *bits.BitReader, h *SliceHeader) (*RefPicListM
type PredWeightTable struct { type PredWeightTable struct {
LumaLog2WeightDenom int LumaLog2WeightDenom int
ChromaLog2WeightDenom int ChromaLog2WeightDenom int
ChromaArrayType int
LumaWeightL0Flag bool LumaWeightL0Flag bool
LumaWeightL0 []int LumaWeightL0 []int
LumaOffsetL0 []int LumaOffsetL0 []int
@ -117,13 +149,13 @@ type PredWeightTable struct {
// NewPredWeightTable parses elements of a pred_weight_table following the // NewPredWeightTable parses elements of a pred_weight_table following the
// syntax structure defined in section 7.3.3.2, and returns as a new // syntax structure defined in section 7.3.3.2, and returns as a new
// PredWeightTable. // PredWeightTable.
func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, error) { func NewPredWeightTable(br *bits.BitReader, h *SliceHeader, chromaArrayType int) (*PredWeightTable, error) {
p := &PredWeightTable{} p := &PredWeightTable{}
r := newFieldReader(br) r := newFieldReader(br)
p.LumaLog2WeightDenom = int(r.readUe()) p.LumaLog2WeightDenom = int(r.readUe())
if p.ChromaArrayType != 0 { if chromaArrayType != 0 {
p.ChromaLog2WeightDenom = int(r.readUe()) p.ChromaLog2WeightDenom = int(r.readUe())
} }
for i := 0; i <= h.NumRefIdxL0ActiveMinus1; i++ { for i := 0; i <= h.NumRefIdxL0ActiveMinus1; i++ {
@ -142,7 +174,7 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e
} }
p.LumaOffsetL0 = append(p.LumaOffsetL0, se) p.LumaOffsetL0 = append(p.LumaOffsetL0, se)
} }
if p.ChromaArrayType != 0 { if chromaArrayType != 0 {
b, err := br.ReadBits(1) b, err := br.ReadBits(1)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag")
@ -189,7 +221,7 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e
} }
p.LumaOffsetL1 = append(p.LumaOffsetL1, se) p.LumaOffsetL1 = append(p.LumaOffsetL1, se)
} }
if p.ChromaArrayType != 0 { if chromaArrayType != 0 {
b, err := br.ReadBits(1) b, err := br.ReadBits(1)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag")
@ -222,11 +254,16 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e
// DecRefPicMarking provides elements of a dec_ref_pic_marking syntax structure // DecRefPicMarking provides elements of a dec_ref_pic_marking syntax structure
// as defined in section 7.3.3.3 of the specifications. // as defined in section 7.3.3.3 of the specifications.
type DecRefPicMarking struct { type DecRefPicMarking struct {
NoOutputOfPriorPicsFlag bool NoOutputOfPriorPicsFlag bool
LongTermReferenceFlag bool LongTermReferenceFlag bool
AdaptiveRefPicMarkingModeFlag bool AdaptiveRefPicMarkingModeFlag bool
elements []drpmElement
}
type drpmElement struct {
MemoryManagementControlOperation int MemoryManagementControlOperation int
DifferenceOfPicNumsMinus1 int DifferenceOfPicNumsMinus1 int
LongTermPicNum int
LongTermFrameIdx int LongTermFrameIdx int
MaxLongTermFrameIdxPlus1 int MaxLongTermFrameIdxPlus1 int
} }
@ -234,7 +271,7 @@ type DecRefPicMarking struct {
// NewDecRefPicMarking parses elements of a dec_ref_pic_marking following the // NewDecRefPicMarking parses elements of a dec_ref_pic_marking following the
// syntax structure defined in section 7.3.3.3, and returns as a new // syntax structure defined in section 7.3.3.3, and returns as a new
// DecRefPicMarking. // DecRefPicMarking.
func NewDecRefPicMarking(br *bits.BitReader, idrPic bool, h *SliceHeader) (*DecRefPicMarking, error) { func NewDecRefPicMarking(br *bits.BitReader, idrPic bool) (*DecRefPicMarking, error) {
d := &DecRefPicMarking{} d := &DecRefPicMarking{}
r := newFieldReader(br) r := newFieldReader(br)
if idrPic { if idrPic {
@ -257,19 +294,26 @@ func NewDecRefPicMarking(br *bits.BitReader, idrPic bool, h *SliceHeader) (*DecR
d.AdaptiveRefPicMarkingModeFlag = b == 1 d.AdaptiveRefPicMarkingModeFlag = b == 1
if d.AdaptiveRefPicMarkingModeFlag { if d.AdaptiveRefPicMarkingModeFlag {
d.MemoryManagementControlOperation = int(r.readUe()) for i := 0; ; i++ {
for d.MemoryManagementControlOperation != 0 { d.elements = append(d.elements, drpmElement{})
if d.MemoryManagementControlOperation == 1 || d.MemoryManagementControlOperation == 3 {
d.DifferenceOfPicNumsMinus1 = int(r.readUe()) d.elements[i].MemoryManagementControlOperation = int(r.readUe())
if d.elements[i].MemoryManagementControlOperation == 1 || d.elements[i].MemoryManagementControlOperation == 3 {
d.elements[i].DifferenceOfPicNumsMinus1 = int(r.readUe())
} }
if d.MemoryManagementControlOperation == 2 { if d.elements[i].MemoryManagementControlOperation == 2 {
h.RefPicListModification.LongTermPicNum = int(r.readUe()) d.elements[i].LongTermPicNum = int(r.readUe())
} }
if d.MemoryManagementControlOperation == 3 || d.MemoryManagementControlOperation == 6 { if d.elements[i].MemoryManagementControlOperation == 3 || d.elements[i].MemoryManagementControlOperation == 6 {
d.LongTermFrameIdx = int(r.readUe()) d.elements[i].LongTermFrameIdx = int(r.readUe())
} }
if d.MemoryManagementControlOperation == 4 { if d.elements[i].MemoryManagementControlOperation == 4 {
d.MaxLongTermFrameIdxPlus1 = int(r.readUe()) d.elements[i].MaxLongTermFrameIdxPlus1 = int(r.readUe())
}
if d.elements[i].MemoryManagementControlOperation == 0 {
break
} }
} }
} }
@ -482,9 +526,10 @@ func NumMbPart(nalUnit *NALUnit, sps *SPS, header *SliceHeader, data *SliceData)
return numMbPart return numMbPart
} }
func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { func MbPred(chromaArrayType int, sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error {
r := newFieldReader(br)
var cabac *CABAC var cabac *CABAC
r := newFieldReader(br)
sliceType := sliceTypeMap[sliceContext.Slice.Header.SliceType] sliceType := sliceTypeMap[sliceContext.Slice.Header.SliceType]
mbPartPredMode, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, 0) mbPartPredMode, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, 0)
if err != nil { if err != nil {
@ -584,7 +629,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error {
} }
} }
if sliceContext.Slice.Header.ChromaArrayType == 1 || sliceContext.Slice.Header.ChromaArrayType == 2 { if chromaArrayType == 1 || chromaArrayType == 2 {
if sliceContext.PPS.EntropyCodingMode == 1 { if sliceContext.PPS.EntropyCodingMode == 1 {
// TODO: ue(v) or ae(v) // TODO: ue(v) or ae(v)
binarization := NewBinarization( binarization := NewBinarization(
@ -828,7 +873,7 @@ func MbaffFrameFlag(sps *SPS, header *SliceHeader) int {
return 0 return 0
} }
func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) { func NewSliceData(chromaArrayType int, sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) {
r := newFieldReader(br) r := newFieldReader(br)
var cabac *CABAC var cabac *CABAC
sliceContext.Slice.Data = &SliceData{BitReader: br} sliceContext.Slice.Data = &SliceData{BitReader: br}
@ -1057,7 +1102,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e
} }
} }
// TODO: fix nil argument for. // TODO: fix nil argument for.
MbPred(sliceContext, br, nil) MbPred(chromaArrayType, sliceContext, br, nil)
} }
m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0)
if err != nil { if err != nil {
@ -1076,7 +1121,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e
} else { } else {
me, _ := readMe( me, _ := readMe(
br, br,
uint(sliceContext.Slice.Header.ChromaArrayType), uint(chromaArrayType),
// TODO: fix this // TODO: fix this
//MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0))) //MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0)))
0) 0)
@ -1153,10 +1198,10 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e
func (c *SliceContext) Update(header *SliceHeader, data *SliceData) { func (c *SliceContext) Update(header *SliceHeader, data *SliceData) {
c.Slice = &Slice{Header: header, Data: data} c.Slice = &Slice{Header: header, Data: data}
} }
func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket bool) (*SliceContext, error) { func NewSliceContext(vid *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket bool) (*SliceContext, error) {
var err error var err error
sps := videoStream.SPS sps := vid.SPS
pps := videoStream.PPS pps := vid.PPS
logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[int(nalUnit.Type)], len(rbsp), len(rbsp)*8) logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[int(nalUnit.Type)], len(rbsp), len(rbsp)*8)
logger.Printf("debug: \t%#v\n", rbsp[0:8]) logger.Printf("debug: \t%#v\n", rbsp[0:8])
var idrPic bool var idrPic bool
@ -1165,9 +1210,9 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh
} }
header := SliceHeader{} header := SliceHeader{}
if sps.SeparateColorPlaneFlag { if sps.SeparateColorPlaneFlag {
header.ChromaArrayType = 0 vid.ChromaArrayType = 0
} else { } else {
header.ChromaArrayType = int(sps.ChromaFormatIDC) vid.ChromaArrayType = int(sps.ChromaFormatIDC)
} }
br := bits.NewBitReader(bytes.NewReader(rbsp)) br := bits.NewBitReader(bytes.NewReader(rbsp))
r := newFieldReader(br) r := newFieldReader(br)
@ -1261,21 +1306,21 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh
// H.7.3.3.1.1 // H.7.3.3.1.1
// refPicListMvcModifications() // refPicListMvcModifications()
} else { } else {
header.RefPicListModification, err = NewRefPicListModification(br, &header) header.RefPicListModification, err = NewRefPicListModification(br, pps, &header)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not parse RefPicListModification") return nil, errors.Wrap(err, "could not parse RefPicListModification")
} }
} }
if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") {
header.PredWeightTable, err = NewPredWeightTable(br, &header) header.PredWeightTable, err = NewPredWeightTable(br, &header, vid.ChromaArrayType)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not parse PredWeightTable") return nil, errors.Wrap(err, "could not parse PredWeightTable")
} }
} }
if nalUnit.RefIdc != 0 { if nalUnit.RefIdc != 0 {
// devRefPicMarking() // devRefPicMarking()
header.DecRefPicMarking, err = NewDecRefPicMarking(br, idrPic, &header) header.DecRefPicMarking, err = NewDecRefPicMarking(br, idrPic)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not parse DecRefPicMarking") return nil, errors.Wrap(err, "could not parse DecRefPicMarking")
} }
@ -1321,7 +1366,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh
Header: &header, Header: &header,
}, },
} }
sliceContext.Slice.Data, err = NewSliceData(sliceContext, br) sliceContext.Slice.Data, err = NewSliceData(vid.ChromaArrayType, sliceContext, br)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not create slice data") return nil, errors.Wrap(err, "could not create slice data")
} }

View File

@ -1,6 +1,37 @@
/*
DESCRIPTION
slice_test.go provides testing for parsing functionality found in slice.go.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>
Shawn Smith <shawn@ausocean.org>
LICENSE
Copyright (C) 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 h264dec package h264dec
import "testing" import (
"bytes"
"reflect"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
var subWidthCTests = []struct { var subWidthCTests = []struct {
in SPS in SPS
@ -47,3 +78,257 @@ func TestSubHeightC(t *testing.T) {
} }
} }
} }
func TestNewRefPicListModification(t *testing.T) {
tests := []struct {
in string
s SliceHeader
p PPS
want RefPicListModification
}{
{
in: "1" + // u(1) ref_pic_list_modification_flag_l0=true
// First modification for list0
"1" + // ue(v) modification_of_pic_nums_idc[0][0] = 0
"010" + // ue(v) abs_diff_pic_num_minus1[0][0] = 1
// Second modification for list0
"010" + // ue(v) modification_of_pic_nums_idc[0][1] = 1
"011" + // ue(v) abs_diff_pic_num_minus1[0][1] = 2
// Third modification for list0
"011" + // ue(v) modification_of_pic_nums_idc[0][2] = 2
"010" + // ue(v) long_term_pic_num = 1
// Fourth modification does not exist
"00100", // ue(v) modification_of_pic_nums_idc[0][3] = 3
s: SliceHeader{
SliceType: 3,
},
p: PPS{
NumRefIdxL0DefaultActiveMinus1: 2,
},
want: RefPicListModification{
RefPicListModificationFlag: [2]bool{true, false},
ModificationOfPicNums: [2][]int{{0, 1, 2, 3}, {0, 0}},
AbsDiffPicNumMinus1: [2][]int{{1, 2, 0, 0}, {0, 0}},
LongTermPicNum: [2][]int{{0, 0, 1, 0}, {0, 0}},
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("unexpected error %v for binToSlice in test %d", err, i)
}
got, err := NewRefPicListModification(bits.NewBitReader(bytes.NewReader(inBytes)), &test.p, &test.s)
if err != nil {
t.Fatalf("unexpected error %v for NewRefPicListModification in test %d", err, i)
}
if !reflect.DeepEqual(*got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestNewPredWeightTable(t *testing.T) {
tests := []struct {
in string
sliceHeader SliceHeader
chromaArrayType int
want PredWeightTable
}{
{
in: "011" + // ue(v) luma_log2_weight_denom = 2
"00100" + // ue(v) chroma_log2_weigght_denom = 3
// list0
// i = 0
"1" + // u(1) luma_weight_l0_flag = true
"011" + // se(v) luma_weight_l0[0] = -1
"010" + // se(v) luma_offset_l0[0] = 1
"1" + // u(1) chroma_weight_l0_flag = true
// i = 0, j = 0
"010" + // se(v) chroma_weight_l0[0][0] = 1
"010" + // se(v) chroma_offset_l0[0][0] = 1
// i = 0, j = 1
"010" + // se(v) chroma_weight_l0[0][1] = 1
"010" + // se(v) chroma_offset_l0[0][1] = 1
// i = 1
"1" + // u(1) luma_weight_l0_flag = true
"011" + // se(v) luma_weight_l0[1] = -1
"00100" + // se(v) luma_offset_l0[1] = 2
"1" + // u(1) chroma_weight_l0_flag = true
// i = 1, j = 0
"011" + // se(v) chroma_weight_l0[1][0] = -1
"00101" + // se(v) chroma_offset_l0[1][0] = -2
// i = 1, j = 1
"011" + // se(v) chroma_weight_l0[1][1] = -1
"011" + // se(v) chroma_offset_l0[1][1] = -1
// list1
// i = 0
"1" + // u(1) luma_weight_l1_flag = true
"011" + // se(v) luma_weight_l1[0] = -1
"010" + // se(v) luma_offset_l1[0] = 1
"1" + // u(1) chroma_weight_l1_flag = true
// i = 0, j = 0
"010" + // se(v) chroma_weight_l1[0][0] = 1
"010" + // se(v) chroma_offset_l1[0][0] = 1
// i = 0, j = 1
"010" + // se(v) chroma_weight_l1[0][1] = 1
"010" + // se(v) chroma_offset_l1[0][1] = 1
// i = 1
"1" + // u(1) luma_weight_l1_flag = true
"011" + // se(v) luma_weight_l1[1] = -1
"00100" + // se(v) luma_offset_l1[1] = 2
"1" + // u(1) chroma_weight_l1_flag = true
// i = 1, j = 0
"011" + // se(v) chroma_weight_l0[1][0] = -1
"00101" + // se(v) chroma_offset_l0[1][0] = -2
// i = 1, j = 1
"011" + // se(v) chroma_weight_l0[1][1] = -1
"011", // se(v) chroma_offset_l0[1][1] = -1
sliceHeader: SliceHeader{
NumRefIdxL0ActiveMinus1: 1,
NumRefIdxL1ActiveMinus1: 1,
SliceType: 1,
},
chromaArrayType: 1,
want: PredWeightTable{
LumaLog2WeightDenom: 2,
ChromaLog2WeightDenom: 3,
LumaWeightL0Flag: true,
LumaWeightL0: []int{-1, -1},
LumaOffsetL0: []int{1, 2},
ChromaWeightL0Flag: true,
ChromaWeightL0: [][]int{{1, 1}, {-1, -1}},
ChromaOffsetL0: [][]int{{1, 1}, {-2, -1}},
LumaWeightL1Flag: true,
LumaWeightL1: []int{-1, -1},
LumaOffsetL1: []int{1, 2},
ChromaWeightL1Flag: true,
ChromaWeightL1: [][]int{{1, 1}, {-1, -1}},
ChromaOffsetL1: [][]int{{1, 1}, {-2, -1}},
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("unexpected error %v for binToSlice in test %d", err, i)
}
got, err := NewPredWeightTable(bits.NewBitReader(bytes.NewReader(inBytes)),
&test.sliceHeader, test.chromaArrayType)
if err != nil {
t.Fatalf("unexpected error %v for NewPredWeightTable in test %d", err, i)
}
if !reflect.DeepEqual(*got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}
func TestDecRefPicMarking(t *testing.T) {
tests := []struct {
in string
idrPic bool
want DecRefPicMarking
}{
{
in: "0" + // u(1) no_output_of_prior_pics_flag = false
"1", // u(1) long_term_reference_flag = true
idrPic: true,
want: DecRefPicMarking{
NoOutputOfPriorPicsFlag: false,
LongTermReferenceFlag: true,
},
},
{
in: "1" + // u(1) adaptive_ref_pic_marking_mode_flag = true
"010" + // ue(v) memory_management_control_operation = 1
"011" + // ue(v) difference_of_pic_nums_minus1 = 2
"00100" + // ue(v) memory_management_control_operation = 3
"1" + // ue(v) difference_of_pic_nums_minus1 = 0
"011" + // ue(v) long_term_frame_idx = 2
"011" + // ue(v) memory_management_control_operation = 2
"00100" + // ue(v) long_term_pic_num = 3
"00101" + // ue(v) memory_management_control_operation = 4
"010" + // ue(v) max_long_term_frame_idx_plus1 = 1
"1", // ue(v) memory_management_control_operation = 0
idrPic: false,
want: DecRefPicMarking{
AdaptiveRefPicMarkingModeFlag: true,
elements: []drpmElement{
{
MemoryManagementControlOperation: 1,
DifferenceOfPicNumsMinus1: 2,
},
{
MemoryManagementControlOperation: 3,
DifferenceOfPicNumsMinus1: 0,
LongTermFrameIdx: 2,
},
{
MemoryManagementControlOperation: 2,
LongTermPicNum: 3,
},
{
MemoryManagementControlOperation: 4,
MaxLongTermFrameIdxPlus1: 1,
},
{
MemoryManagementControlOperation: 0,
},
},
},
},
}
for i, test := range tests {
inBytes, err := binToSlice(test.in)
if err != nil {
t.Fatalf("unexpected error %v for binToSlice in test %d", err, i)
}
got, err := NewDecRefPicMarking(bits.NewBitReader(bytes.NewReader(inBytes)), test.idrPic)
if err != nil {
t.Fatalf("unexpected error %v for NewPredWeightTable in test %d", err, i)
}
if !reflect.DeepEqual(*got, test.want) {
t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}

71
codec/h264/parse.go Normal file
View File

@ -0,0 +1,71 @@
/*
DESCRIPTION
parse.go provides H.264 NAL unit parsing utilities for the extraction of
syntax elements.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
Dan Kortschak <dan@ausocean.org>
LICENSE
Copyright (C) 2017-2018 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 h264
import "errors"
var errNotEnoughBytes = errors.New("not enough bytes to read")
// NALType returns the NAL type of the given NAL unit bytes. The given NAL unit
// may be in byte stream or packet format.
func NALType(n []byte) (int, error) {
sc := frameScanner{buf: n}
b, ok := sc.readByte()
if !ok {
return 0, errNotEnoughBytes
}
for i := 1; b == 0x00 && i != 4; i++ {
b, ok = sc.readByte()
if !ok {
return 0, errNotEnoughBytes
}
if b != 0x01 || (i != 2 && i != 3) {
continue
}
b, ok = sc.readByte()
if !ok {
return 0, errNotEnoughBytes
}
return int(b & 0x1f), nil
}
return int(b & 0x1f), nil
}
type frameScanner struct {
off int
buf []byte
}
func (s *frameScanner) readByte() (b byte, ok bool) {
if s.off >= len(s.buf) {
return 0, false
}
b = s.buf[s.off]
s.off++
return b, true
}

View File

@ -26,14 +26,53 @@ LICENSE
package mts package mts
import ( import (
"fmt"
"io" "io"
"strconv"
"time" "time"
"bitbucket.org/ausocean/av/codec/h264"
"bitbucket.org/ausocean/av/codec/h264/h264dec"
"bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/pes" "bitbucket.org/ausocean/av/container/mts/pes"
"bitbucket.org/ausocean/av/container/mts/psi" "bitbucket.org/ausocean/av/container/mts/psi"
"bitbucket.org/ausocean/utils/realtime"
) )
// Media type values.
// TODO: reference relevant specifications.
const (
H264ID = 27
H265ID = 36
audioStreamID = 0xc0 // First audio stream ID.
)
// Constants used to communicate which media codec will be packetized.
const (
EncodeH264 = iota
EncodeH265
EncodeAudio
)
// Time-related constants.
const (
// ptsOffset is the offset added to the clock to determine
// the current presentation timestamp.
ptsOffset = 700 * time.Millisecond
// PCRFrequency is the base Program Clock Reference frequency in Hz.
PCRFrequency = 90000
// PTSFrequency is the presentation timestamp frequency in Hz.
PTSFrequency = 90000
// MaxPTS is the largest PTS value (i.e., for a 33-bit unsigned integer).
MaxPTS = (1 << 33) - 1
)
// If we are not using NAL based PSI intervals then we will send PSI every 7 packets.
const psiSendCount = 7
// Some common manifestations of PSI. // Some common manifestations of PSI.
var ( var (
// StandardPAT is a minimal PAT. // StandardPAT is a minimal PAT.
@ -72,51 +111,20 @@ var (
} }
) )
const (
psiInterval = 1 * time.Second
psiSendCount = 7
)
// Meta allows addition of metadata to encoded mts from outside of this pkg. // Meta allows addition of metadata to encoded mts from outside of this pkg.
// See meta pkg for usage. // See meta pkg for usage.
// //
// TODO: make this not global. // TODO: make this not global.
var Meta *meta.Data var Meta *meta.Data
// This will help us obtain a realtime for timestamp meta encoding.
var RealTime = realtime.NewRealTime()
var ( var (
patTable = StandardPAT.Bytes() patTable = StandardPAT.Bytes()
pmtTable []byte pmtTable []byte
) )
const (
H264ID = 27
H265ID = 36
audioStreamID = 0xc0 // First audio stream ID.
)
// Constants used to communicate which media codec will be packetized.
const (
EncodeH264 = iota
EncodeH265
EncodeAudio
)
// Time-related constants.
const (
// ptsOffset is the offset added to the clock to determine
// the current presentation timestamp.
ptsOffset = 700 * time.Millisecond
// PCRFrequency is the base Program Clock Reference frequency in Hz.
PCRFrequency = 90000
// PTSFrequency is the presentation timestamp frequency in Hz.
PTSFrequency = 90000
// MaxPTS is the largest PTS value (i.e., for a 33-bit unsigned integer).
MaxPTS = (1 << 33) - 1
)
// Encoder encapsulates properties of an MPEG-TS generator. // Encoder encapsulates properties of an MPEG-TS generator.
type Encoder struct { type Encoder struct {
dst io.WriteCloser dst io.WriteCloser
@ -130,13 +138,11 @@ type Encoder struct {
continuity map[int]byte continuity map[int]byte
timeBasedPsi bool nalBasedPSI bool
pktCount int pktCount int
psiSendCount int psiSendCount int
mediaPid int mediaPid int
streamID byte streamID byte
psiLastTime time.Time
} }
// NewEncoder returns an Encoder with the specified media type and rate eg. if a video stream // NewEncoder returns an Encoder with the specified media type and rate eg. if a video stream
@ -174,7 +180,7 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder {
writePeriod: time.Duration(float64(time.Second) / rate), writePeriod: time.Duration(float64(time.Second) / rate),
ptsOffset: ptsOffset, ptsOffset: ptsOffset,
timeBasedPsi: true, nalBasedPSI: true,
pktCount: 8, pktCount: 8,
@ -199,12 +205,8 @@ const (
hasPTS = 0x2 hasPTS = 0x2
) )
// TimeBasedPsi allows for the setting of the PSI writing method, therefore, if func (e *Encoder) NALBasedPSI(b bool, sendCount int) {
// PSI is written based on some time duration, or based on a packet count. e.nalBasedPSI = b
// If b is true, then time based PSI is used, otherwise the PSI is written
// every sendCount.
func (e *Encoder) TimeBasedPsi(b bool, sendCount int) {
e.timeBasedPsi = b
e.psiSendCount = sendCount e.psiSendCount = sendCount
e.pktCount = e.psiSendCount e.pktCount = e.psiSendCount
} }
@ -212,14 +214,24 @@ func (e *Encoder) TimeBasedPsi(b bool, sendCount int) {
// Write implements io.Writer. Write takes raw video or audio data and encodes into MPEG-TS, // Write implements io.Writer. Write takes raw video or audio data and encodes into MPEG-TS,
// then sending it to the encoder's io.Writer destination. // then sending it to the encoder's io.Writer destination.
func (e *Encoder) Write(data []byte) (int, error) { func (e *Encoder) Write(data []byte) (int, error) {
now := time.Now() if e.nalBasedPSI {
if (e.timeBasedPsi && (now.Sub(e.psiLastTime) > psiInterval)) || (!e.timeBasedPsi && (e.pktCount >= e.psiSendCount)) { nalType, err := h264.NALType(data)
if err != nil {
return 0, fmt.Errorf("could not get type from NAL unit, failed with error: %v", err)
}
if nalType == h264dec.NALTypeSPS {
err := e.writePSI()
if err != nil {
return 0, err
}
}
} else if e.pktCount >= e.psiSendCount {
e.pktCount = 0 e.pktCount = 0
err := e.writePSI() err := e.writePSI()
if err != nil { if err != nil {
return 0, err return 0, err
} }
e.psiLastTime = now
} }
// Prepare PES data. // Prepare PES data.
@ -328,6 +340,9 @@ func (e *Encoder) ccFor(pid int) byte {
// contained in the global Meta struct. // contained in the global Meta struct.
func updateMeta(b []byte) ([]byte, error) { func updateMeta(b []byte) ([]byte, error) {
p := psi.PSIBytes(b) p := psi.PSIBytes(b)
if RealTime.IsSet() {
Meta.Add("ts", strconv.Itoa(int(RealTime.Get().Unix())))
}
err := p.AddDescriptor(psi.MetadataTag, Meta.Encode()) err := p.AddDescriptor(psi.MetadataTag, Meta.Encode())
return []byte(p), err return []byte(p), err
} }

View File

@ -650,8 +650,11 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) {
} }
// pid returns the packet identifier for the given packet. // pid returns the packet identifier for the given packet.
func pid(p []byte) uint16 { func PID(p []byte) (uint16, error) {
return uint16(p[1]&0x1f)<<8 | uint16(p[2]) if len(p) < PacketSize {
return 0, errors.New("packet length less than 188")
}
return uint16(p[1]&0x1f)<<8 | uint16(p[2]), nil
} }
// Programs returns a map of program numbers and corresponding PMT PIDs for a // Programs returns a map of program numbers and corresponding PMT PIDs for a
@ -683,10 +686,14 @@ func Streams(p []byte) ([]gotspsi.PmtElementaryStream, error) {
// but this program may contain different streams, i.e. a video stream + audio // but this program may contain different streams, i.e. a video stream + audio
// stream. // stream.
func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) { func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) {
if len(p) < 2*PacketSize {
return nil, errors.New("PSI is not two packets or more long")
}
pat := p[:PacketSize] pat := p[:PacketSize]
pmt := p[PacketSize : 2*PacketSize] pmt := p[PacketSize : 2*PacketSize]
if pid(pat) != PatPid { pid, _ := PID(pat)
if pid != PatPid {
return nil, errors.New("first packet is not a PAT") return nil, errors.New("first packet is not a PAT")
} }
@ -703,7 +710,8 @@ func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) {
return nil, ErrMultiplePrograms return nil, ErrMultiplePrograms
} }
if pid(pmt) != pmtPIDs(m)[0] { pid, _ = PID(pmt)
if pid != pmtPIDs(m)[0] {
return nil, errors.New("second packet is not desired PMT") return nil, errors.New("second packet is not desired PMT")
} }

4
go.mod
View File

@ -3,8 +3,8 @@ module bitbucket.org/ausocean/av
go 1.12 go 1.12
require ( require (
bitbucket.org/ausocean/iot v1.2.5 bitbucket.org/ausocean/iot v1.2.6
bitbucket.org/ausocean/utils v1.2.6 bitbucket.org/ausocean/utils v1.2.8
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884

8
go.sum
View File

@ -1,10 +1,16 @@
bitbucket.org/ausocean/av v0.0.0-20190416003121-6ee286e98874/go.mod h1:DxZEprrNNQ2slHKAQVUHryDaWc5CbjxyHAvomhzg+AE=
bitbucket.org/ausocean/iot v1.2.4 h1:M/473iQ0d4q+76heerjAQuqXzQyc5dZ3F7Bfuq6X7q4= bitbucket.org/ausocean/iot v1.2.4 h1:M/473iQ0d4q+76heerjAQuqXzQyc5dZ3F7Bfuq6X7q4=
bitbucket.org/ausocean/iot v1.2.4/go.mod h1:5HVLgPHccW2PxS7WDUQO6sKWMgk3Vfze/7d5bHs8EWU= bitbucket.org/ausocean/iot v1.2.4/go.mod h1:5HVLgPHccW2PxS7WDUQO6sKWMgk3Vfze/7d5bHs8EWU=
bitbucket.org/ausocean/iot v1.2.5 h1:udD5X4oXUuKwdjO7bcq4StcDdjP8fJa2L0FnJJwF+6Q= bitbucket.org/ausocean/iot v1.2.5 h1:udD5X4oXUuKwdjO7bcq4StcDdjP8fJa2L0FnJJwF+6Q=
bitbucket.org/ausocean/iot v1.2.5/go.mod h1:dOclxXkdxAQGWO7Y5KcP1wpNfxg9oKUA2VqjJ3Le4RA= bitbucket.org/ausocean/iot v1.2.5/go.mod h1:dOclxXkdxAQGWO7Y5KcP1wpNfxg9oKUA2VqjJ3Le4RA=
bitbucket.org/ausocean/iot v1.2.6 h1:KAAY1KZDbyOpoKajT1dM8BawupHiW9hUOelseSV1Ptc=
bitbucket.org/ausocean/iot v1.2.6/go.mod h1:71AYHh8yGZ8XyzDBskwIWMF+8E8ORagXpXE24wlhoE0=
bitbucket.org/ausocean/utils v0.0.0-20190408050157-66d3b4d4041e/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.4/go.mod h1:5JIXFTAMMNl5Ob79tpZfDCJ+gOO8rj7v4ORj56tHZpw= bitbucket.org/ausocean/utils v1.2.4/go.mod h1:5JIXFTAMMNl5Ob79tpZfDCJ+gOO8rj7v4ORj56tHZpw=
bitbucket.org/ausocean/utils v1.2.6 h1:JN66APCV+hu6GebIHSu2KSywhLym4vigjSz5+fB0zXc= bitbucket.org/ausocean/utils v1.2.6 h1:JN66APCV+hu6GebIHSu2KSywhLym4vigjSz5+fB0zXc=
bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.8 h1:wRlajOtaMz/loUrGmFf4SUcTnZALtTqgPmk49iHMWxs=
bitbucket.org/ausocean/utils v1.2.8/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXShkLA3/kpxXb6LJpH+ekU2krbzw= github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXShkLA3/kpxXb6LJpH+ekU2krbzw=
@ -49,6 +55,8 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

75
revid/audio_linux.go Normal file
View File

@ -0,0 +1,75 @@
/*
LICENSE
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
This 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 revid
import (
"fmt"
"strconv"
"time"
"bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/input/audio"
"bitbucket.org/ausocean/utils/logger"
)
// startAudioDevice is used to start capturing audio from an audio device and processing it.
// It returns a function that can be used to stop the device and any errors that occur.
func (r *Revid) startAudioDevice() (func() error, error) {
// Create audio device.
ac := &audio.Config{
SampleRate: r.config.SampleRate,
Channels: r.config.Channels,
RecPeriod: r.config.RecPeriod,
BitDepth: r.config.BitDepth,
Codec: r.config.InputCodec,
}
mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate))
mts.Meta.Add("channels", strconv.Itoa(r.config.Channels))
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod))
mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth))
switch r.config.InputCodec {
case codecutil.PCM:
mts.Meta.Add("codec", "pcm")
case codecutil.ADPCM:
mts.Meta.Add("codec", "adpcm")
default:
r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
}
ai, err := audio.NewDevice(ac, r.config.Logger)
if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error())
}
// Start audio device
err = ai.Start()
if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error())
}
// Process output from audio device.
r.config.ChunkSize = ai.ChunkSize()
r.wg.Add(1)
go r.processFrom(ai, time.Duration(float64(time.Second)/r.config.WriteRate))
return func() error {
ai.Stop()
return nil
}, nil
}

25
revid/audio_windows.go Normal file
View File

@ -0,0 +1,25 @@
/*
LICENSE
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
This 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.
*/
// startAudioDevice is used to start capturing audio from an audio device
// TODO: Implement on Windows.
package revid
func (r *Revid) startAudioDevice() (func() error, error) {
panic("Audio not implemented on Windows")
}

View File

@ -27,6 +27,7 @@ package revid
import ( import (
"errors" "errors"
"time"
"bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
@ -90,32 +91,41 @@ const (
// Default config settings // Default config settings
const ( const (
defaultInput = Raspivid // General revid defaults.
defaultOutput = HTTP defaultInput = Raspivid
defaultFrameRate = 25 defaultOutput = HTTP
defaultWriteRate = 25 defaultFrameRate = 25
defaultWidth = 1280 defaultWriteRate = 25
defaultHeight = 720 defaultTimeout = 0
defaultIntraRefreshPeriod = 100 defaultInputCodec = codecutil.H264
defaultTimeout = 0 defaultVerbosity = logger.Error
defaultQuantization = 40 defaultRtpAddr = "localhost:6970"
defaultBitrate = 400000 defaultBurstPeriod = 10 // Seconds
defaultFramesPerClip = 1
httpFramesPerClip = 560
defaultInputCodec = codecutil.H264
defaultVerbosity = logger.Error
defaultRtpAddr = "localhost:6970"
defaultBurstPeriod = 10 // Seconds
defaultRotation = 0 // Degrees
defaultBrightness = 50
defaultExposure = "auto"
defaultAutoWhiteBalance = "auto"
// Raspivid video defaults.
defaultBrightness = 50
defaultExposure = "auto"
defaultAutoWhiteBalance = "auto"
defaultRotation = 0 // Degrees
defaultWidth = 1280
defaultHeight = 720
defaultMinFrames = 100
defaultClipDuration = 0
defaultQuantization = 30
defaultBitrate = 400000
// Audio defaults.
defaultAudioInputCodec = codecutil.ADPCM defaultAudioInputCodec = codecutil.ADPCM
defaultSampleRate = 48000 defaultSampleRate = 48000
defaultBitDepth = 16 defaultBitDepth = 16
defaultChannels = 1 defaultChannels = 1
defaultRecPeriod = 1.0 defaultRecPeriod = 1.0
// Ringbuffer defaults.
defaultMTSRBSize = 1000
defaultMTSRBElementSize = 100000
defaultRTMPRBSize = 500
defaultRTMPRBElementSize = 200000
) )
// Config provides parameters relevant to a revid instance. A new config must // Config provides parameters relevant to a revid instance. A new config must
@ -162,14 +172,6 @@ type Config struct {
// localhost:6970. MPEGT-TS packetization is used. // localhost:6970. MPEGT-TS packetization is used.
Outputs []uint8 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 // RTMPURL specifies the Rtmp output destination URL. This must be defined if
// RTMP is to be used as an output. // RTMP is to be used as an output.
RTMPURL string RTMPURL string
@ -202,9 +204,16 @@ type Config struct {
// are using Raspivid input. // are using Raspivid input.
Quantization uint Quantization uint
// IntraRefreshPeriod defines the frequency of video parameter NAL units for // MinFrames defines the frequency of key NAL units SPS, PPS and IDR in
// Raspivid input. // number of NAL units. This will also determine the frequency of PSI if the
IntraRefreshPeriod uint // output container is MPEG-TS. If ClipDuration is less than MinFrames,
// ClipDuration will default to MinFrames.
MinFrames uint
// ClipDuration is the duration of MTS data that is sent using HTTP or RTP
// output. This defaults to 0, therefore MinFrames will determine the length of
// clips by default.
ClipDuration time.Duration
// Logger holds an implementation of the Logger interface as defined in revid.go. // Logger holds an implementation of the Logger interface as defined in revid.go.
// This must be set for revid to work correctly. // This must be set for revid to work correctly.
@ -236,15 +245,26 @@ type Config struct {
BurstPeriod uint // BurstPeriod defines the revid burst period in seconds. BurstPeriod uint // BurstPeriod defines the revid burst period in seconds.
Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input. Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input.
Height uint // Height defines the input video height Raspivid input. Height uint // Height defines the input video height Raspivid input.
Width uint // Width defines the input video width Raspivid input. Width uint // Width defines the input video width Raspivid input.
Bitrate uint // Bitrate specifies the input bitrate for Raspivid input. Bitrate uint // Bitrate specifies the input bitrate for Raspivid input.
FlipHorizontal bool // FlipHorizontal flips video horizontally for Raspivid input. FlipHorizontal bool // FlipHorizontal flips video horizontally for Raspivid input.
FlipVertical bool // FlipVertial flips video vertically for Raspivid input. FlipVertical bool // FlipVertial flips video vertically for Raspivid input.
// Ring buffer sizes.
RTMPRBSize int // The number of elements in the RTMP sender ringbuffer.
RTMPRBElementSize int // The element size in bytes of the RTMP sender RingBuffer.
MTSRBSize int // The number of elements in the MTS sender ringbuffer.
MTSRBElementSize int // The element size in bytes of the MTS sender RingBuffer.
} }
// Validation errors.
var (
errInvalidQuantization = errors.New("invalid quantization")
)
// Validate checks for any errors in the config fields and defaults settings // Validate checks for any errors in the config fields and defaults settings
// if particular parameters have not been defined. // if particular parameters have not been defined.
func (c *Config) Validate(r *Revid) error { func (c *Config) Validate() error {
switch c.LogLevel { switch c.LogLevel {
case logger.Debug: case logger.Debug:
case logger.Info: case logger.Info:
@ -267,16 +287,6 @@ func (c *Config) Validate(r *Revid) error {
switch c.InputCodec { switch c.InputCodec {
case codecutil.H264: case codecutil.H264:
// FIXME(kortschak): This is not really what we want.
// Configuration really needs to be rethought here.
if c.Quantize && c.Quantization == 0 {
c.Quantization = defaultQuantization
}
if (c.Bitrate > 0 && c.Quantize) || (c.Bitrate == 0 && !c.Quantize) {
return errors.New("bad bitrate and quantization combination for H264 input")
}
case codecutil.MJPEG: case codecutil.MJPEG:
if c.Quantization > 0 || c.Bitrate == 0 { if c.Quantization > 0 || c.Bitrate == 0 {
return errors.New("bad bitrate or quantization for mjpeg input") return errors.New("bad bitrate or quantization for mjpeg input")
@ -298,26 +308,33 @@ func (c *Config) Validate(r *Revid) error {
if c.Outputs == nil { if c.Outputs == nil {
c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput) c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput)
c.Outputs = append(c.Outputs, defaultOutput) c.Outputs = append(c.Outputs, defaultOutput)
} else { }
for i, o := range c.Outputs {
switch o { var haveRTMPOut bool
case File: for i, o := range c.Outputs {
case RTMP: switch o {
if c.RTMPURL == "" { case File:
c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") case RTMP:
c.Outputs[i] = HTTP haveRTMPOut = true
// FIXME(kortschak): Does this want the same line as below? if c.Bitrate == 0 {
// c.FramesPerClip = httpFramesPerClip c.Bitrate = defaultBitrate
break
}
c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for rtmp out", "framesPerClip", defaultFramesPerClip)
c.FramesPerClip = defaultFramesPerClip
case HTTP, RTP:
c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out", "framesPerClip", httpFramesPerClip)
c.FramesPerClip = httpFramesPerClip
default:
return errors.New("bad output type defined in config")
} }
c.Quantization = 0
if c.RTMPURL == "" {
c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP")
c.Outputs[i] = HTTP
haveRTMPOut = false
}
fallthrough
case HTTP, RTP:
if !haveRTMPOut {
c.Bitrate = 0
if c.Quantization == 0 {
c.Quantization = defaultQuantization
}
}
default:
return errors.New("bad output type defined in config")
} }
} }
@ -326,11 +343,6 @@ func (c *Config) Validate(r *Revid) error {
c.BurstPeriod = defaultBurstPeriod c.BurstPeriod = defaultBurstPeriod
} }
if c.FramesPerClip < 1 {
c.Logger.Log(logger.Info, pkg+"no FramesPerClip defined, defaulting", "framesPerClip", defaultFramesPerClip)
c.FramesPerClip = defaultFramesPerClip
}
if c.Rotation > 359 { if c.Rotation > 359 {
c.Logger.Log(logger.Warning, pkg+"bad rotate angle, defaulting", "angle", defaultRotation) c.Logger.Log(logger.Warning, pkg+"bad rotate angle, defaulting", "angle", defaultRotation)
c.Rotation = defaultRotation c.Rotation = defaultRotation
@ -376,21 +388,26 @@ func (c *Config) Validate(r *Revid) error {
c.WriteRate = defaultWriteRate c.WriteRate = defaultWriteRate
} }
if c.Bitrate == 0 { if c.Bitrate < 0 {
c.Logger.Log(logger.Info, pkg+"no bitrate defined, defaulting", "bitrate", defaultBitrate) return errors.New("invalid bitrate")
c.Bitrate = defaultBitrate
} }
if c.IntraRefreshPeriod == 0 { if c.MinFrames == 0 {
c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultIntraRefreshPeriod) c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinFrames", defaultMinFrames)
c.IntraRefreshPeriod = defaultIntraRefreshPeriod c.MinFrames = defaultMinFrames
} else if c.MinFrames < 0 {
return errors.New("refresh period is less than 0")
} }
if c.Quantization == 0 { if c.ClipDuration == 0 {
c.Logger.Log(logger.Info, pkg+"no quantization defined, defaulting", "quantization", defaultQuantization) c.Logger.Log(logger.Info, pkg+"no clip duration defined, defaulting", "ClipDuration", defaultClipDuration)
c.Quantization = defaultQuantization c.ClipDuration = defaultClipDuration
} else if c.Quantization > 51 { } else if c.ClipDuration < 0 {
return errors.New("quantisation is over threshold") return errors.New("clip duration is less than 0")
}
if c.Quantization != 0 && (c.Quantization < 10 || c.Quantization > 40) {
return errInvalidQuantization
} }
if c.RTPAddress == "" { if c.RTPAddress == "" {
@ -425,6 +442,26 @@ func (c *Config) Validate(r *Revid) error {
return errors.New(pkg + "bad auto white balance setting in config") return errors.New(pkg + "bad auto white balance setting in config")
} }
if c.RTMPRBSize <= 0 {
c.Logger.Log(logger.Info, pkg+"RTMPRBSize bad or unset, defaulting", "RTMPRBSize", defaultRTMPRBSize)
c.RTMPRBSize = defaultRTMPRBSize
}
if c.RTMPRBElementSize <= 0 {
c.Logger.Log(logger.Info, pkg+"RTMPRBElementSize bad or unset, defaulting", "RTMPRBElementSize", defaultRTMPRBElementSize)
c.RTMPRBElementSize = defaultRTMPRBElementSize
}
if c.MTSRBSize <= 0 {
c.Logger.Log(logger.Info, pkg+"MTSRBSize bad or unset, defaulting", "MTSRBSize", defaultMTSRBSize)
c.MTSRBSize = defaultMTSRBSize
}
if c.MTSRBElementSize <= 0 {
c.Logger.Log(logger.Info, pkg+"MTSRBElementSize bad or unset, defaulting", "MTSRBElementSize", defaultMTSRBElementSize)
c.MTSRBElementSize = defaultMTSRBElementSize
}
return nil return nil
} }

97
revid/config_test.go Normal file
View File

@ -0,0 +1,97 @@
/*
DESCRIPTION
config_test.go provides testing for configuration functionality found in config.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 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 revid
import (
"fmt"
"testing"
)
type dumbLogger struct{}
func (dl *dumbLogger) SetLevel(l int8) {}
func (dl *dumbLogger) Log(l int8, m string, p ...interface{}) {}
func TestValidate(t *testing.T) {
tests := []struct {
in Config
check func(c Config) error
err error
}{
{
in: Config{Outputs: []uint8{HTTP}, Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)
}
if c.Quantization != defaultQuantization {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, defaultQuantization)
}
return nil
},
},
{
in: Config{Outputs: []uint8{RTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != defaultBitrate {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate)
}
if c.Quantization != 0 {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 0)
}
return nil
},
},
{
in: Config{Outputs: []uint8{HTTP}, Quantization: 50, Logger: &dumbLogger{}},
check: func(c Config) error { return nil },
err: errInvalidQuantization,
},
{
in: Config{Outputs: []uint8{HTTP}, Quantization: 20, Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)
}
if c.Quantization != 20 {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 20)
}
return nil
},
},
}
for i, test := range tests {
err := (&test.in).Validate()
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
err = test.check(test.in)
if err != nil {
t.Errorf("test %d failed with err: %v", i, err)
}
}
}

View File

@ -45,25 +45,13 @@ import (
"bitbucket.org/ausocean/av/codec/h265" "bitbucket.org/ausocean/av/codec/h265"
"bitbucket.org/ausocean/av/container/flv" "bitbucket.org/ausocean/av/container/flv"
"bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/input/audio"
"bitbucket.org/ausocean/av/protocol/rtcp" "bitbucket.org/ausocean/av/protocol/rtcp"
"bitbucket.org/ausocean/av/protocol/rtp" "bitbucket.org/ausocean/av/protocol/rtp"
"bitbucket.org/ausocean/av/protocol/rtsp" "bitbucket.org/ausocean/av/protocol/rtsp"
"bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/iot/pi/netsender"
"bitbucket.org/ausocean/utils/ioext" "bitbucket.org/ausocean/utils/ioext"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) "bitbucket.org/ausocean/utils/ring"
// mtsSender ringBuffer sizes.
const (
mtsRBSize = 1000
mtsRBElementSize = 100000
)
// rtmpSender ringBuffer sizes.
const (
rtmpRBSize = 500
rtmpRBElementSize = 200000
) )
// RTMP connection properties. // RTMP connection properties.
@ -210,7 +198,7 @@ func (r *Revid) reset(config Config) error {
// revid config. // revid config.
func (r *Revid) setConfig(config Config) error { func (r *Revid) setConfig(config Config) error {
r.config.Logger = config.Logger r.config.Logger = config.Logger
err := config.Validate(r) err := config.Validate()
if err != nil { if err != nil {
return errors.New("Config struct is bad: " + err.Error()) return errors.New("Config struct is bad: " + err.Error())
} }
@ -240,7 +228,12 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
for _, out := range r.config.Outputs { for _, out := range r.config.Outputs {
switch out { switch out {
case HTTP: case HTTP:
w = newMtsSender(newHttpSender(r.ns, r.config.Logger.Log), r.config.Logger.Log, mtsRBSize, mtsRBElementSize, 0) w = newMtsSender(
newHttpSender(r.ns, r.config.Logger.Log),
r.config.Logger.Log,
ring.NewBuffer(r.config.MTSRBSize, r.config.MTSRBElementSize, 0),
r.config.ClipDuration,
)
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case RTP: case RTP:
w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate)
@ -255,7 +248,14 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
} }
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case RTMP: case RTMP:
w, err := newRtmpSender(r.config.RTMPURL, rtmpConnectionTimeout, rtmpConnectionMaxTries, r.config.Logger.Log) w, err := newRtmpSender(
r.config.RTMPURL,
rtmpConnectionTimeout,
rtmpConnectionMaxTries,
r.config.RTMPRBSize,
r.config.RTMPRBElementSize,
r.config.Logger.Log,
)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error()) r.config.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error())
} }
@ -450,19 +450,27 @@ func (r *Revid) Update(vars map[string]string) error {
case "HttpAddress": case "HttpAddress":
r.config.HTTPAddress = value r.config.HTTPAddress = value
case "Quantization": case "Quantization":
q, err := strconv.ParseUint(value, 10, 0) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", v)
break break
} }
r.config.Quantization = uint(q) r.config.Quantization = uint(v)
case "IntraRefreshPeriod": case "MinFrames":
p, err := strconv.ParseUint(value, 10, 0) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid intrarefreshperiod param", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid MinFrames param", "value", value)
break break
} }
r.config.IntraRefreshPeriod = uint(p) r.config.MinFrames = uint(v)
case "ClipDuration":
v, err := strconv.Atoi(value)
if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid ClipDuration param", "value", value)
break
}
r.config.ClipDuration = time.Duration(v) * time.Second
case "HorizontalFlip": case "HorizontalFlip":
switch strings.ToLower(value) { switch strings.ToLower(value) {
@ -504,6 +512,34 @@ func (r *Revid) Update(vars map[string]string) error {
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid Logging param", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid Logging param", "value", value)
} }
case "RTMPRBSize":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBSize var", "value", value)
break
}
r.config.RTMPRBSize = v
case "RTMPRBElementSize":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBElementSize var", "value", value)
break
}
r.config.RTMPRBElementSize = v
case "MTSRBSize":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBSize var", "value", value)
break
}
r.config.MTSRBSize = v
case "MTSRBElementSize":
v, err := strconv.Atoi(value)
if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBElementSize var", "value", value)
break
}
r.config.MTSRBElementSize = v
} }
} }
r.config.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.config)) r.config.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.config))
@ -549,9 +585,9 @@ func (r *Revid) startRaspivid() (func() error, error) {
args = append(args, args = append(args,
"--codec", "H264", "--codec", "H264",
"--inline", "--inline",
"--intra", fmt.Sprint(r.config.IntraRefreshPeriod), "--intra", fmt.Sprint(r.config.MinFrames),
) )
if r.config.Quantize { if r.config.Quantization != 0 {
args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) args = append(args, "-qp", fmt.Sprint(r.config.Quantization))
} }
case codecutil.MJPEG: case codecutil.MJPEG:
@ -631,51 +667,6 @@ func (r *Revid) setupInputForFile() (func() error, error) {
return func() error { return f.Close() }, nil return func() error { return f.Close() }, nil
} }
// startAudioDevice is used to start capturing audio from an audio device and processing it.
// It returns a function that can be used to stop the device and any errors that occur.
func (r *Revid) startAudioDevice() (func() error, error) {
// Create audio device.
ac := &audio.Config{
SampleRate: r.config.SampleRate,
Channels: r.config.Channels,
RecPeriod: r.config.RecPeriod,
BitDepth: r.config.BitDepth,
Codec: r.config.InputCodec,
}
mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate))
mts.Meta.Add("channels", strconv.Itoa(r.config.Channels))
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod))
mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth))
switch r.config.InputCodec {
case codecutil.PCM:
mts.Meta.Add("codec", "pcm")
case codecutil.ADPCM:
mts.Meta.Add("codec", "adpcm")
default:
r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
}
ai, err := audio.NewDevice(ac, r.config.Logger)
if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error())
}
// Start audio device
err = ai.Start()
if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error())
}
// Process output from audio device.
r.config.ChunkSize = ai.ChunkSize()
r.wg.Add(1)
go r.processFrom(ai, time.Duration(float64(time.Second)/r.config.WriteRate))
return func() error {
ai.Stop()
return nil
}, nil
}
// startRTSPCamera uses RTSP to request an RTP stream from an IP camera. An RTP // startRTSPCamera uses RTSP to request an RTP stream from an IP camera. An RTP
// client is created from which RTP packets containing either h264/h265 can read // client is created from which RTP packets containing either h264/h265 can read
// by the selected lexer. // by the selected lexer.

View File

@ -29,11 +29,11 @@ LICENSE
package revid package revid
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"os" "os"
"strconv"
"sync" "sync"
"time" "time"
@ -50,6 +50,12 @@ import (
// Log is used by the multiSender. // Log is used by the multiSender.
type Log func(level int8, message string, params ...interface{}) type Log func(level int8, message string, params ...interface{})
// Sender ring buffer read timeouts.
const (
rtmpRBReadTimeout = 1 * time.Second
mtsRBReadTimeout = 1 * time.Second
)
// httpSender provides an implemntation of io.Writer to perform sends to a http // httpSender provides an implemntation of io.Writer to perform sends to a http
// destination. // destination.
type httpSender struct { type httpSender struct {
@ -112,7 +118,7 @@ func extractMeta(r string, log func(lvl int8, msg string, args ...interface{}))
log(logger.Warning, pkg+"No timestamp in reply") log(logger.Warning, pkg+"No timestamp in reply")
} else { } else {
log(logger.Debug, fmt.Sprintf("%v got timestamp: %v", pkg, t)) log(logger.Debug, fmt.Sprintf("%v got timestamp: %v", pkg, t))
mts.Meta.Add("ts", strconv.Itoa(t)) mts.RealTime.Set(time.Unix(int64(t), 0))
} }
// Extract location from reply // Extract location from reply
@ -150,8 +156,8 @@ func (s *fileSender) Close() error { return s.file.Close() }
// mtsSender implements io.WriteCloser and provides sending capability specifically // mtsSender implements io.WriteCloser and provides sending capability specifically
// for use with MPEGTS packetization. It handles the construction of appropriately // for use with MPEGTS packetization. It handles the construction of appropriately
// lengthed clips based on PSI. It also accounts for discontinuities by // lengthed clips based on clip duration and PSI. It also accounts for
// setting the discontinuity indicator for the first packet of a clip. // discontinuities by setting the discontinuity indicator for the first packet of a clip.
type mtsSender struct { type mtsSender struct {
dst io.WriteCloser dst io.WriteCloser
buf []byte buf []byte
@ -160,19 +166,22 @@ type mtsSender struct {
pkt packet.Packet pkt packet.Packet
repairer *mts.DiscontinuityRepairer repairer *mts.DiscontinuityRepairer
curPid int curPid int
clipDur time.Duration
prev time.Time
done chan struct{} done chan struct{}
log func(lvl int8, msg string, args ...interface{}) log func(lvl int8, msg string, args ...interface{})
wg sync.WaitGroup wg sync.WaitGroup
} }
// newMtsSender returns a new mtsSender. // newMtsSender returns a new mtsSender.
func newMtsSender(dst io.WriteCloser, log func(lvl int8, msg string, args ...interface{}), ringSize int, ringElementSize int, wTimeout time.Duration) *mtsSender { func newMtsSender(dst io.WriteCloser, log func(lvl int8, msg string, args ...interface{}), rb *ring.Buffer, clipDur time.Duration) *mtsSender {
s := &mtsSender{ s := &mtsSender{
dst: dst, dst: dst,
repairer: mts.NewDiscontinuityRepairer(), repairer: mts.NewDiscontinuityRepairer(),
log: log, log: log,
ring: ring.NewBuffer(ringSize, ringElementSize, wTimeout), ring: rb,
done: make(chan struct{}), done: make(chan struct{}),
clipDur: clipDur,
} }
s.wg.Add(1) s.wg.Add(1)
go s.output() go s.output()
@ -192,7 +201,7 @@ func (s *mtsSender) output() {
// If chunk is nil then we're ready to get another from the ringBuffer. // If chunk is nil then we're ready to get another from the ringBuffer.
if chunk == nil { if chunk == nil {
var err error var err error
chunk, err = s.ring.Next(0) chunk, err = s.ring.Next(mtsRBReadTimeout)
switch err { switch err {
case nil, io.EOF: case nil, io.EOF:
continue continue
@ -223,15 +232,20 @@ func (s *mtsSender) output() {
// Write implements io.Writer. // Write implements io.Writer.
func (s *mtsSender) Write(d []byte) (int, error) { func (s *mtsSender) Write(d []byte) (int, error) {
if len(d) < mts.PacketSize {
return 0, errors.New("do not have full MTS packet")
}
if s.next != nil { if s.next != nil {
s.buf = append(s.buf, s.next...) s.buf = append(s.buf, s.next...)
} }
bytes := make([]byte, len(d)) bytes := make([]byte, len(d))
copy(bytes, d) copy(bytes, d)
s.next = bytes s.next = bytes
copy(s.pkt[:], bytes) p, _ := mts.PID(bytes)
s.curPid = s.pkt.PID() s.curPid = int(p)
if s.curPid == mts.PatPid && len(s.buf) > 0 { if time.Now().Sub(s.prev) >= s.clipDur && s.curPid == mts.PatPid && len(s.buf) > 0 {
s.prev = time.Now()
_, err := s.ring.Write(s.buf) _, err := s.ring.Write(s.buf)
if err != nil { if err != nil {
s.log(logger.Warning, pkg+"mtsSender: ringBuffer write error", "error", err.Error()) s.log(logger.Warning, pkg+"mtsSender: ringBuffer write error", "error", err.Error())
@ -261,7 +275,7 @@ type rtmpSender struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg string, args ...interface{})) (*rtmpSender, error) { func newRtmpSender(url string, timeout uint, retries, rbSize, rbElementSize int, log func(lvl int8, msg string, args ...interface{})) (*rtmpSender, error) {
var conn *rtmp.Conn var conn *rtmp.Conn
var err error var err error
for n := 0; n < retries; n++ { for n := 0; n < retries; n++ {
@ -280,7 +294,7 @@ func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg
timeout: timeout, timeout: timeout,
retries: retries, retries: retries,
log: log, log: log,
ring: ring.NewBuffer(rtmpRBSize, rtmpRBElementSize, 0), ring: ring.NewBuffer(rbSize, rbElementSize, 0),
done: make(chan struct{}), done: make(chan struct{}),
} }
s.wg.Add(1) s.wg.Add(1)
@ -301,7 +315,7 @@ func (s *rtmpSender) output() {
// If chunk is nil then we're ready to get another from the ring buffer. // If chunk is nil then we're ready to get another from the ring buffer.
if chunk == nil { if chunk == nil {
var err error var err error
chunk, err = s.ring.Next(0) chunk, err = s.ring.Next(rtmpRBReadTimeout)
switch err { switch err {
case nil, io.EOF: case nil, io.EOF:
continue continue

View File

@ -39,6 +39,7 @@ import (
"bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
"bitbucket.org/ausocean/utils/ring"
) )
var ( var (
@ -133,12 +134,12 @@ func TestMtsSenderSegment(t *testing.T) {
// Create ringBuffer, sender, sender and the MPEGTS encoder. // Create ringBuffer, sender, sender and the MPEGTS encoder.
const numberOfClips = 11 const numberOfClips = 11
dst := &destination{t: t, done: make(chan struct{}), doneAt: numberOfClips} dst := &destination{t: t, done: make(chan struct{}), doneAt: numberOfClips}
sender := newMtsSender(dst, (*dummyLogger)(t).log, mtsRBSize, mtsRBElementSize, 0) sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(defaultMTSRBSize, defaultMTSRBElementSize, 0), 0)
encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264)
// Turn time based PSI writing off for encoder. // Turn time based PSI writing off for encoder.
const psiSendCount = 10 const psiSendCount = 10
encoder.TimeBasedPsi(false, psiSendCount) encoder.NALBasedPSI(false, psiSendCount)
// Write the packets to the encoder, which will in turn write to the mtsSender. // Write the packets to the encoder, which will in turn write to the mtsSender.
// Payload will just be packet number. // Payload will just be packet number.
@ -211,12 +212,12 @@ func TestMtsSenderFailedSend(t *testing.T) {
// Create destination, the mtsSender and the mtsEncoder // Create destination, the mtsSender and the mtsEncoder
const clipToFailAt = 3 const clipToFailAt = 3
dst := &destination{t: t, testFails: true, failAt: clipToFailAt, done: make(chan struct{})} dst := &destination{t: t, testFails: true, failAt: clipToFailAt, done: make(chan struct{})}
sender := newMtsSender(dst, (*dummyLogger)(t).log, mtsRBSize, mtsRBElementSize, 0) sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(defaultMTSRBSize, defaultMTSRBElementSize, 0), 0)
encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264)
// 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
encoder.TimeBasedPsi(false, psiSendCount) encoder.NALBasedPSI(false, psiSendCount)
// Write the packets to the encoder, which will in turn write to the mtsSender. // Write the packets to the encoder, which will in turn write to the mtsSender.
// Payload will just be packet number. // Payload will just be packet number.
@ -291,12 +292,12 @@ func TestMtsSenderDiscontinuity(t *testing.T) {
// Create destination, the mtsSender and the mtsEncoder. // Create destination, the mtsSender and the mtsEncoder.
const clipToDelay = 3 const clipToDelay = 3
dst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay, done: make(chan struct{})} dst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay, done: make(chan struct{})}
sender := newMtsSender(dst, (*dummyLogger)(t).log, 1, mtsRBElementSize, 0) sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(1, defaultMTSRBElementSize, 0), 0)
encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264)
// Turn time based PSI writing off for encoder. // Turn time based PSI writing off for encoder.
const psiSendCount = 10 const psiSendCount = 10
encoder.TimeBasedPsi(false, psiSendCount) encoder.NALBasedPSI(false, psiSendCount)
// Write the packets to the encoder, which will in turn write to the mtsSender. // Write the packets to the encoder, which will in turn write to the mtsSender.
// Payload will just be packet number. // Payload will just be packet number.