diff --git a/input/gvctrl/gvctrl-cli/main.go b/input/gvctrl/gvctrl-cli/main.go index 489c6bbc..9963a0c1 100644 --- a/input/gvctrl/gvctrl-cli/main.go +++ b/input/gvctrl/gvctrl-cli/main.go @@ -71,18 +71,18 @@ func main() { options = append(options, gvctrl.FrameRate(*fpsPtr)) } - options = append(options, gvctrl.VariableBitRate(*vbrPtr)) + options = append(options, gvctrl.VariableBitrate(*vbrPtr)) if *vbrQualityPtr != -1 { options = append(options, gvctrl.VBRQuality(gvctrl.Quality(strconv.Itoa(*vbrQualityPtr)))) } if *vbrRatePtr != 0 { - options = append(options, gvctrl.VBRBitRate(*vbrRatePtr)) + options = append(options, gvctrl.VBRBitrate(*vbrRatePtr)) } if *cbrRatePtr != 0 { - options = append(options, gvctrl.CBRBitRate(*cbrRatePtr)) + options = append(options, gvctrl.CBRBitrate(*cbrRatePtr)) } if *refreshPtr != 0 { diff --git a/input/gvctrl/gvctrl.go b/input/gvctrl/gvctrl.go index 45685154..b27c805d 100644 --- a/input/gvctrl/gvctrl.go +++ b/input/gvctrl/gvctrl.go @@ -1,7 +1,9 @@ /* DESCRIPTION - gvctrl.go provides a programmatic interface for controlling the HTTP based - server hosted by GeoVision cameras for settings control. + gvctrl.go provides exported functionality of a basic API to allow programmatic + control over the GeoVision camera (namely the GV-BX4700) through the HTTP + server used for settings control. See package documentation for further + information on the API. AUTHORS Saxon A. Nelson-Milton @@ -23,6 +25,13 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ +// Package gvctrl provides a basic API for programmatic control of the +// web based interface provided by GeoVision cameras. This API has been +// developed and tested only with the GV-BX4700, and therefore may not work +// with other models without further evolution. +// +// Settings on a GeoVision camera are updated using the Set function. One or +// more option functions may be provided to control camera function. package gvctrl import ( @@ -34,8 +43,23 @@ import ( "time" ) +// Option describes a function that will apply an option to the passed s. type Option func(s settings) (settings, error) +// Set will log in to the camera at host and submit a form of settings. The +// settings form is populated with values influenced by the optional options +// passed. Available options are defined below this function. +// +// The following defaults are applied to each configurable parameter if not +// influenced by the passed options: +// codec: H264 +// resolution: 640x360 +// framerate: 25 +// variable bitrate: off +// variable bitrate quality: good +// vbr bitrate: 250 kbps +// cbr bitrate: 512 kbps +// refresh: 2 seconds func Set(host string, options ...Option) error { // Randomly generate an ID our client will use. const ( @@ -85,14 +109,18 @@ func Set(host string, options ...Option) error { return nil } +// Codec is a video codec. type Codec string +// The avilable codecs that may be selected using CodecOut below. const ( CodecH265 Codec = "28" CodecH264 Codec = "10" CodecMJPEG Codec = "4" ) +// CodecOut will set the video codec outputted by the camera. The available +// codec options are listed above as consts. func CodecOut(c Codec) Option { return func(s settings) (settings, error) { switch c { @@ -105,6 +133,8 @@ func CodecOut(c Codec) Option { } } +// Height will set the height component of the video resolution. Available +// heights are 256, 360 and 720. func Height(h int) Option { return func(s settings) (settings, error) { v, ok := map[int]string{256: res256, 360: res360, 720: res720}[h] @@ -116,6 +146,8 @@ func Height(h int) Option { } } +// FrameRate will set the frame rate of the video. This value is defined in +// units of frames per second, and must be between 1 and 30 inclusive. func FrameRate(f int) Option { return func(s settings) (settings, error) { if 1 > f || f > 30 { @@ -126,7 +158,9 @@ func FrameRate(f int) Option { } } -func VariableBitRate(b bool) Option { +// VariableBitrate with b set true will turn on variable bitrate video and +// with b set false will turn off variable bitrate (resulting in constant bitrate). +func VariableBitrate(b bool) Option { return func(s settings) (settings, error) { s.vbr = "0" if b { @@ -136,8 +170,11 @@ func VariableBitRate(b bool) Option { } } +// Quality defines an average quality of video from the camera. type Quality string +// The available video qualities under variable bitrate. NB: it is not known +// what bitrates these correspond to. const ( QualityStandard Quality = "4" QualityFair Quality = "3" @@ -146,6 +183,8 @@ const ( QualityExcellent Quality = "0" ) +// VBRQuality will set the average quality of video under variable bitrate. +// The quality may be chosen from standard to excellent, as defined above. func VBRQuality(q Quality) Option { return func(s settings) (settings, error) { switch q { @@ -158,18 +197,30 @@ func VBRQuality(q Quality) Option { } } -func VBRBitRate(r int) Option { +// VBRBitrate will set the maximal bitrate when the camera is set to variable +// bitrate. The possible values of maximal bitrate in kbps are predefined (by +// the camera) as: 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250 and 2500. +// If the passed rate does not match one of these values, the closest value is +// selected. +func VBRBitrate(r int) Option { return func(s settings) (settings, error) { var vbrRates = []int{250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500} if s.vbr == "1" { - s.vbrBitRate = convRate(r, vbrRates) + s.vbrBitrate = convRate(r, vbrRates) return s, nil } return s, nil } } -func CBRBitRate(r int) Option { +// CBRBitrate will select the bitrate when the camera is set to constant bitrate. +// The possible values of bitrate are predefined for each resolution as follows: +// 256p: 128, 256, 512, 1024 +// 360p: 512, 1024, 2048, 3072 +// 720p: 1024, 2048, 4096, 6144 +// If the passed rate does not align with one of these values, the closest +// value is selected. +func CBRBitrate(r int) Option { return func(s settings) (settings, error) { var ( cbrRates256 = []int{128, 256, 512, 1024} @@ -179,11 +230,11 @@ func CBRBitRate(r int) Option { switch s.res { case res720: - s.cbrBitRate = convRate(r, cbrRates720) + s.cbrBitrate = convRate(r, cbrRates720) case res360: - s.cbrBitRate = convRate(r, cbrRates360) + s.cbrBitrate = convRate(r, cbrRates360) case res256: - s.cbrBitRate = convRate(r, cbrRates256) + s.cbrBitrate = convRate(r, cbrRates256) default: panic("bad resolution") } @@ -191,6 +242,9 @@ func CBRBitRate(r int) Option { } } +// Refresh will set the intra refresh period. The passed value is in seconds and +// must be between .25 and 5 inclusive. The value will be rounded to the nearest +// value divisible by .25 seconds. func Refresh(r float64) Option { return func(s settings) (settings, error) { const ( diff --git a/input/gvctrl/gvctrl_test.go b/input/gvctrl/gvctrl_test.go index 92bbb5a8..d15ed9be 100644 --- a/input/gvctrl/gvctrl_test.go +++ b/input/gvctrl/gvctrl_test.go @@ -162,7 +162,7 @@ func TestVBRBitrate(t *testing.T) { in: settings{vbr: "1"}, want: settings{ vbr: "1", - vbrBitRate: "250000", + vbrBitrate: "250000", }, }, { @@ -170,13 +170,13 @@ func TestVBRBitrate(t *testing.T) { in: settings{vbr: "1"}, want: settings{ vbr: "1", - vbrBitRate: "500000", + vbrBitrate: "500000", }, }, } for i, test := range tests { - got, _ := VBRBitRate(test.r)(test.in) + got, _ := VBRBitrate(test.r)(test.in) if got != test.want { t.Errorf("did not get expected result for test: %d\nGot: %v\nWant: %v", i, got, test.want) } @@ -198,7 +198,7 @@ func TestCBRBitrate(t *testing.T) { want: settings{ vbr: "0", res: res256, - cbrBitRate: "512000", + cbrBitrate: "512000", }, }, { @@ -210,7 +210,7 @@ func TestCBRBitrate(t *testing.T) { want: settings{ vbr: "0", res: res256, - cbrBitRate: "128000", + cbrBitrate: "128000", }, }, { @@ -222,7 +222,7 @@ func TestCBRBitrate(t *testing.T) { want: settings{ vbr: "0", res: res360, - cbrBitRate: "2048000", + cbrBitrate: "2048000", }, }, { @@ -234,13 +234,13 @@ func TestCBRBitrate(t *testing.T) { want: settings{ vbr: "0", res: res720, - cbrBitRate: "1024000", + cbrBitrate: "1024000", }, }, } for i, test := range tests { - got, _ := CBRBitRate(test.r)(test.in) + got, _ := CBRBitrate(test.r)(test.in) if got != test.want { t.Errorf("did not get expected result for test: %d\nGot: %v\nWant: %v", i, got, test.want) } @@ -302,8 +302,8 @@ func TestPopulateForm(t *testing.T) { frameRate: defaultFrameRate, vbr: defaultVBR, quality: defaultQuality, - vbrBitRate: defaultVBRBitRate, - cbrBitRate: defaultCBRBitRate, + vbrBitrate: defaultVBRBitrate, + cbrBitrate: defaultCBRBitrate, refresh: defaultRefresh, }, want: "dwConnType=5&mpeg_type=28&dwflicker_hz=0&szResolution=6400360&dwFrameRate=25000&vbr_enable=0&max_bit_rate=512000&custom_rate_control_type=0&custom_bitrate=0&custom_qp_init=25&custom_qp_min=10&custom_qp_max=40&gop_N=2000&dwEncProfile=1&dwEncLevel=31&dwEntropy=0&u8PreAlarmBuf=1&u32PostAlarmBuf2Disk=1&u8SplitInterval=5&bEnableIO=1&bEbIoIn=1&bEbIoIn1=1&bOSDFontSize=0&bEnableOSDCameraName=1&bCamNamePos=2&bEnableOSDDate=1&bDatePos=2&bEnableOSDTime=1&bTimePos=2&szOsdCamName=Camera&u16PostAlarmBuf=1&dwCameraId=1&LangCode=undefined&Recflag=0&submit=Apply", @@ -315,8 +315,8 @@ func TestPopulateForm(t *testing.T) { frameRate: defaultFrameRate, vbr: defaultVBR, quality: defaultQuality, - vbrBitRate: defaultVBRBitRate, - cbrBitRate: defaultCBRBitRate, + vbrBitrate: defaultVBRBitrate, + cbrBitrate: defaultCBRBitrate, refresh: defaultRefresh, }, want: "dwConnType=5&mpeg_type=4&dwflicker_hz=0&szResolution=6400360&dwFrameRate=25000&vbr_enable=1&dwVbrQuality=2&vbrmaxbitrate=500000&custom_qp_init=25&gop_N=1500&u8PreAlarmBuf=1&u32PostAlarmBuf2Disk=1&u8SplitInterval=5&bEnableIO=1&bEbIoIn=1&bEbIoIn1=1&bOSDFontSize=0&bEnableOSDCameraName=1&bCamNamePos=2&bEnableOSDDate=1&bDatePos=2&bEnableOSDTime=1&bTimePos=2&szOsdCamName=Camera&u16PostAlarmBuf=1&dwCameraId=1&LangCode=undefined&Recflag=0&submit=Apply", @@ -328,8 +328,8 @@ func TestPopulateForm(t *testing.T) { frameRate: defaultFrameRate, vbr: "1", quality: defaultQuality, - vbrBitRate: defaultVBRBitRate, - cbrBitRate: defaultCBRBitRate, + vbrBitrate: defaultVBRBitrate, + cbrBitrate: defaultCBRBitrate, refresh: defaultRefresh, }, want: "dwConnType=5&mpeg_type=10&dwflicker_hz=0&szResolution=6400360&dwFrameRate=25000&vbr_enable=1&dwVbrQuality=2&vbrmaxbitrate=250000&custom_rate_control_type=0&custom_bitrate=0&custom_qp_init=25&custom_qp_min=10&custom_qp_max=40&gop_N=2000&dwEncProfile=1&dwEncLevel=31&dwEntropy=0&u8PreAlarmBuf=1&u32PostAlarmBuf2Disk=1&u8SplitInterval=5&bEnableIO=1&bEbIoIn=1&bEbIoIn1=1&bOSDFontSize=0&bEnableOSDCameraName=1&bCamNamePos=2&bEnableOSDDate=1&bDatePos=2&bEnableOSDTime=1&bTimePos=2&szOsdCamName=Camera&u16PostAlarmBuf=1&dwCameraId=1&LangCode=undefined&Recflag=0&submit=Apply", diff --git a/input/gvctrl/utils.go b/input/gvctrl/utils.go index 1ca158da..1c2775fa 100644 --- a/input/gvctrl/utils.go +++ b/input/gvctrl/utils.go @@ -46,8 +46,8 @@ const ( defaultFrameRate = "25000" // 25 fps defaultVBR = "0" // Variable bitrate off defaultQuality = QualityGood - defaultVBRBitRate = "250000" // 512 kbps (lowest with 360p) - defaultCBRBitRate = "512000" + defaultVBRBitrate = "250000" // 512 kbps (lowest with 360p) + defaultCBRBitrate = "512000" defaultRefresh = "2000" // 2 seconds ) @@ -57,8 +57,8 @@ type settings struct { frameRate string vbr string quality Quality - vbrBitRate string - cbrBitRate string + vbrBitrate string + cbrBitrate string refresh string } @@ -69,8 +69,8 @@ func newSettings() settings { frameRate: defaultFrameRate, vbr: defaultVBR, quality: defaultQuality, - vbrBitRate: defaultVBRBitRate, - cbrBitRate: defaultCBRBitRate, + vbrBitrate: defaultVBRBitrate, + cbrBitrate: defaultCBRBitrate, refresh: defaultRefresh, } } @@ -122,11 +122,11 @@ func populateForm(s settings) url.Values { switch s.vbr { case "0": f.Set("vbr_enable", "0") - f.Set("max_bit_rate", s.cbrBitRate) + f.Set("max_bit_rate", s.cbrBitrate) case "1": f.Set("vbr_enable", "1") f.Set("dwVbrQuality", string(s.quality)) - f.Set("vbrmaxbitrate", s.vbrBitRate) + f.Set("vbrmaxbitrate", s.vbrBitrate) default: panic("invalid vbrEnable parameter") }