mirror of https://bitbucket.org/ausocean/av.git
device/raspivid & revid/config: exposed more raspivid parameters, namely contrast, sharpness, exposure value, ISO and AWBGains
This commit is contained in:
parent
d096434e1e
commit
8ad9ee5361
|
@ -32,6 +32,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
|
@ -58,6 +59,11 @@ const (
|
|||
defaultRaspividQuantization = 30
|
||||
defaultRaspividBitrate = 4800
|
||||
defaultRaspividFramerate = 25
|
||||
defaultRaspividSharpness = 0
|
||||
defaultRaspividContrast = 0
|
||||
defaultRaspividISO = 100
|
||||
defaultRaspividEV = 0
|
||||
defaultRaspividAWBGains = "1.0,1.0"
|
||||
)
|
||||
|
||||
// Configuration errors.
|
||||
|
@ -74,6 +80,11 @@ var (
|
|||
errBadExposure = errors.New("exposure bad or unset, defaulting")
|
||||
errBadAutoWhiteBalance = errors.New("auto white balance bad or unset, defaulting")
|
||||
errBadQuantization = errors.New("quantization bad or unset, defaulting")
|
||||
errBadAWBGains = errors.New("auto white balance gains bad or unset, defaulting")
|
||||
errBadEV = errors.New("exposure value bad or unset, defaulting")
|
||||
errBadContrast = errors.New("contrast bad or unset, defaulting")
|
||||
errBadSharpness = errors.New("sharpness bad or unset, defaulting")
|
||||
errBadISO = errors.New("iso bad or unset, defaulting")
|
||||
)
|
||||
|
||||
// Possible modes for raspivid --exposure parameter.
|
||||
|
@ -196,65 +207,75 @@ func (r *Raspivid) Set(c config.Config) error {
|
|||
c.Exposure = defaultRaspividExposure
|
||||
}
|
||||
|
||||
if c.EV < -10 || c.EV > 10 {
|
||||
errs = append(errs, errBadEV)
|
||||
c.EV = defaultRaspividEV
|
||||
}
|
||||
|
||||
if c.Contrast < -100 || c.Contrast > 100 {
|
||||
errs = append(errs, errBadContrast)
|
||||
c.Contrast = defaultRaspividContrast
|
||||
}
|
||||
|
||||
if c.Sharpness < -100 || c.Sharpness > 100 {
|
||||
errs = append(errs, errBadSharpness)
|
||||
c.Sharpness = defaultRaspividSharpness
|
||||
}
|
||||
|
||||
if c.AutoWhiteBalance == "" || !sliceutils.ContainsString(AutoWhiteBalanceModes[:], c.AutoWhiteBalance) {
|
||||
errs = append(errs, errBadAutoWhiteBalance)
|
||||
c.AutoWhiteBalance = defaultRaspividAutoWhiteBalance
|
||||
}
|
||||
|
||||
if !goodAWBGains(c.AWBGains) {
|
||||
errs = append(errs, errBadAWBGains)
|
||||
c.AWBGains = defaultRaspividAWBGains
|
||||
}
|
||||
|
||||
if c.ISO == 0 || c.ISO < 100 || c.ISO > 800 {
|
||||
errs = append(errs, errBadISO)
|
||||
c.ISO = defaultRaspividISO
|
||||
}
|
||||
|
||||
r.cfg = c
|
||||
return errs
|
||||
}
|
||||
|
||||
func goodAWBGains(g string) bool {
|
||||
parts := strings.Split(g, ",")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
bg, err := strconv.ParseFloat(parts[0], 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rg, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if bg < 0 || rg < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Start will prepare the arguments for the raspivid command using the
|
||||
// configuration set using the Set method then call the raspivid command,
|
||||
// piping the video output from which the Read method will read from.
|
||||
func (r *Raspivid) Start() error {
|
||||
const disabled = "0"
|
||||
args := []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", disabled,
|
||||
"--width", fmt.Sprint(r.cfg.Width),
|
||||
"--height", fmt.Sprint(r.cfg.Height),
|
||||
"--bitrate", fmt.Sprint(r.cfg.Bitrate * 1000), // Convert from kbps to bps.
|
||||
"--framerate", fmt.Sprint(r.cfg.FrameRate),
|
||||
"--rotation", fmt.Sprint(r.cfg.Rotation),
|
||||
"--brightness", fmt.Sprint(r.cfg.Brightness),
|
||||
"--saturation", fmt.Sprint(r.cfg.Saturation),
|
||||
"--exposure", fmt.Sprint(r.cfg.Exposure),
|
||||
"--awb", fmt.Sprint(r.cfg.AutoWhiteBalance),
|
||||
args, err := r.createArgs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create raspivid args: %w", err)
|
||||
}
|
||||
|
||||
if r.cfg.HorizontalFlip {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
if r.cfg.VerticalFlip {
|
||||
args = append(args, "--vflip")
|
||||
}
|
||||
if r.cfg.HorizontalFlip {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
default:
|
||||
return fmt.Errorf("revid: invalid input codec: %v", r.cfg.InputCodec)
|
||||
case codecutil.H264:
|
||||
args = append(args,
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", fmt.Sprint(r.cfg.MinFrames),
|
||||
)
|
||||
if !r.cfg.CBR {
|
||||
args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization))
|
||||
}
|
||||
case codecutil.MJPEG:
|
||||
args = append(args, "--codec", "MJPEG")
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " "))
|
||||
r.cmd = exec.Command("raspivid", args...)
|
||||
|
||||
var err error
|
||||
r.out, err = r.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not pipe command output: %w", err)
|
||||
|
@ -325,3 +346,63 @@ func (r *Raspivid) Stop() error {
|
|||
func (r *Raspivid) IsRunning() bool {
|
||||
return r.isRunning
|
||||
}
|
||||
|
||||
func (r *Raspivid) createArgs() ([]string, error) {
|
||||
const disabled = "0"
|
||||
args := []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", disabled,
|
||||
"--width", fmt.Sprint(r.cfg.Width),
|
||||
"--height", fmt.Sprint(r.cfg.Height),
|
||||
"--bitrate", fmt.Sprint(r.cfg.Bitrate * 1000), // Convert from kbps to bps.
|
||||
"--framerate", fmt.Sprint(r.cfg.FrameRate),
|
||||
"--rotation", fmt.Sprint(r.cfg.Rotation),
|
||||
"--brightness", fmt.Sprint(r.cfg.Brightness),
|
||||
"--saturation", fmt.Sprint(r.cfg.Saturation),
|
||||
"--sharpness", fmt.Sprint(r.cfg.Sharpness),
|
||||
"--contrast", fmt.Sprint(r.cfg.Contrast),
|
||||
"--awb", fmt.Sprint(r.cfg.AutoWhiteBalance),
|
||||
"--exposure", fmt.Sprint(r.cfg.Exposure),
|
||||
}
|
||||
|
||||
if r.cfg.ISO != defaultRaspividISO {
|
||||
args = append(args, []string{"--ISO", fmt.Sprint(r.cfg.ISO)}...)
|
||||
}
|
||||
|
||||
if r.cfg.Exposure == "off" {
|
||||
args = append(args, []string{"--ev", fmt.Sprint(r.cfg.EV)}...)
|
||||
}
|
||||
|
||||
if r.cfg.AutoWhiteBalance == "off" {
|
||||
args = append(args, []string{"--awbgains", fmt.Sprint(r.cfg.AWBGains)}...)
|
||||
}
|
||||
|
||||
if r.cfg.HorizontalFlip {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
if r.cfg.VerticalFlip {
|
||||
args = append(args, "--vflip")
|
||||
}
|
||||
if r.cfg.HorizontalFlip {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
default:
|
||||
return []string{}, fmt.Errorf("revid: invalid input codec: %v", r.cfg.InputCodec)
|
||||
case codecutil.H264:
|
||||
args = append(args,
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", fmt.Sprint(r.cfg.MinFrames),
|
||||
)
|
||||
if !r.cfg.CBR {
|
||||
args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization))
|
||||
}
|
||||
case codecutil.MJPEG:
|
||||
args = append(args, "--codec", "MJPEG")
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
|
|
@ -70,3 +70,176 @@ func TestIsRunning(t *testing.T) {
|
|||
t.Error("device is running, when it should not be")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoodAWBGains(t *testing.T) {
|
||||
tests := []struct {
|
||||
gains string
|
||||
expect bool
|
||||
}{
|
||||
{gains: "-0.6,1.7", expect: false},
|
||||
{gains: "0.6,-1.6", expect: false},
|
||||
{gains: "1.3,0.3", expect: true},
|
||||
{gains: "0.8,", expect: false},
|
||||
{gains: "0.3", expect: false},
|
||||
{gains: "0,0", expect: true},
|
||||
{gains: ",1.4", expect: false},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := goodAWBGains(test.gains)
|
||||
if got != test.expect {
|
||||
t.Errorf("did not get get expected result for test: %d\nWant: %v, Got: %v\n", i, test.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
cfg config.Config
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
cfg: config.Config{
|
||||
Height: 1080,
|
||||
Width: 1440,
|
||||
Bitrate: 1000,
|
||||
FrameRate: 25,
|
||||
Rotation: 45,
|
||||
InputCodec: codecutil.H264,
|
||||
Brightness: 50,
|
||||
Saturation: 20,
|
||||
Contrast: 30,
|
||||
Sharpness: -30,
|
||||
AutoWhiteBalance: "auto",
|
||||
Exposure: "auto",
|
||||
EV: 3,
|
||||
AWBGains: "0.9,1.2",
|
||||
ISO: 300,
|
||||
CBR: true,
|
||||
},
|
||||
want: []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", "0",
|
||||
"--width", "1440",
|
||||
"--height", "1080",
|
||||
"--bitrate", "1000000", // Convert from kbps to bps.
|
||||
"--framerate", "25",
|
||||
"--rotation", "45",
|
||||
"--brightness", "50",
|
||||
"--saturation", "20",
|
||||
"--sharpness", "-30",
|
||||
"--contrast", "30",
|
||||
"--awb", "auto",
|
||||
"--exposure", "auto",
|
||||
"--ISO", "300",
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", "0",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: config.Config{
|
||||
Height: 1080,
|
||||
Width: 1440,
|
||||
Bitrate: 1000,
|
||||
FrameRate: 25,
|
||||
Rotation: 45,
|
||||
InputCodec: codecutil.H264,
|
||||
Brightness: 50,
|
||||
Saturation: 20,
|
||||
Contrast: 30,
|
||||
Sharpness: -30,
|
||||
AutoWhiteBalance: "off",
|
||||
Exposure: "off",
|
||||
EV: 3,
|
||||
AWBGains: "0.9,1.2",
|
||||
ISO: 300,
|
||||
CBR: true,
|
||||
},
|
||||
want: []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", "0",
|
||||
"--width", "1440",
|
||||
"--height", "1080",
|
||||
"--bitrate", "1000000", // Convert from kbps to bps.
|
||||
"--framerate", "25",
|
||||
"--rotation", "45",
|
||||
"--brightness", "50",
|
||||
"--saturation", "20",
|
||||
"--sharpness", "-30",
|
||||
"--contrast", "30",
|
||||
"--awb", "off",
|
||||
"--exposure", "off",
|
||||
"--ISO", "300",
|
||||
"--ev", "3",
|
||||
"--awbgains", "0.9,1.2",
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", "0",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: config.Config{
|
||||
Height: 1080,
|
||||
Width: 1440,
|
||||
Bitrate: 1000,
|
||||
FrameRate: 25,
|
||||
Rotation: 45,
|
||||
InputCodec: codecutil.H264,
|
||||
Brightness: 50,
|
||||
Saturation: 20,
|
||||
Contrast: 30,
|
||||
Sharpness: -30,
|
||||
AutoWhiteBalance: "off",
|
||||
Exposure: "off",
|
||||
EV: 3,
|
||||
ISO: 100,
|
||||
AWBGains: "0.9,1.2",
|
||||
CBR: true,
|
||||
},
|
||||
want: []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", "0",
|
||||
"--width", "1440",
|
||||
"--height", "1080",
|
||||
"--bitrate", "1000000", // Convert from kbps to bps.
|
||||
"--framerate", "25",
|
||||
"--rotation", "45",
|
||||
"--brightness", "50",
|
||||
"--saturation", "20",
|
||||
"--sharpness", "-30",
|
||||
"--contrast", "30",
|
||||
"--awb", "off",
|
||||
"--exposure", "off",
|
||||
"--ev", "3",
|
||||
"--awbgains", "0.9,1.2",
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got, err := (&Raspivid{cfg: test.cfg}).createArgs()
|
||||
if err != nil {
|
||||
t.Fatalf("did not expect error from createArgs: %v", err)
|
||||
}
|
||||
|
||||
if !cmpStrSlice(got, test.want) {
|
||||
t.Errorf("did not get expected args list for test: %d\nGot: %v\nWant: %v", i, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cmpStrSlice(a, b []string) bool {
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -102,6 +102,9 @@ type Config struct {
|
|||
// defined at the start of the file.
|
||||
AutoWhiteBalance string
|
||||
|
||||
// AWBGains sets the blue and red channel gains of an image/video capture device.
|
||||
AWBGains string
|
||||
|
||||
BitDepth uint // Sample bit depth.
|
||||
Bitrate uint // Bitrate specifies the bitrate for constant bitrate in kbps.
|
||||
Brightness uint
|
||||
|
@ -127,11 +130,17 @@ type Config struct {
|
|||
// clips by default.
|
||||
ClipDuration time.Duration
|
||||
|
||||
// Contrast is the contrast of captured video/images from a capture device.
|
||||
Contrast int
|
||||
|
||||
// Exposure defines the exposure mode used by the Raspivid input. Valid modes
|
||||
// are defined in the exported []string ExposureModes defined at the start
|
||||
// of the file.
|
||||
Exposure string
|
||||
|
||||
// EV is the exposure value for image/video capture devices.
|
||||
EV int
|
||||
|
||||
FileFPS uint // Defines the rate at which frames from a file source are processed.
|
||||
Filters []uint // Defines the methods of filtering to be used in between lexing and encoding.
|
||||
|
||||
|
@ -174,6 +183,9 @@ type Config struct {
|
|||
// defined if File input is to be used.
|
||||
InputPath string
|
||||
|
||||
// ISO sets the image/video capture device's sensitivity to light.
|
||||
ISO uint
|
||||
|
||||
// Logger holds an implementation of the Logger interface as defined in revid.go.
|
||||
// This must be set for revid to work correctly.
|
||||
Logger Logger
|
||||
|
@ -234,6 +246,9 @@ type Config struct {
|
|||
SampleRate uint // Samples a second (Hz).
|
||||
Saturation int
|
||||
|
||||
// Sharpness is the sharpness of capture image/video from a capture device.
|
||||
Sharpness int
|
||||
|
||||
// JPEGQuality is a value 0-100 inclusive, controlling JPEG compression of the
|
||||
// timelapse snaps. 100 represents minimal compression and 0 represents the most
|
||||
// compression.
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
// Config map Keys.
|
||||
const (
|
||||
KeyAutoWhiteBalance = "AutoWhiteBalance"
|
||||
KeyAWBGains = "AWBGains"
|
||||
KeyBitDepth = "BitDepth"
|
||||
KeyBitrate = "Bitrate"
|
||||
KeyBrightness = "Brightness"
|
||||
|
@ -49,7 +50,9 @@ const (
|
|||
KeyCBR = "CBR"
|
||||
KeyClipDuration = "ClipDuration"
|
||||
KeyChannels = "Channels"
|
||||
KeyContrast = "Contrast"
|
||||
KeyExposure = "Exposure"
|
||||
KeyEV = "EV"
|
||||
KeyFileFPS = "FileFPS"
|
||||
KeyFilters = "Filters"
|
||||
KeyFrameRate = "FrameRate"
|
||||
|
@ -59,6 +62,7 @@ const (
|
|||
KeyInput = "Input"
|
||||
KeyInputCodec = "InputCodec"
|
||||
KeyInputPath = "InputPath"
|
||||
KeyISO = "ISO"
|
||||
KeyLogging = "logging"
|
||||
KeyLoop = "Loop"
|
||||
KeyMinFPS = "MinFPS"
|
||||
|
@ -86,6 +90,7 @@ const (
|
|||
KeyRTPAddress = "RTPAddress"
|
||||
KeySampleRate = "SampleRate"
|
||||
KeySaturation = "Saturation"
|
||||
KeySharpness = "Sharpness"
|
||||
KeyJPEGQuality = "JPEGQuality"
|
||||
KeySuppress = "Suppress"
|
||||
KeyTimelapseDuration = "TimelapseDuration"
|
||||
|
@ -141,6 +146,11 @@ var Variables = []struct {
|
|||
Type_: "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon",
|
||||
Update: func(c *Config, v string) { c.AutoWhiteBalance = v },
|
||||
},
|
||||
{
|
||||
Name: KeyAWBGains,
|
||||
Type_: typeString,
|
||||
Update: func(c *Config, v string) { c.AWBGains = v },
|
||||
},
|
||||
{
|
||||
Name: KeyBitDepth,
|
||||
Type_: typeUint,
|
||||
|
@ -210,6 +220,28 @@ var Variables = []struct {
|
|||
Type_: typeUint,
|
||||
Update: func(c *Config, v string) { c.Channels = parseUint(KeyChannels, v, c) },
|
||||
},
|
||||
{
|
||||
Name: KeyContrast,
|
||||
Type_: typeInt,
|
||||
Update: func(c *Config, v string) {
|
||||
_v, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
c.Logger.Log(logger.Warning, "invalid contrast param", "value", v)
|
||||
}
|
||||
c.Contrast = _v
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: KeyEV,
|
||||
Type_: typeInt,
|
||||
Update: func(c *Config, v string) {
|
||||
_v, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
c.Logger.Log(logger.Warning, "invalid EV param", "value", v)
|
||||
}
|
||||
c.EV = _v
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: KeyExposure,
|
||||
Type_: "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
|
||||
|
@ -327,6 +359,11 @@ var Variables = []struct {
|
|||
Type_: typeString,
|
||||
Update: func(c *Config, v string) { c.InputPath = v },
|
||||
},
|
||||
{
|
||||
Name: KeyISO,
|
||||
Type_: typeUint,
|
||||
Update: func(c *Config, v string) { c.ISO = parseUint(KeyISO, v, c) },
|
||||
},
|
||||
{
|
||||
Name: KeyLogging,
|
||||
Type_: "enum:Debug,Info,Warning,Error,Fatal",
|
||||
|
@ -577,6 +614,17 @@ var Variables = []struct {
|
|||
c.Saturation = _v
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: KeySharpness,
|
||||
Type_: typeInt,
|
||||
Update: func(c *Config, v string) {
|
||||
_v, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
c.Logger.Log(logger.Warning, "invalid Sharpness param", "value", v)
|
||||
}
|
||||
c.Sharpness = _v
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: KeyJPEGQuality,
|
||||
Type_: typeUint,
|
||||
|
|
Loading…
Reference in New Issue