mirror of
https://github.com/gen2brain/cam2ip.git
synced 2026-01-07 23:28:38 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5870110d9 | ||
|
|
72f12931da | ||
|
|
bb676e4e44 | ||
|
|
ec60387edf | ||
|
|
0503355005 | ||
|
|
c9d77f03e7 | ||
|
|
37b19fcfe8 | ||
|
|
5006c48690 | ||
|
|
d340fa2dc3 | ||
|
|
fa5233255e | ||
|
|
ac555cbf7b | ||
|
|
e1f03b55a1 | ||
|
|
3c0c949f31 | ||
|
|
8a740337ab | ||
|
|
cbc7d21b23 | ||
|
|
45ca5bdedb | ||
|
|
173deebc88 | ||
|
|
7392d8d9b8 | ||
|
|
1c9dfcb84c | ||
|
|
f65a7cf1aa |
43
README.md
43
README.md
@@ -11,17 +11,36 @@ or
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
* [OpenCV 2.x](http://opencv.org/)
|
* [libjpeg-turbo](https://www.libjpeg-turbo.org/) (use `-tags jpeg` to build without `CGo`)
|
||||||
|
|
||||||
|
On Linux/RPi native Go [V4L](https://github.com/korandiz/v4l) implementation is used to capture images.
|
||||||
|
On Windows [Video for Windows (VfW)](https://en.wikipedia.org/wiki/Video_for_Windows) framework is used over win32 API.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
go get -u github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
### Build tags
|
||||||
|
|
||||||
|
* `cv2` - build with `OpenCV` 2.x ([go-opencv](https://github.com/lazywei/go-opencv))
|
||||||
|
* `cv3` - build with `OpenCV` 3.x ([gocv](https://github.com/hybridgroup/gocv))
|
||||||
|
* `jpeg` - build with native Go `image/jpeg` instead of `libjpeg-turbo`
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
Binaries are compiled with static OpenCV library:
|
Binaries are compiled with static OpenCV/libjpeg-turbo libraries, they should just work:
|
||||||
|
|
||||||
- [Android 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-android.tar.gz)
|
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.tar.gz)
|
||||||
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-64bit.tar.gz)
|
- [Linux 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.tar.gz)
|
||||||
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-RPi.tar.gz)
|
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi.tar.gz)
|
||||||
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2.zip)
|
- [RPi 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-cv2.tar.gz)
|
||||||
|
- [RPi 32bit Static](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-nocgo.tar.gz)
|
||||||
|
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3.tar.gz)
|
||||||
|
- [RPi3 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3-cv2.tar.gz)
|
||||||
|
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit.zip)
|
||||||
|
- [Windows 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit-cv2.zip)
|
||||||
|
- [Windows 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.zip)
|
||||||
|
- [Windows 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.zip)
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@@ -38,16 +57,20 @@ Usage of ./cam2ip:
|
|||||||
Bind address (default ":56000")
|
Bind address (default ":56000")
|
||||||
-delay int
|
-delay int
|
||||||
Delay between frames, in milliseconds (default 10)
|
Delay between frames, in milliseconds (default 10)
|
||||||
-frame-height float
|
-height float
|
||||||
Frame height (default 480)
|
Frame height (default 480)
|
||||||
-frame-width float
|
-width float
|
||||||
Frame width (default 640)
|
Frame width (default 640)
|
||||||
-htpasswd-file string
|
-htpasswd-file string
|
||||||
Path to htpasswd file, if empty auth is disabled
|
Path to htpasswd file, if empty auth is disabled
|
||||||
-index int
|
-index int
|
||||||
Camera index
|
Camera index
|
||||||
-webgl
|
-nowebgl
|
||||||
Use WebGL to draw images
|
Disable WebGL drawing of images (html handler)
|
||||||
|
-rotate int
|
||||||
|
Rotate image, valid values are 90, 180, 270
|
||||||
|
-video-file string
|
||||||
|
Use video file instead of camera
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handlers
|
### Handlers
|
||||||
|
|||||||
23
cam2ip.go
23
cam2ip.go
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
name = "cam2ip"
|
name = "cam2ip"
|
||||||
version = "1.0"
|
version = "1.5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -19,9 +19,10 @@ func main() {
|
|||||||
|
|
||||||
flag.IntVar(&srv.Index, "index", 0, "Camera index")
|
flag.IntVar(&srv.Index, "index", 0, "Camera index")
|
||||||
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
|
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
|
||||||
flag.Float64Var(&srv.FrameWidth, "frame-width", 640, "Frame width")
|
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width")
|
||||||
flag.Float64Var(&srv.FrameHeight, "frame-height", 480, "Frame height")
|
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height")
|
||||||
flag.BoolVar(&srv.WebGL, "webgl", false, "Use WebGL to draw images")
|
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270")
|
||||||
|
flag.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
|
||||||
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
|
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
|
||||||
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
|
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -38,16 +39,22 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Camera, err = camera.NewCamera(srv.Index)
|
if srv.FileName != "" {
|
||||||
|
if _, err = os.Stat(srv.FileName); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Camera.SetProperty(camera.PropFrameWidth, srv.FrameWidth)
|
srv.Reader = cam
|
||||||
srv.Camera.SetProperty(camera.PropFrameHeight, srv.FrameHeight)
|
|
||||||
|
|
||||||
defer srv.Camera.Close()
|
defer srv.Reader.Close()
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)
|
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)
|
||||||
|
|
||||||
|
|||||||
80
cam2ip_cv.go
Normal file
80
cam2ip_cv.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// +build cv2,cv3
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cam2ip/camera"
|
||||||
|
"github.com/gen2brain/cam2ip/server"
|
||||||
|
"github.com/gen2brain/cam2ip/video"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
name = "cam2ip"
|
||||||
|
version = "1.5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.NewServer()
|
||||||
|
|
||||||
|
flag.IntVar(&srv.Index, "index", 0, "Camera index")
|
||||||
|
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
|
||||||
|
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width")
|
||||||
|
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height")
|
||||||
|
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270")
|
||||||
|
flag.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
|
||||||
|
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
|
||||||
|
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
|
||||||
|
flag.StringVar(&srv.FileName, "video-file", "", "Use video file instead of camera")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
srv.Name = name
|
||||||
|
srv.Version = version
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if srv.Htpasswd != "" {
|
||||||
|
if _, err = os.Stat(srv.Htpasswd); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.FileName != "" {
|
||||||
|
if _, err = os.Stat(srv.FileName); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.FileName != "" {
|
||||||
|
vid, err := video.New(video.Options{srv.FileName, srv.Rotate})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.Reader = vid
|
||||||
|
} else {
|
||||||
|
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.Reader = cam
|
||||||
|
}
|
||||||
|
|
||||||
|
defer srv.Reader.Close()
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)
|
||||||
|
|
||||||
|
err = srv.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
105
camera/camera.go
105
camera/camera.go
@@ -1,105 +0,0 @@
|
|||||||
// Package camera.
|
|
||||||
package camera
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
|
|
||||||
"github.com/lazywei/go-opencv/opencv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Property identifiers.
|
|
||||||
const (
|
|
||||||
PropPosMsec = iota
|
|
||||||
PropPosFrames
|
|
||||||
PropPosAviRatio
|
|
||||||
PropFrameWidth
|
|
||||||
PropFrameHeight
|
|
||||||
PropFps
|
|
||||||
PropFourcc
|
|
||||||
PropFrameCount
|
|
||||||
PropFormat
|
|
||||||
PropMode
|
|
||||||
PropBrightness
|
|
||||||
PropContrast
|
|
||||||
PropSaturation
|
|
||||||
PropHue
|
|
||||||
PropGain
|
|
||||||
PropExposure
|
|
||||||
PropConvertRgb
|
|
||||||
PropWhiteBalanceU
|
|
||||||
PropRectification
|
|
||||||
PropMonocrome
|
|
||||||
PropSharpness
|
|
||||||
PropAutoExposure
|
|
||||||
PropGamma
|
|
||||||
PropTemperature
|
|
||||||
PropTrigger
|
|
||||||
PropTriggerDelay
|
|
||||||
PropWhiteBalanceV
|
|
||||||
PropZoom
|
|
||||||
PropFocus
|
|
||||||
PropGuid
|
|
||||||
PropIsoSpeed
|
|
||||||
PropMaxDc1394
|
|
||||||
PropBacklight
|
|
||||||
PropPan
|
|
||||||
PropTilt
|
|
||||||
PropRoll
|
|
||||||
PropIris
|
|
||||||
PropSettings
|
|
||||||
PropBuffersize
|
|
||||||
)
|
|
||||||
|
|
||||||
// Camera represents camera.
|
|
||||||
type Camera struct {
|
|
||||||
Index int
|
|
||||||
camera *opencv.Capture
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCamera returns new Camera for given camera index.
|
|
||||||
func NewCamera(index int) (camera *Camera, err error) {
|
|
||||||
camera = &Camera{}
|
|
||||||
camera.Index = index
|
|
||||||
|
|
||||||
camera.camera = opencv.NewCameraCapture(index)
|
|
||||||
if camera.camera == nil {
|
|
||||||
err = fmt.Errorf("camera: can not open camera %d", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads next frame from camera and returns image.
|
|
||||||
func (c *Camera) Read() (img image.Image, err error) {
|
|
||||||
if c.camera.GrabFrame() {
|
|
||||||
frame := c.camera.RetrieveFrame(1)
|
|
||||||
img = frame.ToImage()
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("camera: can not grab frame")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProperty returns the specified camera property.
|
|
||||||
func (c *Camera) GetProperty(id int) float64 {
|
|
||||||
return c.camera.GetProperty(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetProperty sets a camera property.
|
|
||||||
func (c *Camera) SetProperty(id int, value float64) int {
|
|
||||||
return c.camera.SetProperty(id, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes camera.
|
|
||||||
func (c *Camera) Close() (err error) {
|
|
||||||
if c.camera == nil {
|
|
||||||
err = fmt.Errorf("camera: camera is not opened")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.camera.Release()
|
|
||||||
c.camera = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
46
camera/camera_const_cv.go
Normal file
46
camera/camera_const_cv.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// +build cv2 cv3
|
||||||
|
|
||||||
|
package camera
|
||||||
|
|
||||||
|
// Property identifiers.
|
||||||
|
const (
|
||||||
|
PropPosMsec = iota
|
||||||
|
PropPosFrames
|
||||||
|
PropPosAviRatio
|
||||||
|
PropFrameWidth
|
||||||
|
PropFrameHeight
|
||||||
|
PropFps
|
||||||
|
PropFourcc
|
||||||
|
PropFrameCount
|
||||||
|
PropFormat
|
||||||
|
PropMode
|
||||||
|
PropBrightness
|
||||||
|
PropContrast
|
||||||
|
PropSaturation
|
||||||
|
PropHue
|
||||||
|
PropGain
|
||||||
|
PropExposure
|
||||||
|
PropConvertRgb
|
||||||
|
PropWhiteBalanceU
|
||||||
|
PropRectification
|
||||||
|
PropMonocrome
|
||||||
|
PropSharpness
|
||||||
|
PropAutoExposure
|
||||||
|
PropGamma
|
||||||
|
PropTemperature
|
||||||
|
PropTrigger
|
||||||
|
PropTriggerDelay
|
||||||
|
PropWhiteBalanceV
|
||||||
|
PropZoom
|
||||||
|
PropFocus
|
||||||
|
PropGuid
|
||||||
|
PropIsoSpeed
|
||||||
|
PropMaxDc1394
|
||||||
|
PropBacklight
|
||||||
|
PropPan
|
||||||
|
PropTilt
|
||||||
|
PropRoll
|
||||||
|
PropIris
|
||||||
|
PropSettings
|
||||||
|
PropBuffersize
|
||||||
|
)
|
||||||
21
camera/camera_const_linux.go
Normal file
21
camera/camera_const_linux.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// +build !cv2,!cv3
|
||||||
|
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/korandiz/v4l"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Property identifiers.
|
||||||
|
const (
|
||||||
|
PropBrightness = v4l.CtrlBrightness
|
||||||
|
PropContrast = v4l.CtrlContrast
|
||||||
|
PropSaturation = v4l.CtrlSaturation
|
||||||
|
PropHue = v4l.CtrlHue
|
||||||
|
PropGain = v4l.CtrlGain
|
||||||
|
PropExposure = v4l.CtrlExposure
|
||||||
|
PropWhiteBalanceU = v4l.CtrlWhiteBalance
|
||||||
|
PropSharpness = v4l.CtrlSharpness
|
||||||
|
PropWhiteBalanceV = v4l.CtrlDoWhiteBalance
|
||||||
|
PropBacklight = v4l.CtrlBacklightCompensation
|
||||||
|
)
|
||||||
96
camera/camera_cv2.go
Normal file
96
camera/camera_cv2.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// +build cv2,!cv3
|
||||||
|
|
||||||
|
// Package camera.
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/lazywei/go-opencv/opencv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Index int
|
||||||
|
Rotate int
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera represents camera.
|
||||||
|
type Camera struct {
|
||||||
|
opts Options
|
||||||
|
camera *opencv.Capture
|
||||||
|
frame *opencv.IplImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Camera for given camera index.
|
||||||
|
func New(opts Options) (camera *Camera, err error) {
|
||||||
|
camera = &Camera{}
|
||||||
|
camera.opts = opts
|
||||||
|
|
||||||
|
camera.camera = opencv.NewCameraCapture(opts.Index)
|
||||||
|
if camera.camera == nil {
|
||||||
|
err = fmt.Errorf("camera: can not open camera %d", opts.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.SetProperty(PropFrameWidth, opts.Width)
|
||||||
|
camera.SetProperty(PropFrameHeight, opts.Height)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from camera and returns image.
|
||||||
|
func (c *Camera) Read() (img image.Image, err error) {
|
||||||
|
if c.camera.GrabFrame() {
|
||||||
|
c.frame = c.camera.RetrieveFrame(1)
|
||||||
|
|
||||||
|
if c.frame == nil {
|
||||||
|
err = fmt.Errorf("camera: can not retrieve frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img = c.frame.ToImage()
|
||||||
|
if c.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("camera: can not grab frame")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty returns the specified camera property.
|
||||||
|
func (c *Camera) GetProperty(id int) float64 {
|
||||||
|
return c.camera.GetProperty(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProperty sets a camera property.
|
||||||
|
func (c *Camera) SetProperty(id int, value float64) {
|
||||||
|
c.camera.SetProperty(id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes camera.
|
||||||
|
func (c *Camera) Close() (err error) {
|
||||||
|
if c.camera == nil {
|
||||||
|
err = fmt.Errorf("camera: camera is not opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.frame.Release()
|
||||||
|
c.camera.Release()
|
||||||
|
c.camera = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
104
camera/camera_cv3.go
Normal file
104
camera/camera_cv3.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// +build cv3,!cv2
|
||||||
|
|
||||||
|
// Package camera.
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"gocv.io/x/gocv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Index int
|
||||||
|
Rotate int
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera represents camera.
|
||||||
|
type Camera struct {
|
||||||
|
opts Options
|
||||||
|
camera *gocv.VideoCapture
|
||||||
|
frame *gocv.Mat
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Camera for given camera index.
|
||||||
|
func New(opts Options) (camera *Camera, err error) {
|
||||||
|
camera = &Camera{}
|
||||||
|
camera.opts = opts
|
||||||
|
|
||||||
|
mat := gocv.NewMat()
|
||||||
|
camera.frame = &mat
|
||||||
|
|
||||||
|
camera.camera, err = gocv.VideoCaptureDevice(opts.Index)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: can not open camera %d: %s", opts.Index, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.SetProperty(PropFrameWidth, opts.Width)
|
||||||
|
camera.SetProperty(PropFrameHeight, opts.Height)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from camera and returns image.
|
||||||
|
func (c *Camera) Read() (img image.Image, err error) {
|
||||||
|
ok := c.camera.Read(c.frame)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("camera: can not grab frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img, e := c.frame.ToImage()
|
||||||
|
if e != nil {
|
||||||
|
err = fmt.Errorf("camera: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.frame == nil {
|
||||||
|
err = fmt.Errorf("camera: can not retrieve frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty returns the specified camera property.
|
||||||
|
func (c *Camera) GetProperty(id int) float64 {
|
||||||
|
return c.camera.Get(gocv.VideoCaptureProperties(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProperty sets a camera property.
|
||||||
|
func (c *Camera) SetProperty(id int, value float64) {
|
||||||
|
c.camera.Set(gocv.VideoCaptureProperties(id), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes camera.
|
||||||
|
func (c *Camera) Close() (err error) {
|
||||||
|
if c.camera == nil {
|
||||||
|
err = fmt.Errorf("camera: camera is not opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.frame.Close()
|
||||||
|
err = c.camera.Close()
|
||||||
|
c.camera = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
132
camera/camera_linux.go
Normal file
132
camera/camera_linux.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// +build !cv2,!cv3
|
||||||
|
|
||||||
|
// Package camera.
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/korandiz/v4l"
|
||||||
|
"github.com/korandiz/v4l/fmt/mjpeg"
|
||||||
|
|
||||||
|
im "github.com/gen2brain/cam2ip/image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Index int
|
||||||
|
Rotate int
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera represents camera.
|
||||||
|
type Camera struct {
|
||||||
|
opts Options
|
||||||
|
camera *v4l.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Camera for given camera index.
|
||||||
|
func New(opts Options) (camera *Camera, err error) {
|
||||||
|
camera = &Camera{}
|
||||||
|
camera.opts = opts
|
||||||
|
|
||||||
|
devices := v4l.FindDevices()
|
||||||
|
if len(devices) < opts.Index+1 {
|
||||||
|
err = fmt.Errorf("camera: no camera at index %d", opts.Index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.camera, err = v4l.Open(devices[opts.Index].Path)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if camera.camera == nil {
|
||||||
|
err = fmt.Errorf("camera: can not open camera %d", opts.Index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := camera.camera.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Format = mjpeg.FourCC
|
||||||
|
config.Width = int(opts.Width)
|
||||||
|
config.Height = int(opts.Height)
|
||||||
|
|
||||||
|
err = camera.camera.SetConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = camera.camera.TurnOn()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from camera and returns image.
|
||||||
|
func (c *Camera) Read() (img image.Image, err error) {
|
||||||
|
|
||||||
|
buffer, err := c.camera.Capture()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: can not grab frame: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err = im.NewDecoder(buffer).Decode()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty returns the specified camera property.
|
||||||
|
func (c *Camera) GetProperty(id int) float64 {
|
||||||
|
ret, _ := c.camera.GetControl(uint32(id))
|
||||||
|
return float64(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProperty sets a camera property.
|
||||||
|
func (c *Camera) SetProperty(id int, value float64) {
|
||||||
|
c.camera.SetControl(uint32(id), int32(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes camera.
|
||||||
|
func (c *Camera) Close() (err error) {
|
||||||
|
if c.camera == nil {
|
||||||
|
err = fmt.Errorf("camera: camera is not opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.camera.TurnOff()
|
||||||
|
|
||||||
|
c.camera.Close()
|
||||||
|
c.camera = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCamera(t *testing.T) {
|
func TestCamera(t *testing.T) {
|
||||||
camera, err := NewCamera(1)
|
camera, err := New(Options{0, 0, 640, 480})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -25,18 +25,6 @@ func TestCamera(t *testing.T) {
|
|||||||
|
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
var width, height float64 = 640, 480
|
|
||||||
camera.SetProperty(PropFrameWidth, width)
|
|
||||||
camera.SetProperty(PropFrameHeight, height)
|
|
||||||
|
|
||||||
if camera.GetProperty(PropFrameWidth) != width {
|
|
||||||
t.Error("FrameWidth not correct")
|
|
||||||
}
|
|
||||||
|
|
||||||
if camera.GetProperty(PropFrameHeight) != height {
|
|
||||||
t.Error("FrameHeight not correct")
|
|
||||||
}
|
|
||||||
|
|
||||||
var i int
|
var i int
|
||||||
var n int = 10
|
var n int = 10
|
||||||
|
|
||||||
|
|||||||
412
camera/camera_windows.go
Normal file
412
camera/camera_windows.go
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
// +build !cv2,!cv3
|
||||||
|
|
||||||
|
// Package camera.
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Index int
|
||||||
|
Rotate int
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera represents camera.
|
||||||
|
type Camera struct {
|
||||||
|
opts Options
|
||||||
|
camera syscall.Handle
|
||||||
|
frame *image.RGBA
|
||||||
|
hdr *videoHdr
|
||||||
|
instance syscall.Handle
|
||||||
|
className string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Camera for given camera index.
|
||||||
|
func New(opts Options) (camera *Camera, err error) {
|
||||||
|
camera = &Camera{}
|
||||||
|
camera.opts = opts
|
||||||
|
camera.className = "capWindowClass"
|
||||||
|
|
||||||
|
camera.instance, err = getModuleHandle()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.frame = image.NewRGBA(image.Rect(0, 0, int(camera.opts.Width), int(camera.opts.Height)))
|
||||||
|
|
||||||
|
go func(c *Camera) {
|
||||||
|
fn := func(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
|
switch msg {
|
||||||
|
case wmClose:
|
||||||
|
destroyWindow(hwnd)
|
||||||
|
case wmDestroy:
|
||||||
|
postQuitMessage(0)
|
||||||
|
default:
|
||||||
|
ret := defWindowProc(hwnd, msg, wparam, lparam)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
err = registerClass(c.className, c.instance, fn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault, int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.camera, err = capCreateCaptureWindow("", wsChild, 0, 0, int64(c.opts.Width), int64(c.opts.Height), hwnd, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := sendMessage(c.camera, wmCapDriverConnect, uintptr(c.opts.Index), 0)
|
||||||
|
if bool(int(ret) == 0) {
|
||||||
|
err = fmt.Errorf("camera: can not open camera %d", c.opts.Index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bi bitmapInfo
|
||||||
|
size := sendMessage(c.camera, wmCapGetVideoformat, 0, 0)
|
||||||
|
sendMessage(c.camera, wmCapGetVideoformat, size, uintptr(unsafe.Pointer(&bi)))
|
||||||
|
|
||||||
|
bi.BmiHeader.BiWidth = int32(c.opts.Width)
|
||||||
|
bi.BmiHeader.BiHeight = int32(c.opts.Height)
|
||||||
|
|
||||||
|
ret = sendMessage(c.camera, wmCapSetVideoformat, size, uintptr(unsafe.Pointer(&bi)))
|
||||||
|
if bool(int(ret) == 0) {
|
||||||
|
err = fmt.Errorf("camera: can not set video format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback))
|
||||||
|
|
||||||
|
messageLoop(c.camera)
|
||||||
|
}(camera)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from camera and returns image.
|
||||||
|
func (c *Camera) Read() (img image.Image, err error) {
|
||||||
|
ret := sendMessage(c.camera, wmCapGrabFrameNoStop, 0, 0)
|
||||||
|
if bool(int(ret) == 0) {
|
||||||
|
err = fmt.Errorf("camera: can not grab frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := (*[1 << 24]uint8)(unsafe.Pointer(c.hdr.LpData))[0:c.hdr.DwBytesUsed]
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
|
||||||
|
width := int(c.opts.Width)
|
||||||
|
height := int(c.opts.Height)
|
||||||
|
|
||||||
|
// Taken from https://github.com/hotei/bmp/blob/master/bmpRGBA.go#L12
|
||||||
|
// There are 3 bytes per pixel, and each row is 4-byte aligned.
|
||||||
|
b := make([]byte, (3*width+3)&^3)
|
||||||
|
// BMP images are stored bottom-up rather than top-down.
|
||||||
|
for y := height - 1; y >= 0; y-- {
|
||||||
|
_, err = r.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("camera: can not retrieve frame: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := c.frame.Pix[y*c.frame.Stride : y*c.frame.Stride+width*4]
|
||||||
|
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
|
||||||
|
// BMP images are stored in BGR order rather than RGB order.
|
||||||
|
p[i+0] = b[j+2]
|
||||||
|
p[i+1] = b[j+1]
|
||||||
|
p[i+2] = b[j+0]
|
||||||
|
p[i+3] = 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img = c.frame
|
||||||
|
if c.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty returns the specified camera property.
|
||||||
|
func (c *Camera) GetProperty(id int) float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProperty sets a camera property.
|
||||||
|
func (c *Camera) SetProperty(id int, value float64) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes camera.
|
||||||
|
func (c *Camera) Close() (err error) {
|
||||||
|
sendMessage(c.camera, wmCapSetCallbackFrame, 0, 0)
|
||||||
|
unregisterClass(c.className, c.instance)
|
||||||
|
sendMessage(c.camera, wmCapDriverDisconnect, 0, 0)
|
||||||
|
destroyWindow(c.camera)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback function.
|
||||||
|
func (c *Camera) callback(hwvd syscall.Handle, hdr *videoHdr) uintptr {
|
||||||
|
c.hdr = hdr
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
user32 = syscall.NewLazyDLL("user32.dll")
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
avicap32 = syscall.NewLazyDLL("avicap32.dll")
|
||||||
|
|
||||||
|
createWindowExW = user32.NewProc("CreateWindowExW")
|
||||||
|
destroyWindowW = user32.NewProc("DestroyWindow")
|
||||||
|
defWindowProcW = user32.NewProc("DefWindowProcW")
|
||||||
|
dispatchMessageW = user32.NewProc("DispatchMessageW")
|
||||||
|
translateMessageW = user32.NewProc("TranslateMessage")
|
||||||
|
getMessageW = user32.NewProc("GetMessageW")
|
||||||
|
sendMessageW = user32.NewProc("SendMessageW")
|
||||||
|
postQuitMessageW = user32.NewProc("PostQuitMessage")
|
||||||
|
registerClassExW = user32.NewProc("RegisterClassExW")
|
||||||
|
unregisterClassW = user32.NewProc("UnregisterClassW")
|
||||||
|
|
||||||
|
getModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||||
|
capCreateCaptureWindowW = avicap32.NewProc("capCreateCaptureWindowW")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
wmDestroy = 0x0002
|
||||||
|
wmClose = 0x0010
|
||||||
|
wmUser = 0x0400
|
||||||
|
|
||||||
|
wmCapStart = wmUser
|
||||||
|
wmCapSetCallbackFrame = wmCapStart + 5
|
||||||
|
wmCapDriverConnect = wmCapStart + 10
|
||||||
|
wmCapDriverDisconnect = wmCapStart + 11
|
||||||
|
wmCapGetVideoformat = wmCapStart + 44
|
||||||
|
wmCapSetVideoformat = wmCapStart + 45
|
||||||
|
wmCapGrabFrame = wmCapStart + 60
|
||||||
|
wmCapGrabFrameNoStop = wmCapStart + 61
|
||||||
|
wmCapStop = wmCapStart + 68
|
||||||
|
wmCapAbort = wmCapStart + 69
|
||||||
|
|
||||||
|
wsChild = 0x40000000
|
||||||
|
wsOverlappedWindow = 0x00CF0000
|
||||||
|
|
||||||
|
cwUseDefault = 0x7fffffff
|
||||||
|
)
|
||||||
|
|
||||||
|
// wndClassExW https://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx
|
||||||
|
type wndClassExW struct {
|
||||||
|
size uint32
|
||||||
|
style uint32
|
||||||
|
wndProc uintptr
|
||||||
|
clsExtra int32
|
||||||
|
wndExtra int32
|
||||||
|
instance syscall.Handle
|
||||||
|
icon syscall.Handle
|
||||||
|
cursor syscall.Handle
|
||||||
|
background syscall.Handle
|
||||||
|
menuName *uint16
|
||||||
|
className *uint16
|
||||||
|
iconSm syscall.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// msgW https://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx
|
||||||
|
type msgW struct {
|
||||||
|
hwnd syscall.Handle
|
||||||
|
message uint32
|
||||||
|
wParam uintptr
|
||||||
|
lParam uintptr
|
||||||
|
time uint32
|
||||||
|
pt pointW
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://msdn.microsoft.com/en-us/ecb0f0e1-90c2-48ab-a069-552262b49c7c
|
||||||
|
type pointW struct {
|
||||||
|
x, y int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
|
||||||
|
type bitmapInfoHeader struct {
|
||||||
|
BiSize uint32
|
||||||
|
BiWidth int32
|
||||||
|
BiHeight int32
|
||||||
|
BiPlanes uint16
|
||||||
|
BiBitCount uint16
|
||||||
|
BiCompression uint32
|
||||||
|
BiSizeImage uint32
|
||||||
|
BiXPelsPerMeter int32
|
||||||
|
BiYPelsPerMeter int32
|
||||||
|
BiClrUsed uint32
|
||||||
|
BiClrImportant uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx
|
||||||
|
type bitmapInfo struct {
|
||||||
|
BmiHeader bitmapInfoHeader
|
||||||
|
BmiColors *rgbQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx
|
||||||
|
type rgbQuad struct {
|
||||||
|
RgbBlue byte
|
||||||
|
RgbGreen byte
|
||||||
|
RgbRed byte
|
||||||
|
RgbReserved byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/vfw/ns-vfw-videohdr_tag
|
||||||
|
type videoHdr struct {
|
||||||
|
LpData *uint8
|
||||||
|
DwBufferLength uint32
|
||||||
|
DwBytesUsed uint32
|
||||||
|
DwTimeCaptured uint32
|
||||||
|
DwUser uint64
|
||||||
|
DwFlags uint32
|
||||||
|
DwReserved [4]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getmodulehandlea
|
||||||
|
func getModuleHandle() (syscall.Handle, error) {
|
||||||
|
ret, _, err := getModuleHandleW.Call(uintptr(0))
|
||||||
|
if ret == 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syscall.Handle(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-createwindowexw
|
||||||
|
func createWindow(exStyle uint64, className, windowName string, style uint64, x, y, width, height int64,
|
||||||
|
parent, menu, instance syscall.Handle) (syscall.Handle, error) {
|
||||||
|
ret, _, err := createWindowExW.Call(uintptr(exStyle), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))),
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(windowName))), uintptr(style), uintptr(x), uintptr(y),
|
||||||
|
uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(instance), uintptr(0))
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syscall.Handle(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-destroywindow
|
||||||
|
func destroyWindow(hwnd syscall.Handle) error {
|
||||||
|
ret, _, err := destroyWindowW.Call(uintptr(hwnd))
|
||||||
|
if ret == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw
|
||||||
|
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
|
ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), uintptr(wparam), uintptr(lparam))
|
||||||
|
return uintptr(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-dispatchmessagew
|
||||||
|
func dispatchMessage(msg *msgW) {
|
||||||
|
dispatchMessageW.Call(uintptr(unsafe.Pointer(msg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-translatemessage
|
||||||
|
func translateMessage(msg *msgW) {
|
||||||
|
translateMessageW.Call(uintptr(unsafe.Pointer(msg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmessagew
|
||||||
|
func getMessage(msg *msgW, hwnd syscall.Handle, msgFilterMin, msgFilterMax uint32) (bool, error) {
|
||||||
|
ret, _, err := getMessageW.Call(uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax))
|
||||||
|
if int32(ret) == -1 {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int32(ret) != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage
|
||||||
|
func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
|
ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-postquitmessage
|
||||||
|
func postQuitMessage(exitCode int32) {
|
||||||
|
postQuitMessageW.Call(uintptr(exitCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerclassexw
|
||||||
|
func registerClass(className string, instance syscall.Handle, fn interface{}) error {
|
||||||
|
var wcx wndClassExW
|
||||||
|
wcx.size = uint32(unsafe.Sizeof(wcx))
|
||||||
|
wcx.wndProc = syscall.NewCallback(fn)
|
||||||
|
wcx.instance = instance
|
||||||
|
wcx.className = syscall.StringToUTF16Ptr(className)
|
||||||
|
|
||||||
|
ret, _, err := registerClassExW.Call(uintptr(unsafe.Pointer(&wcx)))
|
||||||
|
if ret == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-unregisterclassw
|
||||||
|
func unregisterClass(className string, instance syscall.Handle) bool {
|
||||||
|
ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance))
|
||||||
|
return ret != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/vfw/nf-vfw-capcreatecapturewindoww
|
||||||
|
func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height int64, parent syscall.Handle, id int64) (syscall.Handle, error) {
|
||||||
|
ret, _, err := capCreateCaptureWindowW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszWindowName))),
|
||||||
|
uintptr(dwStyle), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(id))
|
||||||
|
if ret == 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syscall.Handle(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageLoop function
|
||||||
|
func messageLoop(hwnd syscall.Handle) {
|
||||||
|
for {
|
||||||
|
msg := &msgW{}
|
||||||
|
ok, _ := getMessage(msg, 0, 0, 0)
|
||||||
|
if ok {
|
||||||
|
translateMessage(msg)
|
||||||
|
dispatchMessage(msg)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ type HTML struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewHTML returns new HTML handler.
|
// NewHTML returns new HTML handler.
|
||||||
func NewHTML(bind string, width, height float64, gl bool) *HTML {
|
func NewHTML(bind string, width, height float64, nogl bool) *HTML {
|
||||||
h := &HTML{}
|
h := &HTML{}
|
||||||
|
|
||||||
b := strings.Split(bind, ":")
|
b := strings.Split(bind, ":")
|
||||||
@@ -21,9 +21,9 @@ func NewHTML(bind string, width, height float64, gl bool) *HTML {
|
|||||||
bind = "127.0.0.1" + bind
|
bind = "127.0.0.1" + bind
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl := html
|
tpl := htmlWebGL
|
||||||
if gl {
|
if nogl {
|
||||||
tpl = htmlWebGL
|
tpl = html
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl = strings.Replace(tpl, "{BIND}", bind, -1)
|
tpl = strings.Replace(tpl, "{BIND}", bind, -1)
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gen2brain/cam2ip/camera"
|
"github.com/gen2brain/cam2ip/image"
|
||||||
|
"github.com/gen2brain/cam2ip/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JPEG handler.
|
// JPEG handler.
|
||||||
type JPEG struct {
|
type JPEG struct {
|
||||||
camera *camera.Camera
|
reader reader.ImageReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJPEG returns new JPEG handler.
|
// NewJPEG returns new JPEG handler.
|
||||||
func NewJPEG(camera *camera.Camera) *JPEG {
|
func NewJPEG(reader reader.ImageReader) *JPEG {
|
||||||
return &JPEG{camera}
|
return &JPEG{reader}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles requests on incoming connections.
|
// ServeHTTP handles requests on incoming connections.
|
||||||
@@ -28,15 +29,13 @@ func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Add("Cache-Control", "no-store, no-cache")
|
w.Header().Add("Cache-Control", "no-store, no-cache")
|
||||||
w.Header().Add("Content-Type", "image/jpeg")
|
w.Header().Add("Content-Type", "image/jpeg")
|
||||||
|
|
||||||
img, err := j.camera.Read()
|
img, err := j.reader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("jpeg: read: %v", err)
|
log.Printf("jpeg: read: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := camera.NewEncoder(w)
|
err = image.NewEncoder(w).Encode(img)
|
||||||
|
|
||||||
err = enc.Encode(img)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("jpeg: encode: %v", err)
|
log.Printf("jpeg: encode: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,18 +9,19 @@ import (
|
|||||||
"net/textproto"
|
"net/textproto"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gen2brain/cam2ip/camera"
|
"github.com/gen2brain/cam2ip/image"
|
||||||
|
"github.com/gen2brain/cam2ip/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MJPEG handler.
|
// MJPEG handler.
|
||||||
type MJPEG struct {
|
type MJPEG struct {
|
||||||
camera *camera.Camera
|
reader reader.ImageReader
|
||||||
delay int
|
delay int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMJPEG returns new MJPEG handler.
|
// NewMJPEG returns new MJPEG handler.
|
||||||
func NewMJPEG(camera *camera.Camera, delay int) *MJPEG {
|
func NewMJPEG(reader reader.ImageReader, delay int) *MJPEG {
|
||||||
return &MJPEG{camera, delay}
|
return &MJPEG{reader, delay}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles requests on incoming connections.
|
// ServeHTTP handles requests on incoming connections.
|
||||||
@@ -55,15 +56,13 @@ loop:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := m.camera.Read()
|
img, err := m.reader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("mjpeg: read: %v", err)
|
log.Printf("mjpeg: read: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := camera.NewEncoder(partWriter)
|
err = image.NewEncoder(partWriter).Encode(img)
|
||||||
|
|
||||||
err = enc.Encode(img)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("mjpeg: encode: %v", err)
|
log.Printf("mjpeg: encode: %v", err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// +build !amd64
|
||||||
|
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,34 +10,34 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
|
||||||
"github.com/gen2brain/cam2ip/camera"
|
"github.com/gen2brain/cam2ip/image"
|
||||||
|
"github.com/gen2brain/cam2ip/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socket handler.
|
// Socket handler.
|
||||||
type Socket struct {
|
type Socket struct {
|
||||||
camera *camera.Camera
|
reader reader.ImageReader
|
||||||
delay int
|
delay int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSocket returns new socket handler.
|
// NewSocket returns new socket handler.
|
||||||
func NewSocket(camera *camera.Camera, delay int) websocket.Handler {
|
func NewSocket(reader reader.ImageReader, delay int) websocket.Handler {
|
||||||
s := &Socket{camera, delay}
|
s := &Socket{reader, delay}
|
||||||
return websocket.Handler(s.write)
|
return websocket.Handler(s.write)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write writes images to socket
|
// write writes images to socket
|
||||||
func (s *Socket) write(ws *websocket.Conn) {
|
func (s *Socket) write(ws *websocket.Conn) {
|
||||||
for {
|
for {
|
||||||
img, err := s.camera.Read()
|
img, err := s.reader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("socket: read: %v", err)
|
log.Printf("socket: read: %v", err)
|
||||||
continue
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
w := new(bytes.Buffer)
|
w := new(bytes.Buffer)
|
||||||
enc := camera.NewEncoder(w)
|
|
||||||
|
|
||||||
err = enc.Encode(img)
|
err = image.NewEncoder(w).Encode(img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("socket: encode: %v", err)
|
log.Printf("socket: encode: %v", err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
53
handlers/socket_amd64.go
Normal file
53
handlers/socket_amd64.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
"goost.org/encoding/base64"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cam2ip/image"
|
||||||
|
"github.com/gen2brain/cam2ip/reader"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket handler.
|
||||||
|
type Socket struct {
|
||||||
|
reader reader.ImageReader
|
||||||
|
delay int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSocket returns new socket handler.
|
||||||
|
func NewSocket(reader reader.ImageReader, delay int) websocket.Handler {
|
||||||
|
s := &Socket{reader, delay}
|
||||||
|
return websocket.Handler(s.write)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes images to socket
|
||||||
|
func (s *Socket) write(ws *websocket.Conn) {
|
||||||
|
for {
|
||||||
|
img, err := s.reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("socket: read: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
|
||||||
|
err = image.NewEncoder(w).Encode(img)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("socket: encode: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b64 := base64.StdEncoding.EncodeToString(w.Bytes())
|
||||||
|
|
||||||
|
_, err = ws.Write([]byte(b64))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(s.delay) * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
image/decode.go
Normal file
26
image/decode.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// +build !jpeg
|
||||||
|
|
||||||
|
// Package image.
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
jpeg "github.com/antonini/golibjpegturbo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDecoder returns a new Decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder struct.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes image from JPEG.
|
||||||
|
func (d Decoder) Decode() (image.Image, error) {
|
||||||
|
return jpeg.Decode(d.r)
|
||||||
|
}
|
||||||
25
image/decode_jpeg.go
Normal file
25
image/decode_jpeg.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// +build jpeg
|
||||||
|
|
||||||
|
// Package image.
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDecoder returns a new Decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder struct.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes image from JPEG.
|
||||||
|
func (d Decoder) Decode() (image.Image, error) {
|
||||||
|
return jpeg.Decode(d.r)
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package camera
|
// +build !jpeg
|
||||||
|
|
||||||
|
// Package image.
|
||||||
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
//"image/jpeg"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
jpeg "github.com/kjk/golibjpegturbo"
|
jpeg "github.com/antonini/golibjpegturbo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewEncoder returns a new Encoder.
|
// NewEncoder returns a new Encoder.
|
||||||
30
image/encode_jpeg.go
Normal file
30
image/encode_jpeg.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// +build jpeg
|
||||||
|
|
||||||
|
// Package image.
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoder returns a new Encoder.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder struct.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes image to JPEG.
|
||||||
|
func (e Encoder) Encode(img image.Image) error {
|
||||||
|
err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: 75})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
79
make.bash
79
make.bash
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
CHROOT="/usr/x86_64-pc-linux-gnu-static"
|
CHROOT="/usr/x86_64-pc-linux-gnu-static"
|
||||||
MINGW="/usr/i686-w64-mingw32"
|
MINGW="/usr/i686-w64-mingw32"
|
||||||
|
MINGW64="/usr/x86_64-w64-mingw32"
|
||||||
RPI="/usr/armv6j-hardfloat-linux-gnueabi"
|
RPI="/usr/armv6j-hardfloat-linux-gnueabi"
|
||||||
ANDROID="/opt/android-toolchain-arm7"
|
RPI3="/usr/armv7a-hardfloat-linux-gnueabi"
|
||||||
|
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
|
|
||||||
@@ -11,8 +12,15 @@ LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
|||||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
CGO_CFLAGS="-I$CHROOT/usr/include" \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags cv2 -o build/cam2ip.linux.amd64.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||||
|
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
||||||
|
CGO_CFLAGS="-I$CHROOT/usr/include" \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||||
@@ -20,7 +28,31 @@ PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
|||||||
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||||
CGO_CFLAGS="-I$MINGW/usr/include" \
|
CGO_CFLAGS="-I$MINGW/usr/include" \
|
||||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -x -o build/cam2ip.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -tags cv2 -o build/cam2ip.386.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$MINGW/usr/include" \
|
||||||
|
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||||
|
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -o build/cam2ip.386.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$MINGW64/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$MINGW64/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$MINGW64/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$MINGW64/usr/include" \
|
||||||
|
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" \
|
||||||
|
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -tags cv2 -o build/cam2ip.amd64.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$MINGW64/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$MINGW64/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$MINGW64/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$MINGW64/usr/include" \
|
||||||
|
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" \
|
||||||
|
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o build/cam2ip.amd64.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||||
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||||
@@ -28,13 +60,36 @@ PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
|||||||
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||||
CGO_CFLAGS="-I$RPI/usr/include" \
|
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||||
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -v -x -o build/cam2ip.linux.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
PATH="$PATH:$ANDROID/bin" \
|
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||||
PKG_CONFIG="$ANDROID/bin/arm-linux-androideabi-pkg-config" \
|
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||||
PKG_CONFIG_PATH="$ANDROID/lib/pkgconfig" \
|
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
||||||
PKG_CONFIG_LIBDIR="$ANDROID/lib/pkgconfig" \
|
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||||
CGO_LDFLAGS="-L$ANDROID/lib" \
|
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||||
CGO_CFLAGS="-I$ANDROID/include" \
|
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||||
CC="arm-linux-androideabi-gcc" CXX="arm-linux-androideabi-g++" \
|
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o build/cam2ip.linux.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
CGO_ENABLED=1 GOOS=android GOARCH=arm go build -v -x -o build/cam2ip.android.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
|
||||||
|
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$RPI/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$RPI/usr/include" \
|
||||||
|
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -tags jpeg -o build/cam2ip.linux.arm.nocgo -ldflags "-s -w" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$RPI3/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$RPI3/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$RPI3/usr/include" \
|
||||||
|
CC="armv7a-hardfloat-linux-gnueabi-gcc" CXX="armv7a-hardfloat-linux-gnueabi-g++" \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm7.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
|
|
||||||
|
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$RPI3/usr/lib/pkgconfig" \
|
||||||
|
CGO_LDFLAGS="-L$RPI3/usr/lib" \
|
||||||
|
CGO_CFLAGS="-I$RPI3/usr/include" \
|
||||||
|
CC="armv7a-hardfloat-linux-gnueabi-gcc" CXX="armv7a-hardfloat-linux-gnueabi-g++" \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o build/cam2ip.linux.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
|
||||||
|
|||||||
15
reader/reader.go
Normal file
15
reader/reader.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Package reader.
|
||||||
|
package reader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageReader interface
|
||||||
|
type ImageReader interface {
|
||||||
|
// Read reads next frame from camera/video and returns image.
|
||||||
|
Read() (img image.Image, err error)
|
||||||
|
|
||||||
|
// Close closes camera/video.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/abbot/go-http-auth"
|
"github.com/abbot/go-http-auth"
|
||||||
|
|
||||||
"github.com/gen2brain/cam2ip/camera"
|
|
||||||
"github.com/gen2brain/cam2ip/handlers"
|
"github.com/gen2brain/cam2ip/handlers"
|
||||||
|
"github.com/gen2brain/cam2ip/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server struct.
|
// Server struct.
|
||||||
@@ -20,13 +20,19 @@ type Server struct {
|
|||||||
Bind string
|
Bind string
|
||||||
Htpasswd string
|
Htpasswd string
|
||||||
|
|
||||||
Index int
|
Index int
|
||||||
Delay int
|
Delay int
|
||||||
|
|
||||||
FrameWidth float64
|
FrameWidth float64
|
||||||
FrameHeight float64
|
FrameHeight float64
|
||||||
WebGL bool
|
|
||||||
|
|
||||||
Camera *camera.Camera
|
Rotate int
|
||||||
|
|
||||||
|
NoWebGL bool
|
||||||
|
|
||||||
|
FileName string
|
||||||
|
|
||||||
|
Reader reader.ImageReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns new Server.
|
// NewServer returns new Server.
|
||||||
@@ -43,11 +49,11 @@ func (s *Server) ListenAndServe() error {
|
|||||||
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
|
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Bind, s.FrameWidth, s.FrameHeight, s.WebGL), basic))
|
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Bind, s.FrameWidth, s.FrameHeight, s.NoWebGL), basic))
|
||||||
http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Camera), basic))
|
http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader), basic))
|
||||||
http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Camera, s.Delay), basic))
|
http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay), basic))
|
||||||
|
|
||||||
http.Handle("/socket", handlers.NewSocket(s.Camera, s.Delay))
|
http.Handle("/socket", handlers.NewSocket(s.Reader, s.Delay))
|
||||||
|
|
||||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
80
video/video_cv2.go
Normal file
80
video/video_cv2.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// +build cv2,!cv3
|
||||||
|
|
||||||
|
// Package video.
|
||||||
|
package video
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/lazywei/go-opencv/opencv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Filename string
|
||||||
|
Rotate int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video represents video.
|
||||||
|
type Video struct {
|
||||||
|
opts Options
|
||||||
|
video *opencv.Capture
|
||||||
|
frame *opencv.IplImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Video for given path.
|
||||||
|
func New(opts Options) (video *Video, err error) {
|
||||||
|
video = &Video{}
|
||||||
|
video.opts = opts
|
||||||
|
|
||||||
|
video.video = opencv.NewFileCapture(opts.Filename)
|
||||||
|
if video.video == nil {
|
||||||
|
err = fmt.Errorf("video: can not open video %s", opts.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from video and returns image.
|
||||||
|
func (v *Video) Read() (img image.Image, err error) {
|
||||||
|
if v.video.GrabFrame() {
|
||||||
|
v.frame = v.video.RetrieveFrame(1)
|
||||||
|
if v.frame == nil {
|
||||||
|
err = fmt.Errorf("video: can not retrieve frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img = v.frame.ToImage()
|
||||||
|
if v.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("video: can not grab frame")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes video.
|
||||||
|
func (v *Video) Close() (err error) {
|
||||||
|
if v.video == nil {
|
||||||
|
err = fmt.Errorf("video: video is not opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.frame.Release()
|
||||||
|
v.video.Release()
|
||||||
|
v.video = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
89
video/video_cv3.go
Normal file
89
video/video_cv3.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// +build cv3,!cv2
|
||||||
|
|
||||||
|
// Package video.
|
||||||
|
package video
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"gocv.io/x/gocv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options.
|
||||||
|
type Options struct {
|
||||||
|
Filename string
|
||||||
|
Rotate int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video represents video.
|
||||||
|
type Video struct {
|
||||||
|
opts Options
|
||||||
|
video *gocv.VideoCapture
|
||||||
|
frame *gocv.Mat
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new Video for given path.
|
||||||
|
func New(opts Options) (video *Video, err error) {
|
||||||
|
video = &Video{}
|
||||||
|
video.opts = opts
|
||||||
|
|
||||||
|
mat := gocv.NewMat()
|
||||||
|
video.frame = &mat
|
||||||
|
|
||||||
|
video.video, err = gocv.VideoCaptureFile(opts.Filename)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("video: can not open video %s: %s", opts.Filename, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads next frame from video and returns image.
|
||||||
|
func (v *Video) Read() (img image.Image, err error) {
|
||||||
|
ok := v.video.Read(v.frame)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("video: can not grab frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.frame == nil {
|
||||||
|
err = fmt.Errorf("video: can not retrieve frame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img, e := v.frame.ToImage()
|
||||||
|
if e != nil {
|
||||||
|
err = fmt.Errorf("video: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.opts.Rotate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
img = imaging.Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
img = imaging.Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
img = imaging.Rotate270(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes video.
|
||||||
|
func (v *Video) Close() (err error) {
|
||||||
|
if v.video == nil {
|
||||||
|
err = fmt.Errorf("video: video is not opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v.frame.Close()
|
||||||
|
err = v.video.Close()
|
||||||
|
v.video = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
62
video/video_test.go
Normal file
62
video/video_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package video
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/jpeg"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVideo(t *testing.T) {
|
||||||
|
video, err := New(video.Options{"test.mp4", 0})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer video.Close()
|
||||||
|
|
||||||
|
tmpdir, err := ioutil.TempDir(os.TempDir(), "cam2ip")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var n int = 10
|
||||||
|
|
||||||
|
timeout := time.After(time.Duration(n) * time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
//fmt.Printf("Fps: %d\n", i/n)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
img, err := video.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(tmpdir, fmt.Sprintf("%03d.jpg", i)))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 75})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user