From 359d3b5d724af6eae58ce455b83196fc00432fbb Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 31 Mar 2018 21:39:31 +1100 Subject: [PATCH 001/123] Changes to allow framegrabbing. --- webcam.go | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/webcam.go b/webcam.go index 62e7111..d325fe4 100644 --- a/webcam.go +++ b/webcam.go @@ -13,9 +13,12 @@ import ( // Webcam object type Webcam struct { fd uintptr + bufcount uint32 buffers [][]byte } +const maxBufferCount = 512 + // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -44,6 +47,7 @@ func Open(path string) (*Webcam, error) { w := new(Webcam) w.fd = uintptr(fd) + w.bufcount = 256 return w, nil } @@ -113,22 +117,29 @@ func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelForma } } +// Set the number of frames to be buffered. +func (w *Webcam) SetBufferCount(count uint32) error { + if count < 2 || count > maxBufferCount { + return errors.New("Illegal buffer count") + } + w.bufcount = count + return nil +} + // Start streaming process func (w *Webcam) StartStreaming() error { - var buf_count uint32 = 256 - - err := mmapRequestBuffers(w.fd, &buf_count) + err := mmapRequestBuffers(w.fd, &w.bufcount) if err != nil { return errors.New("Failed to map request buffers: " + string(err.Error())) } - if buf_count < 2 { + if w.bufcount < 2 { return errors.New("Insufficient buffer memory") } - w.buffers = make([][]byte, buf_count, buf_count) + w.buffers = make([][]byte, w.bufcount, w.bufcount) for index, _ := range w.buffers { var length uint32 @@ -164,21 +175,34 @@ func (w *Webcam) StartStreaming() error { // If frame cannot be read at the moment // function will return empty slice func (w *Webcam) ReadFrame() ([]byte, error) { + result, index, err := w.GetFrame() + if err == nil { + w.ReleaseFrame(index) + } + return result, err +} + +// Get a single frame from the webcam and return the frame and +// the buffer index. To return the buffer, ReleaseFrame must be called. +// If frame cannot be read at the moment +// function will return empty slice +func (w *Webcam) GetFrame() ([]byte, uint32, error) { var index uint32 var length uint32 err := mmapDequeueBuffer(w.fd, &index, &length) if err != nil { - return nil, err + return nil, 0, err } - result := w.buffers[int(index)][:length] + return w.buffers[int(index)][:length], index, nil - err = mmapEnqueueBuffer(w.fd, index) - - return result, err +} +// Release the frame buffer that was obtained via GetFrame +func (w *Webcam) ReleaseFrame(index uint32) error { + return mmapEnqueueBuffer(w.fd, index) } // Wait until frame could be read From 16a32b0369e2cd647d19afd264797f7967351b4c Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sun, 1 Apr 2018 13:59:58 +1000 Subject: [PATCH 002/123] Change import --- v4l2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index 5ab0a1e..357a6be 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) From a2cf408d71abca5ea4582c7f8bf33a9f3c967438 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 4 Apr 2018 12:01:11 +1000 Subject: [PATCH 003/123] Restore import path. --- v4l2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index 357a6be..5ab0a1e 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From 89be1a0d9477b3b3cdeb0e068c1565d9a4be6267 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 5 Apr 2018 11:08:43 +1000 Subject: [PATCH 004/123] Fixed formatting, removed check on buffer limit. --- webcam.go | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/webcam.go b/webcam.go index d325fe4..b628382 100644 --- a/webcam.go +++ b/webcam.go @@ -12,13 +12,11 @@ import ( // Webcam object type Webcam struct { - fd uintptr - bufcount uint32 - buffers [][]byte + fd uintptr + bufcount uint32 + buffers [][]byte } -const maxBufferCount = 512 - // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -47,7 +45,7 @@ func Open(path string) (*Webcam, error) { w := new(Webcam) w.fd = uintptr(fd) - w.bufcount = 256 + w.bufcount = 256 return w, nil } @@ -118,12 +116,8 @@ func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelForma } // Set the number of frames to be buffered. -func (w *Webcam) SetBufferCount(count uint32) error { - if count < 2 || count > maxBufferCount { - return errors.New("Illegal buffer count") - } - w.bufcount = count - return nil +func (w *Webcam) SetBufferCount(count uint32) { + w.bufcount = count } // Start streaming process @@ -135,10 +129,6 @@ func (w *Webcam) StartStreaming() error { return errors.New("Failed to map request buffers: " + string(err.Error())) } - if w.bufcount < 2 { - return errors.New("Insufficient buffer memory") - } - w.buffers = make([][]byte, w.bufcount, w.bufcount) for index, _ := range w.buffers { var length uint32 @@ -175,11 +165,11 @@ func (w *Webcam) StartStreaming() error { // If frame cannot be read at the moment // function will return empty slice func (w *Webcam) ReadFrame() ([]byte, error) { - result, index, err := w.GetFrame() - if err == nil { - w.ReleaseFrame(index) - } - return result, err + result, index, err := w.GetFrame() + if err == nil { + w.ReleaseFrame(index) + } + return result, err } // Get a single frame from the webcam and return the frame and From da3d4aaea89dc6c4389069429325884cb5da3acf Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 5 Apr 2018 11:28:01 +1000 Subject: [PATCH 005/123] Track whether streaming or not. --- README.md | 8 ++++++++ webcam.go | 29 ++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2696246..fe9fda9 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ for { } ``` For more detailed example see [examples folder](https://github.com/blackjack/webcam/tree/master/examples) +The number of frame buffers used may be set as: +```go +// If already streaming, stop streaming. +if streaming_on { + cam.StopStreaming() +} +err = cam.SetBufferCount(64) +``` ## Roadmap diff --git a/webcam.go b/webcam.go index b628382..00aabde 100644 --- a/webcam.go +++ b/webcam.go @@ -12,9 +12,10 @@ import ( // Webcam object type Webcam struct { - fd uintptr - bufcount uint32 - buffers [][]byte + fd uintptr + bufcount uint32 + buffers [][]byte + streaming bool } // Open a webcam with a given path @@ -116,12 +117,20 @@ func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelForma } // Set the number of frames to be buffered. -func (w *Webcam) SetBufferCount(count uint32) { +// Not allowed if streaming is already on. +func (w *Webcam) SetBufferCount(count uint32) error { + if w.streaming { + return errors.New("Cannot set buffer count when streaming") + } w.bufcount = count + return nil } // Start streaming process func (w *Webcam) StartStreaming() error { + if w.streaming { + return errors.New("Already streaming") + } err := mmapRequestBuffers(w.fd, &w.bufcount) @@ -157,6 +166,7 @@ func (w *Webcam) StartStreaming() error { if err != nil { return errors.New("Failed to start streaming: " + string(err.Error())) } + w.streaming = true return nil } @@ -210,6 +220,10 @@ func (w *Webcam) WaitForFrame(timeout uint32) error { } func (w *Webcam) StopStreaming() error { + if !w.streaming { + return errors.New("Request to stop streaming when not streaming") + } + w.streaming = false for _, buffer := range w.buffers { err := mmapReleaseBuffer(buffer) if err != nil { @@ -222,11 +236,8 @@ func (w *Webcam) StopStreaming() error { // Close the device func (w *Webcam) Close() error { - for _, buffer := range w.buffers { - err := mmapReleaseBuffer(buffer) - if err != nil { - return err - } + if w.streaming { + w.StopStreaming() } err := unix.Close(int(w.fd)) From 44bcc77cb576fc3f1e3ebf96b802054020d99437 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sun, 23 Dec 2018 14:05:08 +1100 Subject: [PATCH 006/123] Move to new directory. --- camera.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ frame.go | 68 +++++++++++++++++++++++++ server.go | 44 ++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 camera.go create mode 100644 frame.go create mode 100644 server.go diff --git a/camera.go b/camera.go new file mode 100644 index 0000000..3fd0177 --- /dev/null +++ b/camera.go @@ -0,0 +1,150 @@ +package main + +import ( + "github.com/aamcrae/webcam" + "fmt" + "log" +) + +type snapshot struct { + frame []byte + index uint32 +} + +type Camera struct { + cam *webcam.Webcam + Width int + Height int + Format string + Timeout uint32 + newFrame func(int, int, []byte, func()) (Frame, error) + stop chan struct{} + stream chan snapshot +} + +func OpenCamera(name string) (*Camera, error) { + c, err := webcam.Open(name) + if err != nil { + return nil, err + } + camera := &Camera{cam:c, Timeout:5} + camera.stop = make(chan struct{}, 1) + camera.stream = make(chan snapshot, 0) + return camera, nil +} + +func (c *Camera) Close() { + c.stop <- struct{}{} + // Flush any remaining frames. + for f := range c.stream { + c.cam.ReleaseFrame(f.index) + } + c.cam.StopStreaming() + c.cam.Close() +} + +func (c *Camera) Init(format string, resolution string) error { + // Get the supported formats and their descriptions. + format_desc := c.cam.GetSupportedFormats() + var pixelFormat webcam.PixelFormat + var found bool + for k, v := range format_desc { + if v == format { + found = true + pixelFormat = k + break + } + } + if !found { + return fmt.Errorf("Camera does not support this format: %s", format) + } + var err error + if c.newFrame, err = GetFramer(format); err != nil { + return err + } + + // Build a map of resolution names from the description. + sizeMap := make(map[string]webcam.FrameSize) + for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { + sizeMap[value.GetString()] = value + } + + sz, ok := sizeMap[resolution] + if !ok { + return fmt.Errorf("Unsupported resoluton: %s", resolution) + } + + _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) + + if err != nil { + return err + } + c.Width = int(w) + c.Height = int(h) + + c.cam.SetBufferCount(16) + c.cam.SetAutoWhiteBalance(true) + if err := c.cam.StartStreaming(); err != nil { + return err + } + go c.capture() + return nil +} + +func (c *Camera) GetFrame() (Frame, error) { + snap, ok := <-c.stream + if !ok { + return nil, fmt.Errorf("No frame received") + } + return c.newFrame(c.Width, c.Height, snap.frame, func() { + c.cam.ReleaseFrame(snap.index) + }) +} + +// capture continually reads frames and either discards them or +// sends them to a channel that is ready to receive them. +func (c *Camera) capture() { + for { + err := c.cam.WaitForFrame(c.Timeout) + + switch err.(type) { + case nil: + case *webcam.Timeout: + continue + default: + log.Fatal(err) + } + + frame, index, err := c.cam.GetFrame() + if err != nil { + log.Fatal(err) + } + select { + // Only executed if stream is ready to receive. + case c.stream <- snapshot{frame, index}: + case <-c.stop: + // Finish up. + c.cam.ReleaseFrame(index) + close(c.stream) + return + default: + c.cam.ReleaseFrame(index) + } + } +} + +// Return map of supported formats and resolutions. +func (c *Camera) Query() map[string][]string { + m := map[string][]string{} + formats := c.cam.GetSupportedFormats() + for f, fs := range formats { + r := []string{} + for _, value := range c.cam.GetSupportedFrameSizes(f) { + if value.StepWidth == 0 && value.StepHeight == 0 { + r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) + } + } + m[fs] = r + } + return m +} diff --git a/frame.go b/frame.go new file mode 100644 index 0000000..38b9ea7 --- /dev/null +++ b/frame.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "image" + "image/color" +) + +type Frame interface { + image.Image + Release() +} + +type FrameYUYV422 struct { + model color.Model + b image.Rectangle + frame []byte + release func() +} + +var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error) { + "YUYV 4:2:2": newFrameYUYV422, +} + +// Return a function that wraps the frame for this format. +func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { + if f, ok := frameHandlers[format]; ok { + return f, nil + } + return nil, fmt.Errorf("No handler for format '%s'", format) +} + +// Wrap a raw frame in a Frame so that it can be used as an image. +func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { + expLen := 2 * x * y + if len(f) != expLen { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + } + fr := &FrameYUYV422{model: color.YCbCrModel, b:image.Rect(0, 0, x, y), frame:f, release:rel} + return fr, nil +} + +func (f *FrameYUYV422) ColorModel() color.Model { + return f.model +} + +func (f *FrameYUYV422) Bounds() image.Rectangle { + return f.b +} + +func (f *FrameYUYV422) At(x, y int) color.Color { + index := f.b.Max.X * y * 2 + (x &^ 1) * 2 + if x & 1 == 0 { + return color.YCbCr{f.frame[index], f.frame[index + 1], f.frame[index + 3]} + } else { + return color.YCbCr{f.frame[index + 2], f.frame[index + 1], f.frame[index + 3]} + } +} + +// Done with frame, release back to camera (if required) +func (f* FrameYUYV422) Release() { + if f.release != nil { + f.release() + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..1e8cd27 --- /dev/null +++ b/server.go @@ -0,0 +1,44 @@ +package main + +import ( + "flag" + "fmt" + "image/png" + "log" + "os" +) + +var device = flag.String("input", "/dev/video0", "Input video device") +var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") +var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") + +func init() { + flag.Parse() +} + +func main() { + cam, err := OpenCamera(*device) + if err != nil { + log.Fatalf("%s: %v", *device, err) + } + defer cam.Close() + if err := cam.Init(*format, *resolution); err != nil { + log.Fatalf("Init failed: %v", err) + } + frame, err := cam.GetFrame() + if err != nil { + log.Fatalf("Getframe: %v", err) + } + fname := "test.png" + of, err := os.Create(fname) + if err != nil { + log.Fatalf("Failed to create %s: %v", fname, err) + } + if err := png.Encode(of, frame); err != nil { + fmt.Printf("Error writing %s: %v\n", fname, err) + } else { + fmt.Printf("Wrote %s successfully\n", fname) + } + frame.Release() + of.Close() +} From cf9178b9dfd5f01dc157a3566a4dd7acc70ab314 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sun, 23 Dec 2018 14:46:18 +1100 Subject: [PATCH 007/123] Added http server. --- .gitignore | 1 + server.go | 34 +++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..765aad7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +imageserver diff --git a/server.go b/server.go index 1e8cd27..681e431 100644 --- a/server.go +++ b/server.go @@ -5,12 +5,14 @@ import ( "fmt" "image/png" "log" - "os" + "net/http" ) +var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") +var verbose = flag.Bool("v", false, "Log more information") func init() { flag.Parse() @@ -25,20 +27,30 @@ func main() { if err := cam.Init(*format, *resolution); err != nil { log.Fatalf("Init failed: %v", err) } + http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ + readImage(cam, w, r) + })) + url := fmt.Sprintf(":%d", *port) + if *verbose { + log.Printf("Starting server on %s", url) + } + s := &http.Server{Addr: url} + log.Fatal(s.ListenAndServe()) +} + +func readImage(cam *Camera, w http.ResponseWriter, r *http.Request) { + if *verbose { + log.Printf("URL request: %v", r.URL) + } frame, err := cam.GetFrame() if err != nil { log.Fatalf("Getframe: %v", err) } - fname := "test.png" - of, err := os.Create(fname) - if err != nil { - log.Fatalf("Failed to create %s: %v", fname, err) - } - if err := png.Encode(of, frame); err != nil { - fmt.Printf("Error writing %s: %v\n", fname, err) - } else { - fmt.Printf("Wrote %s successfully\n", fname) + w.Header().Set("Content-Type", "image/png") + if err := png.Encode(w, frame); err != nil { + log.Printf("Error writing image: %v\n", err) + } else if *verbose { + log.Printf("Wrote image successfully\n") } frame.Release() - of.Close() } From e64b7b48df8ffd858699bf110afde412dd37deec Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 24 Dec 2018 15:03:36 +1100 Subject: [PATCH 008/123] Added set/get controls. --- v4l2.go | 118 ++++++++++++++++++++++++++++++++++++++++++++---------- webcam.go | 30 ++++++++++++++ 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/v4l2.go b/v4l2.go index 5ab0a1e..07bc224 100644 --- a/v4l2.go +++ b/v4l2.go @@ -9,6 +9,20 @@ import ( "golang.org/x/sys/unix" ) +type controlType int +const ( + c_int controlType = iota + c_bool + c_menu +) + +type control struct { + id uint32 + c_type controlType + min int64 + max int64 +} + const ( V4L2_CAP_VIDEO_CAPTURE uint32 = 0x00000001 V4L2_CAP_STREAMING uint32 = 0x04000000 @@ -29,16 +43,45 @@ const ( V4L2_CID_PRIVATE_BASE uint32 = 0x08000000 ) +const ( + V4L2_CTRL_TYPE_INTEGER uint32 = 1 + V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 + V4L2_CTRL_TYPE_MENU uint32 = 3 + V4L2_CTRL_TYPE_BUTTON uint32 = 4 + V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 + V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 + V4L2_CTRL_TYPE_STRING uint32 = 7 + V4L2_CTRL_TYPE_BITMASK uint32 = 8 + V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 + + V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 + V4L2_CTRL_TYPE_U8 uint32 = 0x0100 + V4L2_CTRL_TYPE_U16 uint32 = 0x0101 + V4L2_CTRL_TYPE_U32 uint32 = 0x0102 +) + + +const ( + V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 + V4L2_CTRL_FLAG_NEXT_COMPOUND uint32 = 0x40000000 +) + +const ( + V4L2_CTRL_MAX_DIMS uint32 = 4 +) + var ( - VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) - VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) - VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) - VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) - VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) - VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) + VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) + VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) + VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) + VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) + VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) + VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERY_EXT_CTRL = ioctl.IoRW(uintptr('V'), 103, unsafe.Sizeof(v4l2_query_ext_ctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -164,6 +207,22 @@ type v4l2_control struct { value int32 } +type v4l2_query_ext_ctrl struct { + id uint32 + _type uint32 + name [32]uint8 + minimum int64 + maximum int64 + step uint64 + default_value int64 + flags uint32 + elem_size uint32 + elems uint32 + nr_of_dims uint32 + dims uint32 + reserved [32]uint32 +} + func checkCapabilities(fd uintptr) (supportsVideoCapture bool, supportsVideoStreaming bool, err error) { caps := &v4l2_capability{} @@ -418,6 +477,13 @@ func waitForFrame(fd uintptr, timeout uint32) (count int, err error) { } +func getControl(fd uintptr, id uint32) (int32, error) { + ctrl := &v4l2_control{} + ctrl.id = id + err := ioctl.Ioctl(fd, VIDIOC_G_CTRL, uintptr(unsafe.Pointer(ctrl))) + return ctrl.value, err +} + func setControl(fd uintptr, id uint32, val int32) error { ctrl := &v4l2_control{} ctrl.id = id @@ -425,21 +491,31 @@ func setControl(fd uintptr, id uint32, val int32) error { return ioctl.Ioctl(fd, VIDIOC_S_CTRL, uintptr(unsafe.Pointer(ctrl))) } -func getControls(fd uintptr) map[uint32]string { - query := &v4l2_queryctrl{} - var controls map[uint32]string +func queryControls(fd uintptr) map[string]control { + var controls map[string]control = make(map[string]control) var err error - for query.id = V4L2_CID_BASE; err == nil; query.id++ { - err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) - if err == nil { - controls[query.id] = CToGoString(query.name[:]) - } - } - err = nil - for query.id = V4L2_CID_PRIVATE_BASE; err == nil; query.id++ { + id := V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND + for err == nil { + id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND + query := &v4l2_query_ext_ctrl{} + query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) if err == nil { - controls[query.id] = CToGoString(query.name[:]) + id = query.id + var c control + switch query._type { + default: continue + case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: + c.c_type = c_int + case V4L2_CTRL_TYPE_BOOLEAN: + c.c_type = c_bool + case V4L2_CTRL_TYPE_MENU: + c.c_type = c_menu + } + c.id = id + c.min = query.minimum + c.max = query.maximum + controls[CToGoString(query.name[:])] = c } } return controls diff --git a/webcam.go b/webcam.go index 00aabde..ec75af2 100644 --- a/webcam.go +++ b/webcam.go @@ -16,6 +16,7 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool + controls map[string]control } // Open a webcam with a given path @@ -126,6 +127,35 @@ func (w *Webcam) SetBufferCount(count uint32) error { return nil } +// Get the value of a control. +func (w *Webcam) GetControl(name string) (int32, error) { + c, err := w.lookupControl(name) + if err != nil { + return 0, err + } + return getControl(w.fd, c.id) +} + +// Set a control. +func (w *Webcam) SetControl(name string, value int32) error { + c, err := w.lookupControl(name) + if err != nil { + return err + } + return setControl(w.fd, c.id, value) +} + +func (w *Webcam) lookupControl(name string) (control, error) { + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + c, ok := w.controls[name] + if !ok { + return control{}, errors.New("Unknown control: " + name) + } + return c, nil +} + // Start streaming process func (w *Webcam) StartStreaming() error { if w.streaming { From 8d3a8081f8e6a38c97b646ae0aed01e5953158a4 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 24 Dec 2018 16:50:34 +1100 Subject: [PATCH 009/123] Added set/get controls. --- ioctl/ioctl.go | 2 ++ v4l2.go | 45 +++++++++++++++------------------------------ 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 3ce8df6..76a2c9c 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -21,6 +21,8 @@ const ( typeShift = numberShift + numberBits sizeShift = typeShift + typeBits directionShift = sizeShift + sizeBits + + ErrEINVAL = unix.EINVAL ) func ioc(dir, t, nr, size uintptr) uintptr { diff --git a/v4l2.go b/v4l2.go index 07bc224..cef0472 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,9 +3,10 @@ package webcam import ( "bytes" "encoding/binary" + "strings" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) @@ -19,8 +20,8 @@ const ( type control struct { id uint32 c_type controlType - min int64 - max int64 + min int32 + max int32 } const ( @@ -62,12 +63,8 @@ const ( const ( + V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 - V4L2_CTRL_FLAG_NEXT_COMPOUND uint32 = 0x40000000 -) - -const ( - V4L2_CTRL_MAX_DIMS uint32 = 4 ) var ( @@ -81,7 +78,6 @@ var ( VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) - VIDIOC_QUERY_EXT_CTRL = ioctl.IoRW(uintptr('V'), 103, unsafe.Sizeof(v4l2_query_ext_ctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -207,22 +203,6 @@ type v4l2_control struct { value int32 } -type v4l2_query_ext_ctrl struct { - id uint32 - _type uint32 - name [32]uint8 - minimum int64 - maximum int64 - step uint64 - default_value int64 - flags uint32 - elem_size uint32 - elems uint32 - nr_of_dims uint32 - dims uint32 - reserved [32]uint32 -} - func checkCapabilities(fd uintptr) (supportsVideoCapture bool, supportsVideoStreaming bool, err error) { caps := &v4l2_capability{} @@ -494,14 +474,17 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - id := V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND - for err == nil { - id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND - query := &v4l2_query_ext_ctrl{} + id := V4L2_CID_BASE + for err != ioctl.ErrEINVAL { + id |= V4L2_CTRL_FLAG_NEXT_CTRL + query := &v4l2_queryctrl{} query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) if err == nil { id = query.id + if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { + continue + } var c control switch query._type { default: continue @@ -515,7 +498,9 @@ func queryControls(fd uintptr) map[string]control { c.id = id c.min = query.minimum c.max = query.maximum - controls[CToGoString(query.name[:])] = c + // Normalise name (' ' -> '_', make lower case). + n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) + controls[n] = c } } return controls From 4f2ad327889a04fa9cc03851fe07ee104e5f41f0 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 24 Dec 2018 16:51:52 +1100 Subject: [PATCH 010/123] Added set/get controls. --- camera.go | 10 ++++++++++ server.go | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/camera.go b/camera.go index 3fd0177..6c2a330 100644 --- a/camera.go +++ b/camera.go @@ -148,3 +148,13 @@ func (c *Camera) Query() map[string][]string { } return m } + +// Get control value. +func (c *Camera) GetControl(name string) (int32, error) { + return c.cam.GetControl(name) +} + +// Set control value. +func (c *Camera) SetControl(name string, value int32) error { + return c.cam.SetControl(name, value) +} diff --git a/server.go b/server.go index 681e431..b8da8d3 100644 --- a/server.go +++ b/server.go @@ -6,12 +6,16 @@ import ( "image/png" "log" "net/http" + "strconv" + "strings" ) var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") +var controls = flag.String("controls", "focus=170,power_line_frequency=1", + "Control parameters for camera") var verbose = flag.Bool("v", false, "Log more information") func init() { @@ -27,6 +31,24 @@ func main() { if err := cam.Init(*format, *resolution); err != nil { log.Fatalf("Init failed: %v", err) } + // Initialise camera controls. + for _, control := range strings.Split(*controls, ",") { + // If no parameter, assume bool and set to true. + s := strings.Split(control, "=") + if len(s) == 1 { + s = append(s, "true") + } + if len(s) != 2 { + log.Fatalf("Bad control option: %s", control) + } + val, err := strconv.Atoi(s[1]) + if err != nil { + log.Fatalf("Bad control value: %s (%v)", control, err) + } + if err = cam.SetControl(s[0], int32(val)); err != nil { + log.Fatalf("SetControl error: %s (%v)", control, err) + } + } http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ readImage(cam, w, r) })) From 69a270b27882802928dbe7b0f4e5e82dafa9aabb Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 1 Jan 2019 09:56:03 +1100 Subject: [PATCH 011/123] Changed focus default --- server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index b8da8d3..d805d49 100644 --- a/server.go +++ b/server.go @@ -3,7 +3,7 @@ package main import ( "flag" "fmt" - "image/png" + "image/jpeg" "log" "net/http" "strconv" @@ -68,8 +68,8 @@ func readImage(cam *Camera, w http.ResponseWriter, r *http.Request) { if err != nil { log.Fatalf("Getframe: %v", err) } - w.Header().Set("Content-Type", "image/png") - if err := png.Encode(w, frame); err != nil { + w.Header().Set("Content-Type", "image/jpeg") + if err := jpeg.Encode(w, frame, nil); err != nil { log.Printf("Error writing image: %v\n", err) } else if *verbose { log.Printf("Wrote image successfully\n") From 00f9285d122e7ae14d8951274388f7048464207b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 1 Jan 2019 15:17:52 +1100 Subject: [PATCH 012/123] Initial SMA polling. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index b8da8d3..e40cecc 100644 --- a/server.go +++ b/server.go @@ -14,7 +14,7 @@ var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") -var controls = flag.String("controls", "focus=170,power_line_frequency=1", +var controls = flag.String("controls", "focus=190,power_line_frequency=1", "Control parameters for camera") var verbose = flag.Bool("v", false, "Log more information") From 4f4c6dd69763d39bb8744fa7df8887c6cb6d67f9 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 1 Jan 2019 23:08:18 +1100 Subject: [PATCH 013/123] Added systemd service file for imageserver --- imageserver.service | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 imageserver.service diff --git a/imageserver.service b/imageserver.service new file mode 100644 index 0000000..a178d95 --- /dev/null +++ b/imageserver.service @@ -0,0 +1,16 @@ +[Unit] +Description=MeterMan image server +After=network.target + +[Service] +User=root +Type=forking +TimeoutStopSec=10 +ExecStart=/usr/local/bin/imageserver + +Restart=on-failure +RestartSec=15s +SuccessExitStatus=SIGKILL + +[Install] +WantedBy=default.target From 4c85217879239bac3561a6e861fe86ab0164b4b9 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 1 Jan 2019 23:25:47 +1100 Subject: [PATCH 014/123] Add systemd service file for imageserver --- imageserver.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imageserver.service b/imageserver.service index a178d95..1df80de 100644 --- a/imageserver.service +++ b/imageserver.service @@ -4,7 +4,7 @@ After=network.target [Service] User=root -Type=forking +Type=simple TimeoutStopSec=10 ExecStart=/usr/local/bin/imageserver From 81bebf6fb71de539699dcaf4cfbefbd00d614502 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 3 Jan 2019 08:30:25 +1100 Subject: [PATCH 015/123] Added delay to start of imageserver/ --- server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.go b/server.go index d12ae28..7a2e196 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" "strings" + "time" ) var port = flag.Int("port", 8080, "Web server port number") @@ -16,6 +17,7 @@ var resolution = flag.String("resolution", "800x600", "Selected resolution of ca var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") var controls = flag.String("controls", "focus=190,power_line_frequency=1", "Control parameters for camera") +var startDelay = flag.Int("delay", 5, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") func init() { @@ -23,6 +25,9 @@ func init() { } func main() { + if *startDelay != 0 { + time.Sleep(time.Duration(*startDelay) * time.Second) + } cam, err := OpenCamera(*device) if err != nil { log.Fatalf("%s: %v", *device, err) From a6b0ab82c4f7d92f8dee74f2a936f62e85f14792 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sun, 6 Jan 2019 16:13:40 +1100 Subject: [PATCH 016/123] gofmt all files. --- camera.go | 214 +++++++++++++++++++++++++++--------------------------- frame.go | 72 +++++++++--------- server.go | 120 +++++++++++++++--------------- 3 files changed, 203 insertions(+), 203 deletions(-) diff --git a/camera.go b/camera.go index 6c2a330..da94185 100644 --- a/camera.go +++ b/camera.go @@ -1,160 +1,160 @@ package main import ( - "github.com/aamcrae/webcam" - "fmt" - "log" + "fmt" + "github.com/aamcrae/webcam" + "log" ) type snapshot struct { - frame []byte - index uint32 + frame []byte + index uint32 } type Camera struct { - cam *webcam.Webcam - Width int - Height int - Format string - Timeout uint32 - newFrame func(int, int, []byte, func()) (Frame, error) - stop chan struct{} - stream chan snapshot + cam *webcam.Webcam + Width int + Height int + Format string + Timeout uint32 + newFrame func(int, int, []byte, func()) (Frame, error) + stop chan struct{} + stream chan snapshot } func OpenCamera(name string) (*Camera, error) { c, err := webcam.Open(name) if err != nil { - return nil, err + return nil, err } - camera := &Camera{cam:c, Timeout:5} - camera.stop = make(chan struct{}, 1) - camera.stream = make(chan snapshot, 0) - return camera, nil + camera := &Camera{cam: c, Timeout: 5} + camera.stop = make(chan struct{}, 1) + camera.stream = make(chan snapshot, 0) + return camera, nil } func (c *Camera) Close() { - c.stop <- struct{}{} - // Flush any remaining frames. - for f := range c.stream { - c.cam.ReleaseFrame(f.index) - } - c.cam.StopStreaming() - c.cam.Close() + c.stop <- struct{}{} + // Flush any remaining frames. + for f := range c.stream { + c.cam.ReleaseFrame(f.index) + } + c.cam.StopStreaming() + c.cam.Close() } func (c *Camera) Init(format string, resolution string) error { - // Get the supported formats and their descriptions. + // Get the supported formats and their descriptions. format_desc := c.cam.GetSupportedFormats() - var pixelFormat webcam.PixelFormat - var found bool - for k, v := range format_desc { - if v == format { - found = true - pixelFormat = k - break - } - } - if !found { - return fmt.Errorf("Camera does not support this format: %s", format) - } - var err error - if c.newFrame, err = GetFramer(format); err != nil { - return err - } - - // Build a map of resolution names from the description. - sizeMap := make(map[string]webcam.FrameSize) - for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { - sizeMap[value.GetString()] = value - } + var pixelFormat webcam.PixelFormat + var found bool + for k, v := range format_desc { + if v == format { + found = true + pixelFormat = k + break + } + } + if !found { + return fmt.Errorf("Camera does not support this format: %s", format) + } + var err error + if c.newFrame, err = GetFramer(format); err != nil { + return err + } + + // Build a map of resolution names from the description. + sizeMap := make(map[string]webcam.FrameSize) + for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { + sizeMap[value.GetString()] = value + } sz, ok := sizeMap[resolution] - if !ok { - return fmt.Errorf("Unsupported resoluton: %s", resolution) - } + if !ok { + return fmt.Errorf("Unsupported resoluton: %s", resolution) + } _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) if err != nil { - return err + return err } - c.Width = int(w) - c.Height = int(h) + c.Width = int(w) + c.Height = int(h) - c.cam.SetBufferCount(16) - c.cam.SetAutoWhiteBalance(true) + c.cam.SetBufferCount(16) + c.cam.SetAutoWhiteBalance(true) if err := c.cam.StartStreaming(); err != nil { - return err - } - go c.capture() - return nil + return err + } + go c.capture() + return nil } func (c *Camera) GetFrame() (Frame, error) { - snap, ok := <-c.stream - if !ok { - return nil, fmt.Errorf("No frame received") - } - return c.newFrame(c.Width, c.Height, snap.frame, func() { - c.cam.ReleaseFrame(snap.index) - }) + snap, ok := <-c.stream + if !ok { + return nil, fmt.Errorf("No frame received") + } + return c.newFrame(c.Width, c.Height, snap.frame, func() { + c.cam.ReleaseFrame(snap.index) + }) } // capture continually reads frames and either discards them or // sends them to a channel that is ready to receive them. func (c *Camera) capture() { - for { - err := c.cam.WaitForFrame(c.Timeout) - - switch err.(type) { - case nil: - case *webcam.Timeout: - continue - default: - log.Fatal(err) - } - - frame, index, err := c.cam.GetFrame() - if err != nil { - log.Fatal(err) - } - select { - // Only executed if stream is ready to receive. - case c.stream <- snapshot{frame, index}: - case <-c.stop: - // Finish up. - c.cam.ReleaseFrame(index) - close(c.stream) - return - default: - c.cam.ReleaseFrame(index) - } - } + for { + err := c.cam.WaitForFrame(c.Timeout) + + switch err.(type) { + case nil: + case *webcam.Timeout: + continue + default: + log.Fatal(err) + } + + frame, index, err := c.cam.GetFrame() + if err != nil { + log.Fatal(err) + } + select { + // Only executed if stream is ready to receive. + case c.stream <- snapshot{frame, index}: + case <-c.stop: + // Finish up. + c.cam.ReleaseFrame(index) + close(c.stream) + return + default: + c.cam.ReleaseFrame(index) + } + } } // Return map of supported formats and resolutions. func (c *Camera) Query() map[string][]string { - m := map[string][]string{} + m := map[string][]string{} formats := c.cam.GetSupportedFormats() - for f, fs := range formats { - r := []string{} - for _, value := range c.cam.GetSupportedFrameSizes(f) { - if value.StepWidth == 0 && value.StepHeight == 0 { - r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) - } - } - m[fs] = r - } - return m + for f, fs := range formats { + r := []string{} + for _, value := range c.cam.GetSupportedFrameSizes(f) { + if value.StepWidth == 0 && value.StepHeight == 0 { + r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) + } + } + m[fs] = r + } + return m } // Get control value. func (c *Camera) GetControl(name string) (int32, error) { - return c.cam.GetControl(name) + return c.cam.GetControl(name) } // Set control value. func (c *Camera) SetControl(name string, value int32) error { - return c.cam.SetControl(name, value) + return c.cam.SetControl(name, value) } diff --git a/frame.go b/frame.go index 38b9ea7..c9d5780 100644 --- a/frame.go +++ b/frame.go @@ -1,68 +1,68 @@ package main import ( - "fmt" - "image" - "image/color" + "fmt" + "image" + "image/color" ) type Frame interface { - image.Image - Release() + image.Image + Release() } type FrameYUYV422 struct { - model color.Model - b image.Rectangle - frame []byte - release func() + model color.Model + b image.Rectangle + frame []byte + release func() } -var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error) { - "YUYV 4:2:2": newFrameYUYV422, +var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){ + "YUYV 4:2:2": newFrameYUYV422, } // Return a function that wraps the frame for this format. func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { - if f, ok := frameHandlers[format]; ok { - return f, nil - } - return nil, fmt.Errorf("No handler for format '%s'", format) + if f, ok := frameHandlers[format]; ok { + return f, nil + } + return nil, fmt.Errorf("No handler for format '%s'", format) } // Wrap a raw frame in a Frame so that it can be used as an image. func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - expLen := 2 * x * y - if len(f) != expLen { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) - } - fr := &FrameYUYV422{model: color.YCbCrModel, b:image.Rect(0, 0, x, y), frame:f, release:rel} - return fr, nil + expLen := 2 * x * y + if len(f) != expLen { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + } + fr := &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel} + return fr, nil } func (f *FrameYUYV422) ColorModel() color.Model { - return f.model + return f.model } func (f *FrameYUYV422) Bounds() image.Rectangle { - return f.b + return f.b } func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.b.Max.X * y * 2 + (x &^ 1) * 2 - if x & 1 == 0 { - return color.YCbCr{f.frame[index], f.frame[index + 1], f.frame[index + 3]} - } else { - return color.YCbCr{f.frame[index + 2], f.frame[index + 1], f.frame[index + 3]} - } + index := f.b.Max.X*y*2 + (x&^1)*2 + if x&1 == 0 { + return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} + } else { + return color.YCbCr{f.frame[index+2], f.frame[index+1], f.frame[index+3]} + } } // Done with frame, release back to camera (if required) -func (f* FrameYUYV422) Release() { - if f.release != nil { - f.release() - } +func (f *FrameYUYV422) Release() { + if f.release != nil { + f.release() + } } diff --git a/server.go b/server.go index 7a2e196..1af45fd 100644 --- a/server.go +++ b/server.go @@ -1,14 +1,14 @@ package main import ( - "flag" - "fmt" - "image/jpeg" - "log" - "net/http" - "strconv" - "strings" - "time" + "flag" + "fmt" + "image/jpeg" + "log" + "net/http" + "strconv" + "strings" + "time" ) var port = flag.Int("port", 8080, "Web server port number") @@ -16,68 +16,68 @@ var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") var controls = flag.String("controls", "focus=190,power_line_frequency=1", - "Control parameters for camera") + "Control parameters for camera") var startDelay = flag.Int("delay", 5, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") func init() { - flag.Parse() + flag.Parse() } func main() { - if *startDelay != 0 { - time.Sleep(time.Duration(*startDelay) * time.Second) - } - cam, err := OpenCamera(*device) - if err != nil { - log.Fatalf("%s: %v", *device, err) - } + if *startDelay != 0 { + time.Sleep(time.Duration(*startDelay) * time.Second) + } + cam, err := OpenCamera(*device) + if err != nil { + log.Fatalf("%s: %v", *device, err) + } defer cam.Close() - if err := cam.Init(*format, *resolution); err != nil { + if err := cam.Init(*format, *resolution); err != nil { log.Fatalf("Init failed: %v", err) - } - // Initialise camera controls. - for _, control := range strings.Split(*controls, ",") { - // If no parameter, assume bool and set to true. - s := strings.Split(control, "=") - if len(s) == 1 { - s = append(s, "true") - } - if len(s) != 2 { - log.Fatalf("Bad control option: %s", control) - } - val, err := strconv.Atoi(s[1]) - if err != nil { - log.Fatalf("Bad control value: %s (%v)", control, err) - } - if err = cam.SetControl(s[0], int32(val)); err != nil { - log.Fatalf("SetControl error: %s (%v)", control, err) - } - } - http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ - readImage(cam, w, r) - })) - url := fmt.Sprintf(":%d", *port) - if *verbose { - log.Printf("Starting server on %s", url) - } - s := &http.Server{Addr: url} - log.Fatal(s.ListenAndServe()) + } + // Initialise camera controls. + for _, control := range strings.Split(*controls, ",") { + // If no parameter, assume bool and set to true. + s := strings.Split(control, "=") + if len(s) == 1 { + s = append(s, "true") + } + if len(s) != 2 { + log.Fatalf("Bad control option: %s", control) + } + val, err := strconv.Atoi(s[1]) + if err != nil { + log.Fatalf("Bad control value: %s (%v)", control, err) + } + if err = cam.SetControl(s[0], int32(val)); err != nil { + log.Fatalf("SetControl error: %s (%v)", control, err) + } + } + http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + readImage(cam, w, r) + })) + url := fmt.Sprintf(":%d", *port) + if *verbose { + log.Printf("Starting server on %s", url) + } + s := &http.Server{Addr: url} + log.Fatal(s.ListenAndServe()) } func readImage(cam *Camera, w http.ResponseWriter, r *http.Request) { - if *verbose { - log.Printf("URL request: %v", r.URL) - } - frame, err := cam.GetFrame() - if err != nil { - log.Fatalf("Getframe: %v", err) - } - w.Header().Set("Content-Type", "image/jpeg") - if err := jpeg.Encode(w, frame, nil); err != nil { - log.Printf("Error writing image: %v\n", err) - } else if *verbose { - log.Printf("Wrote image successfully\n") - } - frame.Release() + if *verbose { + log.Printf("URL request: %v", r.URL) + } + frame, err := cam.GetFrame() + if err != nil { + log.Fatalf("Getframe: %v", err) + } + w.Header().Set("Content-Type", "image/jpeg") + if err := jpeg.Encode(w, frame, nil); err != nil { + log.Printf("Error writing image: %v\n", err) + } else if *verbose { + log.Printf("Wrote image successfully\n") + } + frame.Release() } From 138049cddf75ad0477690d0cf2621f59c4156d8b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 7 Jan 2019 10:32:18 +1100 Subject: [PATCH 017/123] Fixed get controls. --- examples/getcontrols.go | 20 ++++++++++++++++++++ v4l2.go | 5 +++-- webcam.go | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 examples/getcontrols.go diff --git a/examples/getcontrols.go b/examples/getcontrols.go new file mode 100644 index 0000000..c68cc28 --- /dev/null +++ b/examples/getcontrols.go @@ -0,0 +1,20 @@ +// Example program that reads the list of available controls and prints them. +package main + +import "github.com/aamcrae/webcam" +import "fmt" + +func main() { + cam, err := webcam.Open("/dev/video0") + if err != nil { + panic(err.Error()) + } + defer cam.Close() + + clist := cam.GetControlList() + + fmt.Println("Available controls: ") + for _, c := range clist { + fmt.Printf("%32s Min: %4d Max: %5d\n", c.Name, c.Min, c.Max) + } +} diff --git a/v4l2.go b/v4l2.go index cef0472..e4b57dd 100644 --- a/v4l2.go +++ b/v4l2.go @@ -474,14 +474,15 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - id := V4L2_CID_BASE + // Don't use V42L_CID_BASE since it is the same as brightness. + var id uint32 for err != ioctl.ErrEINVAL { id |= V4L2_CTRL_FLAG_NEXT_CTRL query := &v4l2_queryctrl{} query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) + id = query.id if err == nil { - id = query.id if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { continue } diff --git a/webcam.go b/webcam.go index ec75af2..8d68733 100644 --- a/webcam.go +++ b/webcam.go @@ -19,6 +19,12 @@ type Webcam struct { controls map[string]control } +type Control struct { + Name string + Min int32 + Max int32 +} + // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -127,6 +133,18 @@ func (w *Webcam) SetBufferCount(count uint32) error { return nil } +// Get the list of available controls. +func (w *Webcam) GetControlList() []Control { + var clist []Control + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + for s, c := range w.controls { + clist = append(clist, Control{s, c.min, c.max}) + } + return clist +} + // Get the value of a control. func (w *Webcam) GetControl(name string) (int32, error) { c, err := w.lookupControl(name) From f1acc79f38dc5da4830b8c8e33b6c992a207a49d Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:25:09 +1100 Subject: [PATCH 018/123] Fix spelling mistake. --- camera.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camera.go b/camera.go index da94185..4daec40 100644 --- a/camera.go +++ b/camera.go @@ -71,7 +71,7 @@ func (c *Camera) Init(format string, resolution string) error { sz, ok := sizeMap[resolution] if !ok { - return fmt.Errorf("Unsupported resoluton: %s", resolution) + return fmt.Errorf("Unsupported resolution: %s", resolution) } _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) From 7c57663fa57cbd49236965e6fb81b1f24393d5eb Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:27:23 +1100 Subject: [PATCH 019/123] gofmt. --- v4l2.go | 125 +++++++++++++++++++++++++++--------------------------- webcam.go | 60 +++++++++++++------------- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/v4l2.go b/v4l2.go index e4b57dd..290de03 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,7 +3,7 @@ package webcam import ( "bytes" "encoding/binary" - "strings" + "strings" "unsafe" "github.com/aamcrae/webcam/ioctl" @@ -11,17 +11,18 @@ import ( ) type controlType int + const ( - c_int controlType = iota - c_bool - c_menu + c_int controlType = iota + c_bool + c_menu ) type control struct { - id uint32 - c_type controlType - min int32 - max int32 + id uint32 + c_type controlType + min int32 + max int32 } const ( @@ -45,39 +46,38 @@ const ( ) const ( - V4L2_CTRL_TYPE_INTEGER uint32 = 1 - V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 - V4L2_CTRL_TYPE_MENU uint32 = 3 - V4L2_CTRL_TYPE_BUTTON uint32 = 4 - V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 - V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 - V4L2_CTRL_TYPE_STRING uint32 = 7 - V4L2_CTRL_TYPE_BITMASK uint32 = 8 - V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 - - V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 - V4L2_CTRL_TYPE_U8 uint32 = 0x0100 - V4L2_CTRL_TYPE_U16 uint32 = 0x0101 - V4L2_CTRL_TYPE_U32 uint32 = 0x0102 + V4L2_CTRL_TYPE_INTEGER uint32 = 1 + V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 + V4L2_CTRL_TYPE_MENU uint32 = 3 + V4L2_CTRL_TYPE_BUTTON uint32 = 4 + V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 + V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 + V4L2_CTRL_TYPE_STRING uint32 = 7 + V4L2_CTRL_TYPE_BITMASK uint32 = 8 + V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 + + V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 + V4L2_CTRL_TYPE_U8 uint32 = 0x0100 + V4L2_CTRL_TYPE_U16 uint32 = 0x0101 + V4L2_CTRL_TYPE_U32 uint32 = 0x0102 ) - const ( - V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 - V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 + V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 + V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 ) var ( - VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) - VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) - VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) - VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) - VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) - VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) - VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) + VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) + VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) + VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) + VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) + VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) + VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -461,7 +461,7 @@ func getControl(fd uintptr, id uint32) (int32, error) { ctrl := &v4l2_control{} ctrl.id = id err := ioctl.Ioctl(fd, VIDIOC_G_CTRL, uintptr(unsafe.Pointer(ctrl))) - return ctrl.value, err + return ctrl.value, err } func setControl(fd uintptr, id uint32, val int32) error { @@ -474,34 +474,35 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - // Don't use V42L_CID_BASE since it is the same as brightness. - var id uint32 - for err != ioctl.ErrEINVAL { - id |= V4L2_CTRL_FLAG_NEXT_CTRL - query := &v4l2_queryctrl{} - query.id = id + // Don't use V42L_CID_BASE since it is the same as brightness. + var id uint32 + for err != ioctl.ErrEINVAL { + id |= V4L2_CTRL_FLAG_NEXT_CTRL + query := &v4l2_queryctrl{} + query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) - id = query.id + id = query.id if err == nil { - if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { - continue - } - var c control - switch query._type { - default: continue - case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: - c.c_type = c_int - case V4L2_CTRL_TYPE_BOOLEAN: - c.c_type = c_bool - case V4L2_CTRL_TYPE_MENU: - c.c_type = c_menu - } - c.id = id - c.min = query.minimum - c.max = query.maximum - // Normalise name (' ' -> '_', make lower case). - n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) - controls[n] = c + if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { + continue + } + var c control + switch query._type { + default: + continue + case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: + c.c_type = c_int + case V4L2_CTRL_TYPE_BOOLEAN: + c.c_type = c_bool + case V4L2_CTRL_TYPE_MENU: + c.c_type = c_menu + } + c.id = id + c.min = query.minimum + c.max = query.maximum + // Normalise name (' ' -> '_', make lower case). + n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) + controls[n] = c } } return controls diff --git a/webcam.go b/webcam.go index 8d68733..e04b6d8 100644 --- a/webcam.go +++ b/webcam.go @@ -16,13 +16,13 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool - controls map[string]control + controls map[string]control } type Control struct { - Name string - Min int32 - Max int32 + Name string + Min int32 + Max int32 } // Open a webcam with a given path @@ -135,43 +135,43 @@ func (w *Webcam) SetBufferCount(count uint32) error { // Get the list of available controls. func (w *Webcam) GetControlList() []Control { - var clist []Control - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - for s, c := range w.controls { - clist = append(clist, Control{s, c.min, c.max}) - } - return clist + var clist []Control + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + for s, c := range w.controls { + clist = append(clist, Control{s, c.min, c.max}) + } + return clist } // Get the value of a control. func (w *Webcam) GetControl(name string) (int32, error) { - c, err := w.lookupControl(name) - if err != nil { - return 0, err - } - return getControl(w.fd, c.id) + c, err := w.lookupControl(name) + if err != nil { + return 0, err + } + return getControl(w.fd, c.id) } // Set a control. func (w *Webcam) SetControl(name string, value int32) error { - c, err := w.lookupControl(name) - if err != nil { - return err - } - return setControl(w.fd, c.id, value) + c, err := w.lookupControl(name) + if err != nil { + return err + } + return setControl(w.fd, c.id, value) } func (w *Webcam) lookupControl(name string) (control, error) { - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - c, ok := w.controls[name] - if !ok { - return control{}, errors.New("Unknown control: " + name) - } - return c, nil + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + c, ok := w.controls[name] + if !ok { + return control{}, errors.New("Unknown control: " + name) + } + return c, nil } // Start streaming process From 025e1b07501e58db2b5ffc6b54251c105b1606c2 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:34:27 +1100 Subject: [PATCH 020/123] gofmt on ioctl. --- ioctl/ioctl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 76a2c9c..a1118f3 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -22,7 +22,7 @@ const ( sizeShift = typeShift + typeBits directionShift = sizeShift + sizeBits - ErrEINVAL = unix.EINVAL + ErrEINVAL = unix.EINVAL ) func ioc(dir, t, nr, size uintptr) uintptr { From c11a4eee848d155a01de9ba870fd5f3223b74ac5 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:36:48 +1100 Subject: [PATCH 021/123] Changed import. --- examples/getcontrols.go | 2 +- v4l2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/getcontrols.go b/examples/getcontrols.go index c68cc28..625ecf8 100644 --- a/examples/getcontrols.go +++ b/examples/getcontrols.go @@ -1,7 +1,7 @@ // Example program that reads the list of available controls and prints them. package main -import "github.com/aamcrae/webcam" +import "github.com/blackjack/webcam" import "fmt" func main() { diff --git a/v4l2.go b/v4l2.go index 290de03..d8e94f7 100644 --- a/v4l2.go +++ b/v4l2.go @@ -6,7 +6,7 @@ import ( "strings" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From 1d69131d0ea4ed758bab63b3536f2069e517fa9f Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 31 Mar 2018 21:39:31 +1100 Subject: [PATCH 022/123] Changes to allow framegrabbing. --- webcam.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/webcam.go b/webcam.go index 00aabde..b177e69 100644 --- a/webcam.go +++ b/webcam.go @@ -18,6 +18,8 @@ type Webcam struct { streaming bool } +const maxBufferCount = 512 + // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -122,6 +124,9 @@ func (w *Webcam) SetBufferCount(count uint32) error { if w.streaming { return errors.New("Cannot set buffer count when streaming") } + if count < 2 || count > maxBufferCount { + return errors.New("Illegal buffer count") + } w.bufcount = count return nil } @@ -138,6 +143,10 @@ func (w *Webcam) StartStreaming() error { return errors.New("Failed to map request buffers: " + string(err.Error())) } + if w.bufcount < 2 { + return errors.New("Insufficient buffer memory") + } + w.buffers = make([][]byte, w.bufcount, w.bufcount) for index, _ := range w.buffers { var length uint32 From 2ce98f3b76d697240e5ddaf7654ae5fb97f63af1 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sun, 1 Apr 2018 13:59:58 +1000 Subject: [PATCH 023/123] Change import --- v4l2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index 5ab0a1e..357a6be 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) From e8542c364b645231379386df1f4c45f161ae2641 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 4 Apr 2018 12:01:11 +1000 Subject: [PATCH 024/123] Restore import path. --- v4l2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index 357a6be..5ab0a1e 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From bba6499ecb705f220126db3e443bef05a136c73e Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 5 Apr 2018 11:08:43 +1000 Subject: [PATCH 025/123] Fixed formatting, removed check on buffer limit. --- webcam.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/webcam.go b/webcam.go index b177e69..00aabde 100644 --- a/webcam.go +++ b/webcam.go @@ -18,8 +18,6 @@ type Webcam struct { streaming bool } -const maxBufferCount = 512 - // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -124,9 +122,6 @@ func (w *Webcam) SetBufferCount(count uint32) error { if w.streaming { return errors.New("Cannot set buffer count when streaming") } - if count < 2 || count > maxBufferCount { - return errors.New("Illegal buffer count") - } w.bufcount = count return nil } @@ -143,10 +138,6 @@ func (w *Webcam) StartStreaming() error { return errors.New("Failed to map request buffers: " + string(err.Error())) } - if w.bufcount < 2 { - return errors.New("Insufficient buffer memory") - } - w.buffers = make([][]byte, w.bufcount, w.bufcount) for index, _ := range w.buffers { var length uint32 From 5b6eb998f4259046a4e1af50e07f3c6dcadd1838 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 24 Dec 2018 15:03:36 +1100 Subject: [PATCH 026/123] Added set/get controls. --- v4l2.go | 118 ++++++++++++++++++++++++++++++++++++++++++++---------- webcam.go | 30 ++++++++++++++ 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/v4l2.go b/v4l2.go index 5ab0a1e..07bc224 100644 --- a/v4l2.go +++ b/v4l2.go @@ -9,6 +9,20 @@ import ( "golang.org/x/sys/unix" ) +type controlType int +const ( + c_int controlType = iota + c_bool + c_menu +) + +type control struct { + id uint32 + c_type controlType + min int64 + max int64 +} + const ( V4L2_CAP_VIDEO_CAPTURE uint32 = 0x00000001 V4L2_CAP_STREAMING uint32 = 0x04000000 @@ -29,16 +43,45 @@ const ( V4L2_CID_PRIVATE_BASE uint32 = 0x08000000 ) +const ( + V4L2_CTRL_TYPE_INTEGER uint32 = 1 + V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 + V4L2_CTRL_TYPE_MENU uint32 = 3 + V4L2_CTRL_TYPE_BUTTON uint32 = 4 + V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 + V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 + V4L2_CTRL_TYPE_STRING uint32 = 7 + V4L2_CTRL_TYPE_BITMASK uint32 = 8 + V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 + + V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 + V4L2_CTRL_TYPE_U8 uint32 = 0x0100 + V4L2_CTRL_TYPE_U16 uint32 = 0x0101 + V4L2_CTRL_TYPE_U32 uint32 = 0x0102 +) + + +const ( + V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 + V4L2_CTRL_FLAG_NEXT_COMPOUND uint32 = 0x40000000 +) + +const ( + V4L2_CTRL_MAX_DIMS uint32 = 4 +) + var ( - VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) - VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) - VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) - VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) - VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) - VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) + VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) + VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) + VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) + VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) + VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) + VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERY_EXT_CTRL = ioctl.IoRW(uintptr('V'), 103, unsafe.Sizeof(v4l2_query_ext_ctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -164,6 +207,22 @@ type v4l2_control struct { value int32 } +type v4l2_query_ext_ctrl struct { + id uint32 + _type uint32 + name [32]uint8 + minimum int64 + maximum int64 + step uint64 + default_value int64 + flags uint32 + elem_size uint32 + elems uint32 + nr_of_dims uint32 + dims uint32 + reserved [32]uint32 +} + func checkCapabilities(fd uintptr) (supportsVideoCapture bool, supportsVideoStreaming bool, err error) { caps := &v4l2_capability{} @@ -418,6 +477,13 @@ func waitForFrame(fd uintptr, timeout uint32) (count int, err error) { } +func getControl(fd uintptr, id uint32) (int32, error) { + ctrl := &v4l2_control{} + ctrl.id = id + err := ioctl.Ioctl(fd, VIDIOC_G_CTRL, uintptr(unsafe.Pointer(ctrl))) + return ctrl.value, err +} + func setControl(fd uintptr, id uint32, val int32) error { ctrl := &v4l2_control{} ctrl.id = id @@ -425,21 +491,31 @@ func setControl(fd uintptr, id uint32, val int32) error { return ioctl.Ioctl(fd, VIDIOC_S_CTRL, uintptr(unsafe.Pointer(ctrl))) } -func getControls(fd uintptr) map[uint32]string { - query := &v4l2_queryctrl{} - var controls map[uint32]string +func queryControls(fd uintptr) map[string]control { + var controls map[string]control = make(map[string]control) var err error - for query.id = V4L2_CID_BASE; err == nil; query.id++ { - err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) - if err == nil { - controls[query.id] = CToGoString(query.name[:]) - } - } - err = nil - for query.id = V4L2_CID_PRIVATE_BASE; err == nil; query.id++ { + id := V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND + for err == nil { + id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND + query := &v4l2_query_ext_ctrl{} + query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) if err == nil { - controls[query.id] = CToGoString(query.name[:]) + id = query.id + var c control + switch query._type { + default: continue + case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: + c.c_type = c_int + case V4L2_CTRL_TYPE_BOOLEAN: + c.c_type = c_bool + case V4L2_CTRL_TYPE_MENU: + c.c_type = c_menu + } + c.id = id + c.min = query.minimum + c.max = query.maximum + controls[CToGoString(query.name[:])] = c } } return controls diff --git a/webcam.go b/webcam.go index 00aabde..ec75af2 100644 --- a/webcam.go +++ b/webcam.go @@ -16,6 +16,7 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool + controls map[string]control } // Open a webcam with a given path @@ -126,6 +127,35 @@ func (w *Webcam) SetBufferCount(count uint32) error { return nil } +// Get the value of a control. +func (w *Webcam) GetControl(name string) (int32, error) { + c, err := w.lookupControl(name) + if err != nil { + return 0, err + } + return getControl(w.fd, c.id) +} + +// Set a control. +func (w *Webcam) SetControl(name string, value int32) error { + c, err := w.lookupControl(name) + if err != nil { + return err + } + return setControl(w.fd, c.id, value) +} + +func (w *Webcam) lookupControl(name string) (control, error) { + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + c, ok := w.controls[name] + if !ok { + return control{}, errors.New("Unknown control: " + name) + } + return c, nil +} + // Start streaming process func (w *Webcam) StartStreaming() error { if w.streaming { From efb38a7fd273e0d0e57c277e26247097f489d3fe Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 24 Dec 2018 16:50:34 +1100 Subject: [PATCH 027/123] Added set/get controls. --- ioctl/ioctl.go | 2 ++ v4l2.go | 45 +++++++++++++++------------------------------ 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 3ce8df6..76a2c9c 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -21,6 +21,8 @@ const ( typeShift = numberShift + numberBits sizeShift = typeShift + typeBits directionShift = sizeShift + sizeBits + + ErrEINVAL = unix.EINVAL ) func ioc(dir, t, nr, size uintptr) uintptr { diff --git a/v4l2.go b/v4l2.go index 07bc224..cef0472 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,9 +3,10 @@ package webcam import ( "bytes" "encoding/binary" + "strings" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) @@ -19,8 +20,8 @@ const ( type control struct { id uint32 c_type controlType - min int64 - max int64 + min int32 + max int32 } const ( @@ -62,12 +63,8 @@ const ( const ( + V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 - V4L2_CTRL_FLAG_NEXT_COMPOUND uint32 = 0x40000000 -) - -const ( - V4L2_CTRL_MAX_DIMS uint32 = 4 ) var ( @@ -81,7 +78,6 @@ var ( VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) - VIDIOC_QUERY_EXT_CTRL = ioctl.IoRW(uintptr('V'), 103, unsafe.Sizeof(v4l2_query_ext_ctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -207,22 +203,6 @@ type v4l2_control struct { value int32 } -type v4l2_query_ext_ctrl struct { - id uint32 - _type uint32 - name [32]uint8 - minimum int64 - maximum int64 - step uint64 - default_value int64 - flags uint32 - elem_size uint32 - elems uint32 - nr_of_dims uint32 - dims uint32 - reserved [32]uint32 -} - func checkCapabilities(fd uintptr) (supportsVideoCapture bool, supportsVideoStreaming bool, err error) { caps := &v4l2_capability{} @@ -494,14 +474,17 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - id := V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND - for err == nil { - id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND - query := &v4l2_query_ext_ctrl{} + id := V4L2_CID_BASE + for err != ioctl.ErrEINVAL { + id |= V4L2_CTRL_FLAG_NEXT_CTRL + query := &v4l2_queryctrl{} query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) if err == nil { id = query.id + if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { + continue + } var c control switch query._type { default: continue @@ -515,7 +498,9 @@ func queryControls(fd uintptr) map[string]control { c.id = id c.min = query.minimum c.max = query.maximum - controls[CToGoString(query.name[:])] = c + // Normalise name (' ' -> '_', make lower case). + n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) + controls[n] = c } } return controls From e1776e2794bfc34ba8b244e4629482376a4835be Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 7 Jan 2019 10:32:18 +1100 Subject: [PATCH 028/123] Fixed get controls. --- examples/getcontrols.go | 20 ++++++++++++++++++++ v4l2.go | 5 +++-- webcam.go | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 examples/getcontrols.go diff --git a/examples/getcontrols.go b/examples/getcontrols.go new file mode 100644 index 0000000..c68cc28 --- /dev/null +++ b/examples/getcontrols.go @@ -0,0 +1,20 @@ +// Example program that reads the list of available controls and prints them. +package main + +import "github.com/aamcrae/webcam" +import "fmt" + +func main() { + cam, err := webcam.Open("/dev/video0") + if err != nil { + panic(err.Error()) + } + defer cam.Close() + + clist := cam.GetControlList() + + fmt.Println("Available controls: ") + for _, c := range clist { + fmt.Printf("%32s Min: %4d Max: %5d\n", c.Name, c.Min, c.Max) + } +} diff --git a/v4l2.go b/v4l2.go index cef0472..e4b57dd 100644 --- a/v4l2.go +++ b/v4l2.go @@ -474,14 +474,15 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - id := V4L2_CID_BASE + // Don't use V42L_CID_BASE since it is the same as brightness. + var id uint32 for err != ioctl.ErrEINVAL { id |= V4L2_CTRL_FLAG_NEXT_CTRL query := &v4l2_queryctrl{} query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) + id = query.id if err == nil { - id = query.id if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { continue } diff --git a/webcam.go b/webcam.go index ec75af2..8d68733 100644 --- a/webcam.go +++ b/webcam.go @@ -19,6 +19,12 @@ type Webcam struct { controls map[string]control } +type Control struct { + Name string + Min int32 + Max int32 +} + // Open a webcam with a given path // Checks if device is a v4l2 device and if it is // capable to stream video @@ -127,6 +133,18 @@ func (w *Webcam) SetBufferCount(count uint32) error { return nil } +// Get the list of available controls. +func (w *Webcam) GetControlList() []Control { + var clist []Control + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + for s, c := range w.controls { + clist = append(clist, Control{s, c.min, c.max}) + } + return clist +} + // Get the value of a control. func (w *Webcam) GetControl(name string) (int32, error) { c, err := w.lookupControl(name) From 01f67467374d2a95d8ce9a1bca8ba5f2fb227970 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:27:23 +1100 Subject: [PATCH 029/123] gofmt. --- v4l2.go | 125 +++++++++++++++++++++++++++--------------------------- webcam.go | 60 +++++++++++++------------- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/v4l2.go b/v4l2.go index e4b57dd..290de03 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,7 +3,7 @@ package webcam import ( "bytes" "encoding/binary" - "strings" + "strings" "unsafe" "github.com/aamcrae/webcam/ioctl" @@ -11,17 +11,18 @@ import ( ) type controlType int + const ( - c_int controlType = iota - c_bool - c_menu + c_int controlType = iota + c_bool + c_menu ) type control struct { - id uint32 - c_type controlType - min int32 - max int32 + id uint32 + c_type controlType + min int32 + max int32 } const ( @@ -45,39 +46,38 @@ const ( ) const ( - V4L2_CTRL_TYPE_INTEGER uint32 = 1 - V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 - V4L2_CTRL_TYPE_MENU uint32 = 3 - V4L2_CTRL_TYPE_BUTTON uint32 = 4 - V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 - V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 - V4L2_CTRL_TYPE_STRING uint32 = 7 - V4L2_CTRL_TYPE_BITMASK uint32 = 8 - V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 - - V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 - V4L2_CTRL_TYPE_U8 uint32 = 0x0100 - V4L2_CTRL_TYPE_U16 uint32 = 0x0101 - V4L2_CTRL_TYPE_U32 uint32 = 0x0102 + V4L2_CTRL_TYPE_INTEGER uint32 = 1 + V4L2_CTRL_TYPE_BOOLEAN uint32 = 2 + V4L2_CTRL_TYPE_MENU uint32 = 3 + V4L2_CTRL_TYPE_BUTTON uint32 = 4 + V4L2_CTRL_TYPE_INTEGER64 uint32 = 5 + V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6 + V4L2_CTRL_TYPE_STRING uint32 = 7 + V4L2_CTRL_TYPE_BITMASK uint32 = 8 + V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9 + + V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100 + V4L2_CTRL_TYPE_U8 uint32 = 0x0100 + V4L2_CTRL_TYPE_U16 uint32 = 0x0101 + V4L2_CTRL_TYPE_U32 uint32 = 0x0102 ) - const ( - V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 - V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 + V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001 + V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000 ) var ( - VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) - VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) - VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) - VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) - VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) - VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) - VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) - VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) + VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{})) + VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{})) + VIDIOC_S_FMT = ioctl.IoRW(uintptr('V'), 5, unsafe.Sizeof(v4l2_format{})) + VIDIOC_REQBUFS = ioctl.IoRW(uintptr('V'), 8, unsafe.Sizeof(v4l2_requestbuffers{})) + VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{})) + VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{})) + VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{})) + VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{})) //sizeof int32 VIDIOC_STREAMON = ioctl.IoW(uintptr('V'), 18, 4) VIDIOC_STREAMOFF = ioctl.IoW(uintptr('V'), 19, 4) @@ -461,7 +461,7 @@ func getControl(fd uintptr, id uint32) (int32, error) { ctrl := &v4l2_control{} ctrl.id = id err := ioctl.Ioctl(fd, VIDIOC_G_CTRL, uintptr(unsafe.Pointer(ctrl))) - return ctrl.value, err + return ctrl.value, err } func setControl(fd uintptr, id uint32, val int32) error { @@ -474,34 +474,35 @@ func setControl(fd uintptr, id uint32, val int32) error { func queryControls(fd uintptr) map[string]control { var controls map[string]control = make(map[string]control) var err error - // Don't use V42L_CID_BASE since it is the same as brightness. - var id uint32 - for err != ioctl.ErrEINVAL { - id |= V4L2_CTRL_FLAG_NEXT_CTRL - query := &v4l2_queryctrl{} - query.id = id + // Don't use V42L_CID_BASE since it is the same as brightness. + var id uint32 + for err != ioctl.ErrEINVAL { + id |= V4L2_CTRL_FLAG_NEXT_CTRL + query := &v4l2_queryctrl{} + query.id = id err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query))) - id = query.id + id = query.id if err == nil { - if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { - continue - } - var c control - switch query._type { - default: continue - case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: - c.c_type = c_int - case V4L2_CTRL_TYPE_BOOLEAN: - c.c_type = c_bool - case V4L2_CTRL_TYPE_MENU: - c.c_type = c_menu - } - c.id = id - c.min = query.minimum - c.max = query.maximum - // Normalise name (' ' -> '_', make lower case). - n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) - controls[n] = c + if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 { + continue + } + var c control + switch query._type { + default: + continue + case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64: + c.c_type = c_int + case V4L2_CTRL_TYPE_BOOLEAN: + c.c_type = c_bool + case V4L2_CTRL_TYPE_MENU: + c.c_type = c_menu + } + c.id = id + c.min = query.minimum + c.max = query.maximum + // Normalise name (' ' -> '_', make lower case). + n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) + controls[n] = c } } return controls diff --git a/webcam.go b/webcam.go index 8d68733..e04b6d8 100644 --- a/webcam.go +++ b/webcam.go @@ -16,13 +16,13 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool - controls map[string]control + controls map[string]control } type Control struct { - Name string - Min int32 - Max int32 + Name string + Min int32 + Max int32 } // Open a webcam with a given path @@ -135,43 +135,43 @@ func (w *Webcam) SetBufferCount(count uint32) error { // Get the list of available controls. func (w *Webcam) GetControlList() []Control { - var clist []Control - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - for s, c := range w.controls { - clist = append(clist, Control{s, c.min, c.max}) - } - return clist + var clist []Control + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + for s, c := range w.controls { + clist = append(clist, Control{s, c.min, c.max}) + } + return clist } // Get the value of a control. func (w *Webcam) GetControl(name string) (int32, error) { - c, err := w.lookupControl(name) - if err != nil { - return 0, err - } - return getControl(w.fd, c.id) + c, err := w.lookupControl(name) + if err != nil { + return 0, err + } + return getControl(w.fd, c.id) } // Set a control. func (w *Webcam) SetControl(name string, value int32) error { - c, err := w.lookupControl(name) - if err != nil { - return err - } - return setControl(w.fd, c.id, value) + c, err := w.lookupControl(name) + if err != nil { + return err + } + return setControl(w.fd, c.id, value) } func (w *Webcam) lookupControl(name string) (control, error) { - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - c, ok := w.controls[name] - if !ok { - return control{}, errors.New("Unknown control: " + name) - } - return c, nil + if len(w.controls) == 0 { + w.controls = queryControls(w.fd) + } + c, ok := w.controls[name] + if !ok { + return control{}, errors.New("Unknown control: " + name) + } + return c, nil } // Start streaming process From 21ec8f166b9eba38f913ac4bdcf1c5f70c9188be Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:34:27 +1100 Subject: [PATCH 030/123] gofmt on ioctl. --- ioctl/ioctl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 76a2c9c..a1118f3 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -22,7 +22,7 @@ const ( sizeShift = typeShift + typeBits directionShift = sizeShift + sizeBits - ErrEINVAL = unix.EINVAL + ErrEINVAL = unix.EINVAL ) func ioc(dir, t, nr, size uintptr) uintptr { From b1459fc831185d93af8c64e9aa200fe05a24c01b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 12 Jan 2019 12:36:48 +1100 Subject: [PATCH 031/123] Changed import. --- examples/getcontrols.go | 2 +- v4l2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/getcontrols.go b/examples/getcontrols.go index c68cc28..625ecf8 100644 --- a/examples/getcontrols.go +++ b/examples/getcontrols.go @@ -1,7 +1,7 @@ // Example program that reads the list of available controls and prints them. package main -import "github.com/aamcrae/webcam" +import "github.com/blackjack/webcam" import "fmt" func main() { diff --git a/v4l2.go b/v4l2.go index 290de03..d8e94f7 100644 --- a/v4l2.go +++ b/v4l2.go @@ -6,7 +6,7 @@ import ( "strings" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From ea7db1f0fe6a756445c97913c3b60c8551f28dd6 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 14 Jan 2019 16:14:08 +1100 Subject: [PATCH 032/123] Fixed review comments. --- examples/{ => getcontrols}/getcontrols.go | 8 ++-- v4l2.go | 13 +++---- webcam.go | 45 ++++++----------------- 3 files changed, 22 insertions(+), 44 deletions(-) rename examples/{ => getcontrols}/getcontrols.go (60%) diff --git a/examples/getcontrols.go b/examples/getcontrols/getcontrols.go similarity index 60% rename from examples/getcontrols.go rename to examples/getcontrols/getcontrols.go index 625ecf8..297b16e 100644 --- a/examples/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -1,7 +1,7 @@ // Example program that reads the list of available controls and prints them. package main -import "github.com/blackjack/webcam" +import "github.com/aamcrae/webcam" import "fmt" func main() { @@ -11,10 +11,10 @@ func main() { } defer cam.Close() - clist := cam.GetControlList() + cmap := cam.GetControls() fmt.Println("Available controls: ") - for _, c := range clist { - fmt.Printf("%32s Min: %4d Max: %5d\n", c.Name, c.Min, c.Max) + for id, c := range cmap { + fmt.Printf("ID:%04x %32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) } } diff --git a/v4l2.go b/v4l2.go index d8e94f7..f7a08c8 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,10 +3,9 @@ package webcam import ( "bytes" "encoding/binary" - "strings" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) @@ -20,6 +19,7 @@ const ( type control struct { id uint32 + name string c_type controlType min int32 max int32 @@ -471,8 +471,8 @@ func setControl(fd uintptr, id uint32, val int32) error { return ioctl.Ioctl(fd, VIDIOC_S_CTRL, uintptr(unsafe.Pointer(ctrl))) } -func queryControls(fd uintptr) map[string]control { - var controls map[string]control = make(map[string]control) +func queryControls(fd uintptr) []control { + controls := []control{} var err error // Don't use V42L_CID_BASE since it is the same as brightness. var id uint32 @@ -498,11 +498,10 @@ func queryControls(fd uintptr) map[string]control { c.c_type = c_menu } c.id = id + c.name = CToGoString(query.name[:]) c.min = query.minimum c.max = query.maximum - // Normalise name (' ' -> '_', make lower case). - n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1) - controls[n] = c + controls = append(controls, c) } } return controls diff --git a/webcam.go b/webcam.go index e04b6d8..b40abef 100644 --- a/webcam.go +++ b/webcam.go @@ -16,9 +16,10 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool - controls map[string]control } +type ControlID uint32 + type Control struct { Name string Min int32 @@ -133,45 +134,23 @@ func (w *Webcam) SetBufferCount(count uint32) error { return nil } -// Get the list of available controls. -func (w *Webcam) GetControlList() []Control { - var clist []Control - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - for s, c := range w.controls { - clist = append(clist, Control{s, c.min, c.max}) +// Get a map of available controls. +func (w *Webcam) GetControls() map[ControlID]Control { + cmap := make(map[ControlID]Control) + for _, c := range queryControls(w.fd) { + cmap[ControlID(c.id)] = Control{c.name, c.min, c.max} } - return clist + return cmap } // Get the value of a control. -func (w *Webcam) GetControl(name string) (int32, error) { - c, err := w.lookupControl(name) - if err != nil { - return 0, err - } - return getControl(w.fd, c.id) +func (w *Webcam) GetControl(id ControlID) (int32, error) { + return getControl(w.fd, uint32(id)) } // Set a control. -func (w *Webcam) SetControl(name string, value int32) error { - c, err := w.lookupControl(name) - if err != nil { - return err - } - return setControl(w.fd, c.id, value) -} - -func (w *Webcam) lookupControl(name string) (control, error) { - if len(w.controls) == 0 { - w.controls = queryControls(w.fd) - } - c, ok := w.controls[name] - if !ok { - return control{}, errors.New("Unknown control: " + name) - } - return c, nil +func (w *Webcam) SetControl(id ControlID, value int32) error { + return setControl(w.fd, uint32(id), value) } // Start streaming process From c4baa575aa6a2566690365a5533c9c3439380fbd Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 14 Jan 2019 16:28:21 +1100 Subject: [PATCH 033/123] Fixed formatting. --- examples/getcontrols/getcontrols.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 297b16e..fe56fc0 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -15,6 +15,6 @@ func main() { fmt.Println("Available controls: ") for id, c := range cmap { - fmt.Printf("ID:%04x %32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) + fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) } } From 163d30511a76019bdf930ecf416dc481672d5eba Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 14 Jan 2019 16:31:03 +1100 Subject: [PATCH 034/123] Replaced imports. --- examples/getcontrols/getcontrols.go | 2 +- v4l2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index fe56fc0..daadfde 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -1,7 +1,7 @@ // Example program that reads the list of available controls and prints them. package main -import "github.com/aamcrae/webcam" +import "github.com/blackjack/webcam" import "fmt" func main() { diff --git a/v4l2.go b/v4l2.go index f7a08c8..09b2ed0 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From 065d5615511ea0e61b066b4b0bfe1d036dce0599 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 14 Jan 2019 16:46:55 +1100 Subject: [PATCH 035/123] Formatting. --- examples/getcontrols/getcontrols.go | 8 ++++---- v4l2.go | 4 ++-- webcam.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index daadfde..5f89f68 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -11,10 +11,10 @@ func main() { } defer cam.Close() - cmap := cam.GetControls() + cmap := cam.GetControls() fmt.Println("Available controls: ") - for id, c := range cmap { - fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) - } + for id, c := range cmap { + fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) + } } diff --git a/v4l2.go b/v4l2.go index 09b2ed0..e81f26e 100644 --- a/v4l2.go +++ b/v4l2.go @@ -19,7 +19,7 @@ const ( type control struct { id uint32 - name string + name string c_type controlType min int32 max int32 @@ -498,7 +498,7 @@ func queryControls(fd uintptr) []control { c.c_type = c_menu } c.id = id - c.name = CToGoString(query.name[:]) + c.name = CToGoString(query.name[:]) c.min = query.minimum c.max = query.maximum controls = append(controls, c) diff --git a/webcam.go b/webcam.go index b40abef..19d3559 100644 --- a/webcam.go +++ b/webcam.go @@ -138,7 +138,7 @@ func (w *Webcam) SetBufferCount(count uint32) error { func (w *Webcam) GetControls() map[ControlID]Control { cmap := make(map[ControlID]Control) for _, c := range queryControls(w.fd) { - cmap[ControlID(c.id)] = Control{c.name, c.min, c.max} + cmap[ControlID(c.id)] = Control{c.name, c.min, c.max} } return cmap } From 8482ae92b60f42d312decd954e632c3c309db0c9 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 14 Jan 2019 17:27:38 +1100 Subject: [PATCH 036/123] Used new webcam controls interface. --- camera.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/camera.go b/camera.go index 4daec40..dd0ac0a 100644 --- a/camera.go +++ b/camera.go @@ -151,10 +151,33 @@ func (c *Camera) Query() map[string][]string { // Get control value. func (c *Camera) GetControl(name string) (int32, error) { - return c.cam.GetControl(name) + id, err := getControlID(name) + if err != nil { + return 0, err + } + return c.cam.GetControl(id) } // Set control value. func (c *Camera) SetControl(name string, value int32) error { - return c.cam.SetControl(name, value) + id, err := getControlID(name) + if err != nil { + return err + } + return c.cam.SetControl(id, value) +} + +// Return the appropriate ControlID for the control name. +func getControlID(name string) (webcam.ControlID, error) { + var controls map[string]webcam.ControlID = map[string]webcam.ControlID{ + "focus" : 0x009a090a, + "power_line_frequency" : 0x00980918, + "brightness" : 0x00980900, + "contrast" : 0x00980901, + } + id, ok := controls[name] + if !ok { + return 0, fmt.Errorf("%s: unknown control") + } + return id, nil } From cdc6efe2cdb21b29a53d88d13a037b83658c8400 Mon Sep 17 00:00:00 2001 From: Andrew McRae <37428808+aamcrae@users.noreply.github.com> Date: Mon, 14 Jan 2019 17:41:42 +1100 Subject: [PATCH 037/123] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..244db54 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# imageserver +A simple web server to capture still images from a webcam. From ce6f496c198600c054f1df3a4887186d8eed5c97 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 15 Jan 2019 15:32:53 +1100 Subject: [PATCH 038/123] Split frame.go --- frame.go | 49 +++---------------------------------------------- yuyv422.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 yuyv422.go diff --git a/frame.go b/frame.go index c9d5780..2b1fe22 100644 --- a/frame.go +++ b/frame.go @@ -3,7 +3,6 @@ package main import ( "fmt" "image" - "image/color" ) type Frame interface { @@ -11,15 +10,10 @@ type Frame interface { Release() } -type FrameYUYV422 struct { - model color.Model - b image.Rectangle - frame []byte - release func() -} +var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} -var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){ - "YUYV 4:2:2": newFrameYUYV422, +func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { + frameHandlers[format] = handler } // Return a function that wraps the frame for this format. @@ -29,40 +23,3 @@ func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), er } return nil, fmt.Errorf("No handler for format '%s'", format) } - -// Wrap a raw frame in a Frame so that it can be used as an image. -func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - expLen := 2 * x * y - if len(f) != expLen { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) - } - fr := &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel} - return fr, nil -} - -func (f *FrameYUYV422) ColorModel() color.Model { - return f.model -} - -func (f *FrameYUYV422) Bounds() image.Rectangle { - return f.b -} - -func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.b.Max.X*y*2 + (x&^1)*2 - if x&1 == 0 { - return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} - } else { - return color.YCbCr{f.frame[index+2], f.frame[index+1], f.frame[index+3]} - } -} - -// Done with frame, release back to camera (if required) -func (f *FrameYUYV422) Release() { - if f.release != nil { - f.release() - } -} diff --git a/yuyv422.go b/yuyv422.go new file mode 100644 index 0000000..2918eab --- /dev/null +++ b/yuyv422.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "image" + "image/color" +) + +type FrameYUYV422 struct { + model color.Model + b image.Rectangle + frame []byte + release func() +} + +func init() { + RegisterFramer("YUYV 4:2:2", newFrameYUYV422) +} + +// Wrap a raw frame in a Frame so that it can be used as an image. +func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { + expLen := 2 * x * y + if len(f) != expLen { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + } + return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel}, nil +} + +func (f *FrameYUYV422) ColorModel() color.Model { + return f.model +} + +func (f *FrameYUYV422) Bounds() image.Rectangle { + return f.b +} + +func (f *FrameYUYV422) At(x, y int) color.Color { + index := f.b.Max.X*y*2 + (x&^1)*2 + if x&1 == 0 { + return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} + } else { + return color.YCbCr{f.frame[index+2], f.frame[index+1], f.frame[index+3]} + } +} + +// Done with frame, release back to camera (if required) +func (f *FrameYUYV422) Release() { + if f.release != nil { + f.release() + } +} From 85e7e914eb2993b280b1fb22ac66b57a218f8db1 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 15 Jan 2019 15:52:28 +1100 Subject: [PATCH 039/123] Added comments. --- camera.go => cam/camera.go | 22 ++++++++++++++-------- frame.go => cam/frame.go | 6 ++++-- yuyv422.go => cam/yuyv422.go | 7 ++++--- server.go | 16 +++++++++------- 4 files changed, 31 insertions(+), 20 deletions(-) rename camera.go => cam/camera.go (85%) rename frame.go => cam/frame.go (69%) rename yuyv422.go => cam/yuyv422.go (84%) diff --git a/camera.go b/cam/camera.go similarity index 85% rename from camera.go rename to cam/camera.go index dd0ac0a..4cb53de 100644 --- a/camera.go +++ b/cam/camera.go @@ -1,9 +1,11 @@ -package main +// package cam is an snapshot interface to a webcam. +// Still frames are returned as an image.Image. +package cam import ( "fmt" + "github.com/aamcrae/webcam" - "log" ) type snapshot struct { @@ -22,6 +24,7 @@ type Camera struct { stream chan snapshot } +// OpenCamera opens the webcam and creates the channels ready for use. func OpenCamera(name string) (*Camera, error) { c, err := webcam.Open(name) if err != nil { @@ -33,6 +36,7 @@ func OpenCamera(name string) (*Camera, error) { return camera, nil } +// Close releases all current frames and shuts down the webcam. func (c *Camera) Close() { c.stop <- struct{}{} // Flush any remaining frames. @@ -43,6 +47,7 @@ func (c *Camera) Close() { c.cam.Close() } +// Init initialises the webcam ready for use, and begins streaming. func (c *Camera) Init(format string, resolution string) error { // Get the supported formats and their descriptions. format_desc := c.cam.GetSupportedFormats() @@ -91,6 +96,7 @@ func (c *Camera) Init(format string, resolution string) error { return nil } +// GetFrame returns one frame from the camera. func (c *Camera) GetFrame() (Frame, error) { snap, ok := <-c.stream if !ok { @@ -112,12 +118,12 @@ func (c *Camera) capture() { case *webcam.Timeout: continue default: - log.Fatal(err) + panic(err) } frame, index, err := c.cam.GetFrame() if err != nil { - log.Fatal(err) + panic(err) } select { // Only executed if stream is ready to receive. @@ -133,7 +139,7 @@ func (c *Camera) capture() { } } -// Return map of supported formats and resolutions. +// Query returns a map of the supported formats and resolutions. func (c *Camera) Query() map[string][]string { m := map[string][]string{} formats := c.cam.GetSupportedFormats() @@ -149,7 +155,7 @@ func (c *Camera) Query() map[string][]string { return m } -// Get control value. +// GetControl returns the current value of a camera control. func (c *Camera) GetControl(name string) (int32, error) { id, err := getControlID(name) if err != nil { @@ -158,7 +164,7 @@ func (c *Camera) GetControl(name string) (int32, error) { return c.cam.GetControl(id) } -// Set control value. +// SetControl sets the selected camera control. func (c *Camera) SetControl(name string, value int32) error { id, err := getControlID(name) if err != nil { @@ -167,7 +173,7 @@ func (c *Camera) SetControl(name string, value int32) error { return c.cam.SetControl(id, value) } -// Return the appropriate ControlID for the control name. +// getControlID returns the appropriate ControlID for a user-friendly control name. func getControlID(name string) (webcam.ControlID, error) { var controls map[string]webcam.ControlID = map[string]webcam.ControlID{ "focus" : 0x009a090a, diff --git a/frame.go b/cam/frame.go similarity index 69% rename from frame.go rename to cam/frame.go index 2b1fe22..208bb53 100644 --- a/frame.go +++ b/cam/frame.go @@ -1,4 +1,4 @@ -package main +package cam import ( "fmt" @@ -12,11 +12,13 @@ type Frame interface { var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} +// RegisterFramer registers a frame handler for a particular format. +// Note that only one handler can be registered for any format. func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { frameHandlers[format] = handler } -// Return a function that wraps the frame for this format. +// GetFramer returns a function that wraps the frame for this format. func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { if f, ok := frameHandlers[format]; ok { return f, nil diff --git a/yuyv422.go b/cam/yuyv422.go similarity index 84% rename from yuyv422.go rename to cam/yuyv422.go index 2918eab..7e39899 100644 --- a/yuyv422.go +++ b/cam/yuyv422.go @@ -1,4 +1,4 @@ -package main +package cam import ( "fmt" @@ -13,11 +13,12 @@ type FrameYUYV422 struct { release func() } +// Register this framer for this format. func init() { RegisterFramer("YUYV 4:2:2", newFrameYUYV422) } -// Wrap a raw frame in a Frame so that it can be used as an image. +// Wrap a raw webcam frame in a Frame so that it can be used as an image. func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { expLen := 2 * x * y if len(f) != expLen { @@ -46,7 +47,7 @@ func (f *FrameYUYV422) At(x, y int) color.Color { } } -// Done with frame, release back to camera (if required) +// Done with frame, release back to camera (if required). func (f *FrameYUYV422) Release() { if f.release != nil { f.release() diff --git a/server.go b/server.go index 1af45fd..82e6b7e 100644 --- a/server.go +++ b/server.go @@ -9,6 +9,8 @@ import ( "strconv" "strings" "time" + + "github.com/aamcrae/imageserver/cam" ) var port = flag.Int("port", 8080, "Web server port number") @@ -28,12 +30,12 @@ func main() { if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } - cam, err := OpenCamera(*device) + cm, err := cam.OpenCamera(*device) if err != nil { log.Fatalf("%s: %v", *device, err) } - defer cam.Close() - if err := cam.Init(*format, *resolution); err != nil { + defer cm.Close() + if err := cm.Init(*format, *resolution); err != nil { log.Fatalf("Init failed: %v", err) } // Initialise camera controls. @@ -50,12 +52,12 @@ func main() { if err != nil { log.Fatalf("Bad control value: %s (%v)", control, err) } - if err = cam.SetControl(s[0], int32(val)); err != nil { + if err = cm.SetControl(s[0], int32(val)); err != nil { log.Fatalf("SetControl error: %s (%v)", control, err) } } http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - readImage(cam, w, r) + readImage(cm, w, r) })) url := fmt.Sprintf(":%d", *port) if *verbose { @@ -65,11 +67,11 @@ func main() { log.Fatal(s.ListenAndServe()) } -func readImage(cam *Camera, w http.ResponseWriter, r *http.Request) { +func readImage(cm *cam.Camera, w http.ResponseWriter, r *http.Request) { if *verbose { log.Printf("URL request: %v", r.URL) } - frame, err := cam.GetFrame() + frame, err := cm.GetFrame() if err != nil { log.Fatalf("Getframe: %v", err) } From 3702d0a76488aabcd1ce8b6a0b2cdc9bfc3bac6e Mon Sep 17 00:00:00 2001 From: amcrae Date: Tue, 15 Jan 2019 17:10:34 +1100 Subject: [PATCH 040/123] Added more info in getcontrols. --- examples/getcontrols/getcontrols.go | 44 +++++++++++++++++++++-------- v4l2.go | 2 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 5f89f68..24363dd 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -1,20 +1,40 @@ // Example program that reads the list of available controls and prints them. package main -import "github.com/blackjack/webcam" -import "fmt" +import ( + "flag" + "fmt" + + "github.com/aamcrae/webcam" +) + +var device = flag.String("input", "/dev/video0", "Input video device") func main() { - cam, err := webcam.Open("/dev/video0") - if err != nil { - panic(err.Error()) - } - defer cam.Close() + flag.Parse() + cam, err := webcam.Open("/dev/video0") + if err != nil { + panic(err.Error()) + } + defer cam.Close() - cmap := cam.GetControls() + fmap := cam.GetSupportedFormats() + fmt.Println("Available Formats: ") + for p, s := range fmap { + var pix []byte + for i := 0; i < 4; i++ { + pix = append(pix, byte(p >> uint(i * 8))) + } + fmt.Printf("ID:%08x ('%s') %s\n ", p, pix, s) + for _, fs := range cam.GetSupportedFrameSizes(p) { + fmt.Printf(" %s", fs.GetString()) + } + fmt.Printf("\n") + } - fmt.Println("Available controls: ") - for id, c := range cmap { - fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) - } + cmap := cam.GetControls() + fmt.Println("Available controls: ") + for id, c := range cmap { + fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) + } } diff --git a/v4l2.go b/v4l2.go index e81f26e..ad5d36d 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/blackjack/webcam/ioctl" + "github.com/aamcrae/webcam/ioctl" "golang.org/x/sys/unix" ) From 65ffcaaf00a1695ea66009e89956af52ca6c415d Mon Sep 17 00:00:00 2001 From: amcrae Date: Wed, 16 Jan 2019 07:58:06 +1100 Subject: [PATCH 041/123] Added mjpeg framer. --- cam/camera.go | 2 +- cam/mjpeg.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 38 +++++++++++++++++---------------- 3 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 cam/mjpeg.go diff --git a/cam/camera.go b/cam/camera.go index 4cb53de..b693561 100644 --- a/cam/camera.go +++ b/cam/camera.go @@ -76,7 +76,7 @@ func (c *Camera) Init(format string, resolution string) error { sz, ok := sizeMap[resolution] if !ok { - return fmt.Errorf("Unsupported resolution: %s", resolution) + return fmt.Errorf("Unsupported resolution: %s (allowed: %v)", resolution, sizeMap) } _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) diff --git a/cam/mjpeg.go b/cam/mjpeg.go new file mode 100644 index 0000000..fe3efcc --- /dev/null +++ b/cam/mjpeg.go @@ -0,0 +1,58 @@ +package cam + +import ( + "bytes" + "image" + "image/color" + _ "image/jpeg" + _ "image/png" + _ "image/gif" + "os" +) + +type FrameMJPEG struct { + img image.Image + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("Motion-JPEG", newFrameMJPEG) +} + +// Wrap a mjpeg still in a Frame so that it can be used as an image. +func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { + file, err := os.Create("/tmp/xfile") + if err != nil { + return nil, err + } + file.Write(f) + file.Close() + j, _, err := image.Decode(bytes.NewBuffer(f)) + if err != nil { + if rel != nil { + rel() + } + return nil, err + } + return &FrameMJPEG{img: j, release: rel}, nil +} + +func (f *FrameMJPEG) ColorModel() color.Model { + return f.img.ColorModel() +} + +func (f *FrameMJPEG) Bounds() image.Rectangle { + return f.img.Bounds() +} + +func (f *FrameMJPEG) At(x, y int) color.Color { + return f.img.At(x, y) +} + +// Done with frame, release back to camera (if required). +func (f *FrameMJPEG) Release() { + if f.release != nil { + f.release() + } +} diff --git a/server.go b/server.go index 82e6b7e..83dcc25 100644 --- a/server.go +++ b/server.go @@ -19,7 +19,7 @@ var resolution = flag.String("resolution", "800x600", "Selected resolution of ca var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") var controls = flag.String("controls", "focus=190,power_line_frequency=1", "Control parameters for camera") -var startDelay = flag.Int("delay", 5, "Delay at start (seconds)") +var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") func init() { @@ -39,23 +39,25 @@ func main() { log.Fatalf("Init failed: %v", err) } // Initialise camera controls. - for _, control := range strings.Split(*controls, ",") { - // If no parameter, assume bool and set to true. - s := strings.Split(control, "=") - if len(s) == 1 { - s = append(s, "true") - } - if len(s) != 2 { - log.Fatalf("Bad control option: %s", control) - } - val, err := strconv.Atoi(s[1]) - if err != nil { - log.Fatalf("Bad control value: %s (%v)", control, err) - } - if err = cm.SetControl(s[0], int32(val)); err != nil { - log.Fatalf("SetControl error: %s (%v)", control, err) - } - } + if len(*controls) != 0 { + for _, control := range strings.Split(*controls, ",") { + // If no parameter, assume bool and set to true. + s := strings.Split(control, "=") + if len(s) == 1 { + s = append(s, "true") + } + if len(s) != 2 { + log.Fatalf("Bad control option: %s", control) + } + val, err := strconv.Atoi(s[1]) + if err != nil { + log.Fatalf("Bad control value: %s (%v)", control, err) + } + if err = cm.SetControl(s[0], int32(val)); err != nil { + log.Fatalf("SetControl error: %s (%v)", control, err) + } + } + } http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { readImage(cm, w, r) })) From d781dcf225e1bb240df0485010f7c779dd2bf2cd Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 08:07:12 +1100 Subject: [PATCH 042/123] Added argument for device. --- examples/getcontrols/getcontrols.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 24363dd..38345c4 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -12,7 +12,7 @@ var device = flag.String("input", "/dev/video0", "Input video device") func main() { flag.Parse() - cam, err := webcam.Open("/dev/video0") + cam, err := webcam.Open(*device) if err != nil { panic(err.Error()) } From 8a6afa015ead94c4da499013a2a8d24b339bf73c Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 08:12:28 +1100 Subject: [PATCH 043/123] Add check for control query. --- v4l2.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index ad5d36d..4334729 100644 --- a/v4l2.go +++ b/v4l2.go @@ -2,6 +2,7 @@ package webcam import ( "bytes" + "fmt" "encoding/binary" "unsafe" @@ -502,7 +503,9 @@ func queryControls(fd uintptr) []control { c.min = query.minimum c.max = query.maximum controls = append(controls, c) - } + } else { + fmt.Printf("id = %08x, Err = %v, %T\n", id, err, err) + } } return controls } From 9cf2f4ee93c6c8217d075d9cf41a7249f436b3ee Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 16:07:43 +1100 Subject: [PATCH 044/123] Added check for missing Huffman tables. --- cam/mjpeg.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/cam/mjpeg.go b/cam/mjpeg.go index fe3efcc..10879a9 100644 --- a/cam/mjpeg.go +++ b/cam/mjpeg.go @@ -2,12 +2,10 @@ package cam import ( "bytes" + "fmt" "image" "image/color" - _ "image/jpeg" - _ "image/png" - _ "image/gif" - "os" + "image/jpeg" ) type FrameMJPEG struct { @@ -15,20 +13,96 @@ type FrameMJPEG struct { release func() } +const ( + sectionFlag = 0xFF + soiMarker = 0xd8 + eoiMarker = 0xd9 + dhtMarker = 0xc4 + sosMarker = 0xda +) + +// Default Huffman tables. +var default_dht []byte = []byte { + 0xff, 0xc4, 0x01, 0xa2, + + 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + + 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, + 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + + 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, + 0x04, 0x00, 0x01, 0x02, 0x77, + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, + 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, + 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, + 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, +} + // Register this framer for this format. func init() { RegisterFramer("Motion-JPEG", newFrameMJPEG) } -// Wrap a mjpeg still in a Frame so that it can be used as an image. +// Wrap a mjpeg block in a Frame so that it can be used as an image. +// The standard jpeg decoding does not work if there are no Huffman tables, +// so check the frame and add a default table if required. func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { - file, err := os.Create("/tmp/xfile") + sect, err := findConfig(f) if err != nil { - return nil, err + if rel != nil { + rel() + } + return nil, err + } + _, ok := sect[dhtMarker] + var buf *bytes.Buffer + if !ok { + s, ok := sect[sosMarker] + if !ok { + return nil, fmt.Errorf("no scan data in image") + } + // Insert the default Huffman table before the start + // of the scan data. + ins := s[0] + buf = bytes.NewBuffer(f[:ins]) + buf.Write(default_dht) + buf.Write(f[ins:]) + } else { + buf = bytes.NewBuffer(f) } - file.Write(f) - file.Close() - j, _, err := image.Decode(bytes.NewBuffer(f)) + j, err := jpeg.Decode(buf) if err != nil { if rel != nil { rel() @@ -56,3 +130,29 @@ func (f *FrameMJPEG) Release() { f.release() } } + +// findConfig returns a map of the different config markers and their location. +func findConfig(f []byte) (map[byte][]int, error) { + m := make(map[byte][]int) + for l := 0; l < len(f) - 1; { + if f[l] != sectionFlag { + return nil, fmt.Errorf("No section marker at location %d", l) + } + l++ + marker := f[l] + m[marker] = append(m[marker], l - 1) + l++ + if marker == soiMarker || marker == eoiMarker { + continue; + } + if marker == sosMarker { + break + } + // next 2 bytes are length of the section (big-endian). + if l >= len(f) - 2 { + return nil, fmt.Errorf("unexpected EOF at location %d", l) + } + l += int((f[l] << 8) + f[l+1]) + } + return m, nil +} From f9d7f1f98c4ec3043f8ee7c3ec569362a8359c77 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 16:42:59 +1100 Subject: [PATCH 045/123] Formatted. --- cam/camera.go | 38 +++++------ cam/frame.go | 2 +- cam/mjpeg.go | 168 +++++++++++++++++++++++++------------------------ cam/yuyv422.go | 2 +- server.go | 40 ++++++------ 5 files changed, 126 insertions(+), 124 deletions(-) diff --git a/cam/camera.go b/cam/camera.go index b693561..9a2eb51 100644 --- a/cam/camera.go +++ b/cam/camera.go @@ -157,33 +157,33 @@ func (c *Camera) Query() map[string][]string { // GetControl returns the current value of a camera control. func (c *Camera) GetControl(name string) (int32, error) { - id, err := getControlID(name) - if err != nil { - return 0, err - } + id, err := getControlID(name) + if err != nil { + return 0, err + } return c.cam.GetControl(id) } // SetControl sets the selected camera control. func (c *Camera) SetControl(name string, value int32) error { - id, err := getControlID(name) - if err != nil { - return err - } + id, err := getControlID(name) + if err != nil { + return err + } return c.cam.SetControl(id, value) } // getControlID returns the appropriate ControlID for a user-friendly control name. func getControlID(name string) (webcam.ControlID, error) { - var controls map[string]webcam.ControlID = map[string]webcam.ControlID{ - "focus" : 0x009a090a, - "power_line_frequency" : 0x00980918, - "brightness" : 0x00980900, - "contrast" : 0x00980901, - } - id, ok := controls[name] - if !ok { - return 0, fmt.Errorf("%s: unknown control") - } - return id, nil + var controls map[string]webcam.ControlID = map[string]webcam.ControlID{ + "focus": 0x009a090a, + "power_line_frequency": 0x00980918, + "brightness": 0x00980900, + "contrast": 0x00980901, + } + id, ok := controls[name] + if !ok { + return 0, fmt.Errorf("%s: unknown control") + } + return id, nil } diff --git a/cam/frame.go b/cam/frame.go index 208bb53..d0c8a27 100644 --- a/cam/frame.go +++ b/cam/frame.go @@ -15,7 +15,7 @@ var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} // RegisterFramer registers a frame handler for a particular format. // Note that only one handler can be registered for any format. func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { - frameHandlers[format] = handler + frameHandlers[format] = handler } // GetFramer returns a function that wraps the frame for this format. diff --git a/cam/mjpeg.go b/cam/mjpeg.go index 10879a9..a65cbc9 100644 --- a/cam/mjpeg.go +++ b/cam/mjpeg.go @@ -1,115 +1,117 @@ package cam import ( - "bytes" - "fmt" + "bytes" + "fmt" "image" "image/color" "image/jpeg" ) type FrameMJPEG struct { - img image.Image + img image.Image release func() } const ( - sectionFlag = 0xFF - soiMarker = 0xd8 - eoiMarker = 0xd9 - dhtMarker = 0xc4 - sosMarker = 0xda + sectionFlag = 0xFF + soiMarker = 0xd8 + eoiMarker = 0xd9 + dhtMarker = 0xc4 + sosMarker = 0xda ) // Default Huffman tables. -var default_dht []byte = []byte { - 0xff, 0xc4, 0x01, 0xa2, +var default_dht []byte = []byte{ + 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, - 0x04, 0x00, 0x00, 0x01, 0x7d, + 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, - 0x13, 0x51, 0x61, 0x07, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, - 0x15, 0x52, 0xd1, 0xf0, + 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, - 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, - 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, - 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, - 0x04, 0x00, 0x01, 0x02, 0x77, + 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, - 0x51, 0x07, 0x61, 0x71, + 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, - 0x23, 0x33, 0x52, 0xf0, + 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, - 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, - 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, - 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, } // Register this framer for this format. func init() { - RegisterFramer("Motion-JPEG", newFrameMJPEG) + RegisterFramer("Motion-JPEG", newFrameMJPEG) } // Wrap a mjpeg block in a Frame so that it can be used as an image. // The standard jpeg decoding does not work if there are no Huffman tables, // so check the frame and add a default table if required. func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { - sect, err := findConfig(f) - if err != nil { + img, err := decodeMJPEG(f) + if err != nil { if rel != nil { rel() } return nil, err } - _, ok := sect[dhtMarker] - var buf *bytes.Buffer - if !ok { - s, ok := sect[sosMarker] - if !ok { - return nil, fmt.Errorf("no scan data in image") - } - // Insert the default Huffman table before the start - // of the scan data. - ins := s[0] - buf = bytes.NewBuffer(f[:ins]) - buf.Write(default_dht) - buf.Write(f[ins:]) - } else { - buf = bytes.NewBuffer(f) - } - j, err := jpeg.Decode(buf) - if err != nil { - if rel != nil { - rel() - } + return &FrameMJPEG{img: img, release: rel}, nil +} + +// decodeMJPEG decodes the frame into an image. +func decodeMJPEG(f []byte) (image.Image, error) { + sect, err := findConfig(f) + if err != nil { return nil, err } - return &FrameMJPEG{img: j, release: rel}, nil + _, ok := sect[dhtMarker] + var buf *bytes.Buffer + if !ok { + s, ok := sect[sosMarker] + if !ok { + return nil, fmt.Errorf("no scan data in image") + } + // Insert the default Huffman table before the start + // of the scan data. + ins := s[0] + buf = bytes.NewBuffer(f[:ins]) + buf.Write(default_dht) + buf.Write(f[ins:]) + } else { + buf = bytes.NewBuffer(f) + } + return jpeg.Decode(buf) } func (f *FrameMJPEG) ColorModel() color.Model { @@ -121,7 +123,7 @@ func (f *FrameMJPEG) Bounds() image.Rectangle { } func (f *FrameMJPEG) At(x, y int) color.Color { - return f.img.At(x, y) + return f.img.At(x, y) } // Done with frame, release back to camera (if required). @@ -133,26 +135,26 @@ func (f *FrameMJPEG) Release() { // findConfig returns a map of the different config markers and their location. func findConfig(f []byte) (map[byte][]int, error) { - m := make(map[byte][]int) - for l := 0; l < len(f) - 1; { - if f[l] != sectionFlag { - return nil, fmt.Errorf("No section marker at location %d", l) - } - l++ - marker := f[l] - m[marker] = append(m[marker], l - 1) - l++ - if marker == soiMarker || marker == eoiMarker { - continue; - } - if marker == sosMarker { - break - } - // next 2 bytes are length of the section (big-endian). - if l >= len(f) - 2 { - return nil, fmt.Errorf("unexpected EOF at location %d", l) - } - l += int((f[l] << 8) + f[l+1]) - } - return m, nil + m := make(map[byte][]int) + for l := 0; l < len(f)-1; { + if f[l] != sectionFlag { + return nil, fmt.Errorf("No section marker at location %d", l) + } + l++ + marker := f[l] + m[marker] = append(m[marker], l-1) + l++ + if marker == soiMarker || marker == eoiMarker { + continue + } + if marker == sosMarker { + break + } + // next 2 bytes are length of the section (big-endian). + if l >= len(f)-2 { + return nil, fmt.Errorf("unexpected EOF at location %d", l) + } + l += int((f[l] << 8) + f[l+1]) + } + return m, nil } diff --git a/cam/yuyv422.go b/cam/yuyv422.go index 7e39899..342cad6 100644 --- a/cam/yuyv422.go +++ b/cam/yuyv422.go @@ -15,7 +15,7 @@ type FrameYUYV422 struct { // Register this framer for this format. func init() { - RegisterFramer("YUYV 4:2:2", newFrameYUYV422) + RegisterFramer("YUYV 4:2:2", newFrameYUYV422) } // Wrap a raw webcam frame in a Frame so that it can be used as an image. diff --git a/server.go b/server.go index 83dcc25..d168f2f 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/aamcrae/imageserver/cam" + "github.com/aamcrae/imageserver/cam" ) var port = flag.Int("port", 8080, "Web server port number") @@ -39,25 +39,25 @@ func main() { log.Fatalf("Init failed: %v", err) } // Initialise camera controls. - if len(*controls) != 0 { - for _, control := range strings.Split(*controls, ",") { - // If no parameter, assume bool and set to true. - s := strings.Split(control, "=") - if len(s) == 1 { - s = append(s, "true") - } - if len(s) != 2 { - log.Fatalf("Bad control option: %s", control) - } - val, err := strconv.Atoi(s[1]) - if err != nil { - log.Fatalf("Bad control value: %s (%v)", control, err) - } - if err = cm.SetControl(s[0], int32(val)); err != nil { - log.Fatalf("SetControl error: %s (%v)", control, err) - } - } - } + if len(*controls) != 0 { + for _, control := range strings.Split(*controls, ",") { + // If no parameter, assume bool and set to true. + s := strings.Split(control, "=") + if len(s) == 1 { + s = append(s, "true") + } + if len(s) != 2 { + log.Fatalf("Bad control option: %s", control) + } + val, err := strconv.Atoi(s[1]) + if err != nil { + log.Fatalf("Bad control value: %s (%v)", control, err) + } + if err = cm.SetControl(s[0], int32(val)); err != nil { + log.Fatalf("SetControl error: %s (%v)", control, err) + } + } + } http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { readImage(cm, w, r) })) From 494037987265afec5c4cb7470a1bba58e8ae1e8f Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 17:13:32 +1100 Subject: [PATCH 046/123] Stop control query on any error. --- v4l2.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/v4l2.go b/v4l2.go index 4334729..4b9ab3a 100644 --- a/v4l2.go +++ b/v4l2.go @@ -477,7 +477,7 @@ func queryControls(fd uintptr) []control { var err error // Don't use V42L_CID_BASE since it is the same as brightness. var id uint32 - for err != ioctl.ErrEINVAL { + for err == nil { id |= V4L2_CTRL_FLAG_NEXT_CTRL query := &v4l2_queryctrl{} query.id = id @@ -503,9 +503,7 @@ func queryControls(fd uintptr) []control { c.min = query.minimum c.max = query.maximum controls = append(controls, c) - } else { - fmt.Printf("id = %08x, Err = %v, %T\n", id, err, err) - } + } } return controls } From b91eaeecf4abbf194dcaefdf178e85ed8e9fa915 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 17:14:34 +1100 Subject: [PATCH 047/123] Formatted. --- examples/getcontrols/getcontrols.go | 54 ++++++++++++++--------------- v4l2.go | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 38345c4..9da10f5 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -2,39 +2,39 @@ package main import ( - "flag" - "fmt" + "flag" + "fmt" - "github.com/aamcrae/webcam" + "github.com/aamcrae/webcam" ) var device = flag.String("input", "/dev/video0", "Input video device") func main() { - flag.Parse() - cam, err := webcam.Open(*device) - if err != nil { - panic(err.Error()) - } - defer cam.Close() + flag.Parse() + cam, err := webcam.Open(*device) + if err != nil { + panic(err.Error()) + } + defer cam.Close() - fmap := cam.GetSupportedFormats() - fmt.Println("Available Formats: ") - for p, s := range fmap { - var pix []byte - for i := 0; i < 4; i++ { - pix = append(pix, byte(p >> uint(i * 8))) - } - fmt.Printf("ID:%08x ('%s') %s\n ", p, pix, s) - for _, fs := range cam.GetSupportedFrameSizes(p) { - fmt.Printf(" %s", fs.GetString()) - } - fmt.Printf("\n") - } + fmap := cam.GetSupportedFormats() + fmt.Println("Available Formats: ") + for p, s := range fmap { + var pix []byte + for i := 0; i < 4; i++ { + pix = append(pix, byte(p>>uint(i*8))) + } + fmt.Printf("ID:%08x ('%s') %s\n ", p, pix, s) + for _, fs := range cam.GetSupportedFrameSizes(p) { + fmt.Printf(" %s", fs.GetString()) + } + fmt.Printf("\n") + } - cmap := cam.GetControls() - fmt.Println("Available controls: ") - for id, c := range cmap { - fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) - } + cmap := cam.GetControls() + fmt.Println("Available controls: ") + for id, c := range cmap { + fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) + } } diff --git a/v4l2.go b/v4l2.go index 4b9ab3a..5da3dbb 100644 --- a/v4l2.go +++ b/v4l2.go @@ -2,8 +2,8 @@ package webcam import ( "bytes" - "fmt" "encoding/binary" + "fmt" "unsafe" "github.com/aamcrae/webcam/ioctl" From 87c2c282d91d4151f9c0abc137c2a7fa3bf5730a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 19:04:58 +1100 Subject: [PATCH 048/123] Fixed huffman table processing. --- cam/mjpeg.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cam/mjpeg.go b/cam/mjpeg.go index a65cbc9..ba115f0 100644 --- a/cam/mjpeg.go +++ b/cam/mjpeg.go @@ -105,7 +105,8 @@ func decodeMJPEG(f []byte) (image.Image, error) { // Insert the default Huffman table before the start // of the scan data. ins := s[0] - buf = bytes.NewBuffer(f[:ins]) + buf = new(bytes.Buffer) + buf.Write(f[:ins]) buf.Write(default_dht) buf.Write(f[ins:]) } else { From ecf5d70168916198aa113ce4825c80ffcdad5324 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 19:42:07 +1100 Subject: [PATCH 049/123] Split cam to camera and frame. --- {cam => camera}/camera.go | 16 ++++++++-------- {cam => frame}/frame.go | 3 ++- {cam => frame}/mjpeg.go | 2 +- {cam => frame}/yuyv422.go | 2 +- server.go | 6 +++--- 5 files changed, 15 insertions(+), 14 deletions(-) rename {cam => camera}/camera.go (91%) rename {cam => frame}/frame.go (91%) rename {cam => frame}/mjpeg.go (99%) rename {cam => frame}/yuyv422.go (98%) diff --git a/cam/camera.go b/camera/camera.go similarity index 91% rename from cam/camera.go rename to camera/camera.go index 9a2eb51..02ff9dc 100644 --- a/cam/camera.go +++ b/camera/camera.go @@ -1,10 +1,10 @@ -// package cam is an snapshot interface to a webcam. -// Still frames are returned as an image.Image. -package cam +// package camera is an snapshot interface to a webcam. +package camera import ( "fmt" + "github.com/aamcrae/imageserver/frame" "github.com/aamcrae/webcam" ) @@ -19,13 +19,13 @@ type Camera struct { Height int Format string Timeout uint32 - newFrame func(int, int, []byte, func()) (Frame, error) + newFrame func(int, int, []byte, func()) (frame.Frame, error) stop chan struct{} stream chan snapshot } -// OpenCamera opens the webcam and creates the channels ready for use. -func OpenCamera(name string) (*Camera, error) { +// Open opens the webcam and creates the channels ready for use. +func Open(name string) (*Camera, error) { c, err := webcam.Open(name) if err != nil { return nil, err @@ -64,7 +64,7 @@ func (c *Camera) Init(format string, resolution string) error { return fmt.Errorf("Camera does not support this format: %s", format) } var err error - if c.newFrame, err = GetFramer(format); err != nil { + if c.newFrame, err = frame.GetFramer(format); err != nil { return err } @@ -97,7 +97,7 @@ func (c *Camera) Init(format string, resolution string) error { } // GetFrame returns one frame from the camera. -func (c *Camera) GetFrame() (Frame, error) { +func (c *Camera) GetFrame() (frame.Frame, error) { snap, ok := <-c.stream if !ok { return nil, fmt.Errorf("No frame received") diff --git a/cam/frame.go b/frame/frame.go similarity index 91% rename from cam/frame.go rename to frame/frame.go index d0c8a27..de9d413 100644 --- a/cam/frame.go +++ b/frame/frame.go @@ -1,4 +1,5 @@ -package cam +// package frame wraps raw webcam frames as an image. +package frame import ( "fmt" diff --git a/cam/mjpeg.go b/frame/mjpeg.go similarity index 99% rename from cam/mjpeg.go rename to frame/mjpeg.go index ba115f0..79d4fab 100644 --- a/cam/mjpeg.go +++ b/frame/mjpeg.go @@ -1,4 +1,4 @@ -package cam +package frame import ( "bytes" diff --git a/cam/yuyv422.go b/frame/yuyv422.go similarity index 98% rename from cam/yuyv422.go rename to frame/yuyv422.go index 342cad6..a83f6a8 100644 --- a/cam/yuyv422.go +++ b/frame/yuyv422.go @@ -1,4 +1,4 @@ -package cam +package frame import ( "fmt" diff --git a/server.go b/server.go index d168f2f..f9acc25 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/aamcrae/imageserver/cam" + "github.com/aamcrae/imageserver/camera" ) var port = flag.Int("port", 8080, "Web server port number") @@ -30,7 +30,7 @@ func main() { if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } - cm, err := cam.OpenCamera(*device) + cm, err := camera.Open(*device) if err != nil { log.Fatalf("%s: %v", *device, err) } @@ -69,7 +69,7 @@ func main() { log.Fatal(s.ListenAndServe()) } -func readImage(cm *cam.Camera, w http.ResponseWriter, r *http.Request) { +func readImage(cm *camera.Camera, w http.ResponseWriter, r *http.Request) { if *verbose { log.Printf("URL request: %v", r.URL) } From 914d20a49beb00131d003a98d131aac2eaf4f822 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 16 Jan 2019 19:42:07 +1100 Subject: [PATCH 050/123] Split cam to camera and frame. --- frame.go | 28 ++++++++++ mjpeg.go | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yuyv422.go | 55 ++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 frame.go create mode 100644 mjpeg.go create mode 100644 yuyv422.go diff --git a/frame.go b/frame.go new file mode 100644 index 0000000..de9d413 --- /dev/null +++ b/frame.go @@ -0,0 +1,28 @@ +// package frame wraps raw webcam frames as an image. +package frame + +import ( + "fmt" + "image" +) + +type Frame interface { + image.Image + Release() +} + +var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} + +// RegisterFramer registers a frame handler for a particular format. +// Note that only one handler can be registered for any format. +func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { + frameHandlers[format] = handler +} + +// GetFramer returns a function that wraps the frame for this format. +func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { + if f, ok := frameHandlers[format]; ok { + return f, nil + } + return nil, fmt.Errorf("No handler for format '%s'", format) +} diff --git a/mjpeg.go b/mjpeg.go new file mode 100644 index 0000000..79d4fab --- /dev/null +++ b/mjpeg.go @@ -0,0 +1,161 @@ +package frame + +import ( + "bytes" + "fmt" + "image" + "image/color" + "image/jpeg" +) + +type FrameMJPEG struct { + img image.Image + release func() +} + +const ( + sectionFlag = 0xFF + soiMarker = 0xd8 + eoiMarker = 0xd9 + dhtMarker = 0xc4 + sosMarker = 0xda +) + +// Default Huffman tables. +var default_dht []byte = []byte{ + 0xff, 0xc4, 0x01, 0xa2, + + 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + + 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, + 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + + 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, + 0x04, 0x00, 0x01, 0x02, 0x77, + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, + 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, + 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, + 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, +} + +// Register this framer for this format. +func init() { + RegisterFramer("Motion-JPEG", newFrameMJPEG) +} + +// Wrap a mjpeg block in a Frame so that it can be used as an image. +// The standard jpeg decoding does not work if there are no Huffman tables, +// so check the frame and add a default table if required. +func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { + img, err := decodeMJPEG(f) + if err != nil { + if rel != nil { + rel() + } + return nil, err + } + return &FrameMJPEG{img: img, release: rel}, nil +} + +// decodeMJPEG decodes the frame into an image. +func decodeMJPEG(f []byte) (image.Image, error) { + sect, err := findConfig(f) + if err != nil { + return nil, err + } + _, ok := sect[dhtMarker] + var buf *bytes.Buffer + if !ok { + s, ok := sect[sosMarker] + if !ok { + return nil, fmt.Errorf("no scan data in image") + } + // Insert the default Huffman table before the start + // of the scan data. + ins := s[0] + buf = new(bytes.Buffer) + buf.Write(f[:ins]) + buf.Write(default_dht) + buf.Write(f[ins:]) + } else { + buf = bytes.NewBuffer(f) + } + return jpeg.Decode(buf) +} + +func (f *FrameMJPEG) ColorModel() color.Model { + return f.img.ColorModel() +} + +func (f *FrameMJPEG) Bounds() image.Rectangle { + return f.img.Bounds() +} + +func (f *FrameMJPEG) At(x, y int) color.Color { + return f.img.At(x, y) +} + +// Done with frame, release back to camera (if required). +func (f *FrameMJPEG) Release() { + if f.release != nil { + f.release() + } +} + +// findConfig returns a map of the different config markers and their location. +func findConfig(f []byte) (map[byte][]int, error) { + m := make(map[byte][]int) + for l := 0; l < len(f)-1; { + if f[l] != sectionFlag { + return nil, fmt.Errorf("No section marker at location %d", l) + } + l++ + marker := f[l] + m[marker] = append(m[marker], l-1) + l++ + if marker == soiMarker || marker == eoiMarker { + continue + } + if marker == sosMarker { + break + } + // next 2 bytes are length of the section (big-endian). + if l >= len(f)-2 { + return nil, fmt.Errorf("unexpected EOF at location %d", l) + } + l += int((f[l] << 8) + f[l+1]) + } + return m, nil +} diff --git a/yuyv422.go b/yuyv422.go new file mode 100644 index 0000000..a83f6a8 --- /dev/null +++ b/yuyv422.go @@ -0,0 +1,55 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type FrameYUYV422 struct { + model color.Model + b image.Rectangle + frame []byte + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("YUYV 4:2:2", newFrameYUYV422) +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { + expLen := 2 * x * y + if len(f) != expLen { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + } + return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel}, nil +} + +func (f *FrameYUYV422) ColorModel() color.Model { + return f.model +} + +func (f *FrameYUYV422) Bounds() image.Rectangle { + return f.b +} + +func (f *FrameYUYV422) At(x, y int) color.Color { + index := f.b.Max.X*y*2 + (x&^1)*2 + if x&1 == 0 { + return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} + } else { + return color.YCbCr{f.frame[index+2], f.frame[index+1], f.frame[index+3]} + } +} + +// Done with frame, release back to camera (if required). +func (f *FrameYUYV422) Release() { + if f.release != nil { + f.release() + } +} From cc89b6696e2d88ec8e5f3faefe870ba797cd9820 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 09:56:17 +1100 Subject: [PATCH 051/123] Fixed unused import. --- v4l2.go | 1 - 1 file changed, 1 deletion(-) diff --git a/v4l2.go b/v4l2.go index 5da3dbb..cef33aa 100644 --- a/v4l2.go +++ b/v4l2.go @@ -3,7 +3,6 @@ package webcam import ( "bytes" "encoding/binary" - "fmt" "unsafe" "github.com/aamcrae/webcam/ioctl" From b9930e970d802083f639645cd662240a12f3fecc Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 10:01:24 +1100 Subject: [PATCH 052/123] Removed unused errno reference. Fixed imports. --- examples/getcontrols/getcontrols.go | 2 +- ioctl/ioctl.go | 2 -- v4l2.go | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 9da10f5..eefd062 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -5,7 +5,7 @@ import ( "flag" "fmt" - "github.com/aamcrae/webcam" + "github.com/blackjack/webcam" ) var device = flag.String("input", "/dev/video0", "Input video device") diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index a1118f3..3ce8df6 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -21,8 +21,6 @@ const ( typeShift = numberShift + numberBits sizeShift = typeShift + typeBits directionShift = sizeShift + sizeBits - - ErrEINVAL = unix.EINVAL ) func ioc(dir, t, nr, size uintptr) uintptr { diff --git a/v4l2.go b/v4l2.go index cef33aa..e573783 100644 --- a/v4l2.go +++ b/v4l2.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "unsafe" - "github.com/aamcrae/webcam/ioctl" + "github.com/blackjack/webcam/ioctl" "golang.org/x/sys/unix" ) From 0be3d720cffc0090d25147a15f226d45b6a2a62a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 11:28:02 +1100 Subject: [PATCH 053/123] Release call via defer. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index f9acc25..f7b827c 100644 --- a/server.go +++ b/server.go @@ -77,11 +77,11 @@ func readImage(cm *camera.Camera, w http.ResponseWriter, r *http.Request) { if err != nil { log.Fatalf("Getframe: %v", err) } + defer frame.Release() w.Header().Set("Content-Type", "image/jpeg") if err := jpeg.Encode(w, frame, nil); err != nil { log.Printf("Error writing image: %v\n", err) } else if *verbose { log.Printf("Wrote image successfully\n") } - frame.Release() } From 4c8af693d582e473db3bc1d53bc5929957ff7039 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 13:54:40 +1100 Subject: [PATCH 054/123] Renamed camera to snapper. --- server.go | 33 ++++--- camera/camera.go => snapshot/snapshot.go | 104 ++++++++++------------- 2 files changed, 65 insertions(+), 72 deletions(-) rename camera/camera.go => snapshot/snapshot.go (58%) diff --git a/server.go b/server.go index f7b827c..0ec4c88 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,8 @@ import ( "strings" "time" - "github.com/aamcrae/imageserver/camera" + "github.com/aamcrae/imageserver/snapshot" + "github.com/aamcrae/webcam" ) var port = flag.Int("port", 8080, "Web server port number") @@ -22,6 +23,13 @@ var controls = flag.String("controls", "focus=190,power_line_frequency=1", var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") +var cnames map[string]webcam.ControlID = map[string]webcam.ControlID{ + "focus": 0x009a090a, + "power_line_frequency": 0x00980918, + "brightness": 0x00980900, + "contrast": 0x00980901, +} + func init() { flag.Parse() } @@ -30,30 +38,27 @@ func main() { if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } - cm, err := camera.Open(*device) - if err != nil { + cm := snapshot.NewSnapper() + if err := cm.Open(*device, *format, *resolution); err != nil { log.Fatalf("%s: %v", *device, err) } defer cm.Close() - if err := cm.Init(*format, *resolution); err != nil { - log.Fatalf("Init failed: %v", err) - } - // Initialise camera controls. + // Set camera controls. if len(*controls) != 0 { for _, control := range strings.Split(*controls, ",") { - // If no parameter, assume bool and set to true. s := strings.Split(control, "=") - if len(s) == 1 { - s = append(s, "true") - } if len(s) != 2 { log.Fatalf("Bad control option: %s", control) } + id, ok := cnames[s[0]] + if !ok { + log.Fatalf("%s: Unknown control", s[0]) + } val, err := strconv.Atoi(s[1]) if err != nil { log.Fatalf("Bad control value: %s (%v)", control, err) } - if err = cm.SetControl(s[0], int32(val)); err != nil { + if err = cm.SetControl(id, int32(val)); err != nil { log.Fatalf("SetControl error: %s (%v)", control, err) } } @@ -69,11 +74,11 @@ func main() { log.Fatal(s.ListenAndServe()) } -func readImage(cm *camera.Camera, w http.ResponseWriter, r *http.Request) { +func readImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request) { if *verbose { log.Printf("URL request: %v", r.URL) } - frame, err := cm.GetFrame() + frame, err := cm.Snap() if err != nil { log.Fatalf("Getframe: %v", err) } diff --git a/camera/camera.go b/snapshot/snapshot.go similarity index 58% rename from camera/camera.go rename to snapshot/snapshot.go index 02ff9dc..84a6ba7 100644 --- a/camera/camera.go +++ b/snapshot/snapshot.go @@ -1,5 +1,5 @@ -// package camera is an snapshot interface to a webcam. -package camera +// package snapshot is an webcam stills capture module. +package snapshot import ( "fmt" @@ -8,47 +8,59 @@ import ( "github.com/aamcrae/webcam" ) -type snapshot struct { +const ( + defaultTimeout = 5 + defaultBuffers = 16 +) + +type snap struct { frame []byte index uint32 } -type Camera struct { +type Snapper struct { cam *webcam.Webcam Width int Height int Format string Timeout uint32 + Buffers uint32 newFrame func(int, int, []byte, func()) (frame.Frame, error) stop chan struct{} - stream chan snapshot + stream chan snap } -// Open opens the webcam and creates the channels ready for use. -func Open(name string) (*Camera, error) { - c, err := webcam.Open(name) - if err != nil { - return nil, err - } - camera := &Camera{cam: c, Timeout: 5} - camera.stop = make(chan struct{}, 1) - camera.stream = make(chan snapshot, 0) - return camera, nil +// NewSnapper creates a new Snapper. +func NewSnapper() *Snapper { + return &Snapper{Timeout: defaultTimeout, Buffers: defaultBuffers} } // Close releases all current frames and shuts down the webcam. -func (c *Camera) Close() { - c.stop <- struct{}{} - // Flush any remaining frames. - for f := range c.stream { - c.cam.ReleaseFrame(f.index) +func (c *Snapper) Close() { + if c.cam != nil { + c.stop <- struct{}{} + // Flush any remaining frames. + for f := range c.stream { + c.cam.ReleaseFrame(f.index) + } + c.cam.StopStreaming() + c.cam.Close() + c.cam = nil } - c.cam.StopStreaming() - c.cam.Close() } -// Init initialises the webcam ready for use, and begins streaming. -func (c *Camera) Init(format string, resolution string) error { +// Open initialises the webcam ready for use, and begins streaming. +func (c *Snapper) Open(device, format, resolution string) error { + if c.cam != nil { + c.Close() + } + cam, err := webcam.Open(device) + if err != nil { + return err + } + c.cam = cam + c.stop = make(chan struct{}, 1) + c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. format_desc := c.cam.GetSupportedFormats() var pixelFormat webcam.PixelFormat @@ -61,9 +73,8 @@ func (c *Camera) Init(format string, resolution string) error { } } if !found { - return fmt.Errorf("Camera does not support this format: %s", format) + return fmt.Errorf("%s: unsupported format: %s", device, format) } - var err error if c.newFrame, err = frame.GetFramer(format); err != nil { return err } @@ -76,7 +87,7 @@ func (c *Camera) Init(format string, resolution string) error { sz, ok := sizeMap[resolution] if !ok { - return fmt.Errorf("Unsupported resolution: %s (allowed: %v)", resolution, sizeMap) + return fmt.Errorf("%s: unsupported resolution: %s (allowed: %v)", device, resolution, sizeMap) } _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) @@ -87,7 +98,7 @@ func (c *Camera) Init(format string, resolution string) error { c.Width = int(w) c.Height = int(h) - c.cam.SetBufferCount(16) + c.cam.SetBufferCount(c.Buffers) c.cam.SetAutoWhiteBalance(true) if err := c.cam.StartStreaming(); err != nil { return err @@ -96,8 +107,8 @@ func (c *Camera) Init(format string, resolution string) error { return nil } -// GetFrame returns one frame from the camera. -func (c *Camera) GetFrame() (frame.Frame, error) { +// Snap returns one frame from the camera. +func (c *Snapper) Snap() (frame.Frame, error) { snap, ok := <-c.stream if !ok { return nil, fmt.Errorf("No frame received") @@ -109,7 +120,7 @@ func (c *Camera) GetFrame() (frame.Frame, error) { // capture continually reads frames and either discards them or // sends them to a channel that is ready to receive them. -func (c *Camera) capture() { +func (c *Snapper) capture() { for { err := c.cam.WaitForFrame(c.Timeout) @@ -127,7 +138,7 @@ func (c *Camera) capture() { } select { // Only executed if stream is ready to receive. - case c.stream <- snapshot{frame, index}: + case c.stream <- snap{frame, index}: case <-c.stop: // Finish up. c.cam.ReleaseFrame(index) @@ -140,7 +151,7 @@ func (c *Camera) capture() { } // Query returns a map of the supported formats and resolutions. -func (c *Camera) Query() map[string][]string { +func (c *Snapper) Query() map[string][]string { m := map[string][]string{} formats := c.cam.GetSupportedFormats() for f, fs := range formats { @@ -156,34 +167,11 @@ func (c *Camera) Query() map[string][]string { } // GetControl returns the current value of a camera control. -func (c *Camera) GetControl(name string) (int32, error) { - id, err := getControlID(name) - if err != nil { - return 0, err - } +func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { return c.cam.GetControl(id) } // SetControl sets the selected camera control. -func (c *Camera) SetControl(name string, value int32) error { - id, err := getControlID(name) - if err != nil { - return err - } +func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { return c.cam.SetControl(id, value) } - -// getControlID returns the appropriate ControlID for a user-friendly control name. -func getControlID(name string) (webcam.ControlID, error) { - var controls map[string]webcam.ControlID = map[string]webcam.ControlID{ - "focus": 0x009a090a, - "power_line_frequency": 0x00980918, - "brightness": 0x00980900, - "contrast": 0x00980901, - } - id, ok := controls[name] - if !ok { - return 0, fmt.Errorf("%s: unknown control") - } - return id, nil -} From c29f1681886144ba0b74a79869f79a1eb9050cb4 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 13:54:40 +1100 Subject: [PATCH 055/123] Renamed camera to snapper. --- snapshot.go | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 snapshot.go diff --git a/snapshot.go b/snapshot.go new file mode 100644 index 0000000..84a6ba7 --- /dev/null +++ b/snapshot.go @@ -0,0 +1,177 @@ +// package snapshot is an webcam stills capture module. +package snapshot + +import ( + "fmt" + + "github.com/aamcrae/imageserver/frame" + "github.com/aamcrae/webcam" +) + +const ( + defaultTimeout = 5 + defaultBuffers = 16 +) + +type snap struct { + frame []byte + index uint32 +} + +type Snapper struct { + cam *webcam.Webcam + Width int + Height int + Format string + Timeout uint32 + Buffers uint32 + newFrame func(int, int, []byte, func()) (frame.Frame, error) + stop chan struct{} + stream chan snap +} + +// NewSnapper creates a new Snapper. +func NewSnapper() *Snapper { + return &Snapper{Timeout: defaultTimeout, Buffers: defaultBuffers} +} + +// Close releases all current frames and shuts down the webcam. +func (c *Snapper) Close() { + if c.cam != nil { + c.stop <- struct{}{} + // Flush any remaining frames. + for f := range c.stream { + c.cam.ReleaseFrame(f.index) + } + c.cam.StopStreaming() + c.cam.Close() + c.cam = nil + } +} + +// Open initialises the webcam ready for use, and begins streaming. +func (c *Snapper) Open(device, format, resolution string) error { + if c.cam != nil { + c.Close() + } + cam, err := webcam.Open(device) + if err != nil { + return err + } + c.cam = cam + c.stop = make(chan struct{}, 1) + c.stream = make(chan snap, 0) + // Get the supported formats and their descriptions. + format_desc := c.cam.GetSupportedFormats() + var pixelFormat webcam.PixelFormat + var found bool + for k, v := range format_desc { + if v == format { + found = true + pixelFormat = k + break + } + } + if !found { + return fmt.Errorf("%s: unsupported format: %s", device, format) + } + if c.newFrame, err = frame.GetFramer(format); err != nil { + return err + } + + // Build a map of resolution names from the description. + sizeMap := make(map[string]webcam.FrameSize) + for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { + sizeMap[value.GetString()] = value + } + + sz, ok := sizeMap[resolution] + if !ok { + return fmt.Errorf("%s: unsupported resolution: %s (allowed: %v)", device, resolution, sizeMap) + } + + _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) + + if err != nil { + return err + } + c.Width = int(w) + c.Height = int(h) + + c.cam.SetBufferCount(c.Buffers) + c.cam.SetAutoWhiteBalance(true) + if err := c.cam.StartStreaming(); err != nil { + return err + } + go c.capture() + return nil +} + +// Snap returns one frame from the camera. +func (c *Snapper) Snap() (frame.Frame, error) { + snap, ok := <-c.stream + if !ok { + return nil, fmt.Errorf("No frame received") + } + return c.newFrame(c.Width, c.Height, snap.frame, func() { + c.cam.ReleaseFrame(snap.index) + }) +} + +// capture continually reads frames and either discards them or +// sends them to a channel that is ready to receive them. +func (c *Snapper) capture() { + for { + err := c.cam.WaitForFrame(c.Timeout) + + switch err.(type) { + case nil: + case *webcam.Timeout: + continue + default: + panic(err) + } + + frame, index, err := c.cam.GetFrame() + if err != nil { + panic(err) + } + select { + // Only executed if stream is ready to receive. + case c.stream <- snap{frame, index}: + case <-c.stop: + // Finish up. + c.cam.ReleaseFrame(index) + close(c.stream) + return + default: + c.cam.ReleaseFrame(index) + } + } +} + +// Query returns a map of the supported formats and resolutions. +func (c *Snapper) Query() map[string][]string { + m := map[string][]string{} + formats := c.cam.GetSupportedFormats() + for f, fs := range formats { + r := []string{} + for _, value := range c.cam.GetSupportedFrameSizes(f) { + if value.StepWidth == 0 && value.StepHeight == 0 { + r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) + } + } + m[fs] = r + } + return m +} + +// GetControl returns the current value of a camera control. +func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { + return c.cam.GetControl(id) +} + +// SetControl sets the selected camera control. +func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { + return c.cam.SetControl(id, value) +} From 670cca1f58135006037c6819de06e88a5cddd13b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 14:21:35 +1100 Subject: [PATCH 056/123] More comments. --- frame/frame.go | 4 ++-- server.go | 5 +++-- snapshot/snapshot.go | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frame/frame.go b/frame/frame.go index de9d413..ae5a30f 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -13,8 +13,8 @@ type Frame interface { var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} -// RegisterFramer registers a frame handler for a particular format. -// Note that only one handler can be registered for any format. +// RegisterFramer registers a frame handler for a format. +// Note that only one handler can be registered for any single format. func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { frameHandlers[format] = handler } diff --git a/server.go b/server.go index 0ec4c88..9585a76 100644 --- a/server.go +++ b/server.go @@ -1,3 +1,4 @@ +// Program that serves jpeg images taken from a webcam. package main import ( @@ -16,8 +17,8 @@ import ( var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") -var resolution = flag.String("resolution", "800x600", "Selected resolution of camera") -var format = flag.String("format", "YUYV 4:2:2", "Selected pixel format of camera") +var resolution = flag.String("resolution", "800x600", "Camera resolution") +var format = flag.String("format", "YUYV 4:2:2", "Pixel format of camera") var controls = flag.String("controls", "focus=190,power_line_frequency=1", "Control parameters for camera") var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 84a6ba7..8348365 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -1,4 +1,4 @@ -// package snapshot is an webcam stills capture module. +// package snapshot is a webcam stills capture module. package snapshot import ( @@ -60,7 +60,7 @@ func (c *Snapper) Open(device, format, resolution string) error { } c.cam = cam c.stop = make(chan struct{}, 1) - c.stream = make(chan snap, 0) + c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. format_desc := c.cam.GetSupportedFormats() var pixelFormat webcam.PixelFormat From c7769d513c7cd8071e1951b8c644f4184892c371 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 14:21:35 +1100 Subject: [PATCH 057/123] More comments. --- frame.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame.go b/frame.go index de9d413..ae5a30f 100644 --- a/frame.go +++ b/frame.go @@ -13,8 +13,8 @@ type Frame interface { var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} -// RegisterFramer registers a frame handler for a particular format. -// Note that only one handler can be registered for any format. +// RegisterFramer registers a frame handler for a format. +// Note that only one handler can be registered for any single format. func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { frameHandlers[format] = handler } From ae57abddb707de2b0ee683dcdd6187b756970fb1 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Thu, 17 Jan 2019 14:21:35 +1100 Subject: [PATCH 058/123] More comments. --- snapshot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapshot.go b/snapshot.go index 84a6ba7..8348365 100644 --- a/snapshot.go +++ b/snapshot.go @@ -1,4 +1,4 @@ -// package snapshot is an webcam stills capture module. +// package snapshot is a webcam stills capture module. package snapshot import ( @@ -60,7 +60,7 @@ func (c *Snapper) Open(device, format, resolution string) error { } c.cam = cam c.stop = make(chan struct{}, 1) - c.stream = make(chan snap, 0) + c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. format_desc := c.cam.GetSupportedFormats() var pixelFormat webcam.PixelFormat From d106d87c852a1528800cfd25cd3ac046cf3d85a9 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 13:51:16 +1100 Subject: [PATCH 059/123] Use PixelFormat for framers. Add jpeg framer. --- frame/frame.go | 28 +++++++++++++++-- frame/jpeg.go | 49 +++++++++++++++++++++++++++++ frame/mjpeg.go | 4 +-- frame/yuyv422.go | 18 ++++++++--- server.go | 26 +++++++++++----- snapshot/snapshot.go | 74 +++++++++++++++++++++----------------------- 6 files changed, 143 insertions(+), 56 deletions(-) create mode 100644 frame/jpeg.go diff --git a/frame/frame.go b/frame/frame.go index ae5a30f..70758dc 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -4,25 +4,47 @@ package frame import ( "fmt" "image" + + "github.com/aamcrae/webcam" ) +type FourCC string + type Frame interface { image.Image Release() } -var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} +var frameHandlers = map[FourCC]func(int, int, []byte, func()) (Frame, error){} // RegisterFramer registers a frame handler for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, handler func(int, int, []byte, func()) (Frame, error)) { frameHandlers[format] = handler } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { +func GetFramer(format FourCC) (func(int, int, []byte, func()) (Frame, error), error) { if f, ok := frameHandlers[format]; ok { return f, nil } return nil, fmt.Errorf("No handler for format '%s'", format) } + +// PixelFormatToFourCC converts the v4l2 PixelFormat to a FourCC. +func PixelFormatToFourCC(pf webcam.PixelFormat) FourCC { + b := make([]byte, 4) + b[0] = byte(pf) + b[1] = byte(pf >> 8) + b[2] = byte(pf >> 16) + b[3] = byte(pf >> 24) + return FourCC(b) +} + +// FourCCToPixelFormat converts the four character string to a v4l2 PixelFormat. +func FourCCToPixelFormat(f FourCC) (webcam.PixelFormat, error) { + if len(f) != 4 { + return 0, fmt.Errorf("%s: Illegal FourCC", f) + } + return webcam.PixelFormat(uint32(f[0]) | uint32(f[1]) << 8 | uint32(f[2]) << 16 | uint32(f[3]) << 24), nil +} diff --git a/frame/jpeg.go b/frame/jpeg.go new file mode 100644 index 0000000..dedc26f --- /dev/null +++ b/frame/jpeg.go @@ -0,0 +1,49 @@ +package frame + +import ( + "bytes" + "image" + "image/color" + "image/jpeg" +) + +type FrameJPEG struct { + img image.Image + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("JPEG", newFrameJPEG) +} + +// Wrap a jpeg block in a Frame so that it can be used as an image. +func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { + img, err := jpeg.Decode(bytes.NewBuffer(f)) + if err != nil { + if rel != nil { + rel() + } + return nil, err + } + return &FrameJPEG{img: img, release: rel}, nil +} + +func (f *FrameJPEG) ColorModel() color.Model { + return f.img.ColorModel() +} + +func (f *FrameJPEG) Bounds() image.Rectangle { + return f.img.Bounds() +} + +func (f *FrameJPEG) At(x, y int) color.Color { + return f.img.At(x, y) +} + +// Done with frame, release back to camera (if required). +func (f *FrameJPEG) Release() { + if f.release != nil { + f.release() + } +} diff --git a/frame/mjpeg.go b/frame/mjpeg.go index 79d4fab..f331c8c 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -72,7 +72,7 @@ var default_dht []byte = []byte{ // Register this framer for this format. func init() { - RegisterFramer("Motion-JPEG", newFrameMJPEG) + RegisterFramer("MJPG", newFrameMJPEG) } // Wrap a mjpeg block in a Frame so that it can be used as an image. @@ -155,7 +155,7 @@ func findConfig(f []byte) (map[byte][]int, error) { if l >= len(f)-2 { return nil, fmt.Errorf("unexpected EOF at location %d", l) } - l += int((f[l] << 8) + f[l+1]) + l += (int(f[l]) << 8) + int(f[l+1]) } return m, nil } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index a83f6a8..f7e44c6 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -1,6 +1,7 @@ package frame import ( + "flag" "fmt" "image" "image/color" @@ -9,25 +10,34 @@ import ( type FrameYUYV422 struct { model color.Model b image.Rectangle + bw int frame []byte release func() } +var padded = flag.Bool("padded", false, "Frame has padding") + // Register this framer for this format. func init() { - RegisterFramer("YUYV 4:2:2", newFrameYUYV422) + RegisterFramer("YUYV", newFrameYUYV422) } // Wrap a raw webcam frame in a Frame so that it can be used as an image. func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - expLen := 2 * x * y + bw := x + bh := y + if *padded { + bw = (x + 31) &^ 31 + bh = (y + 15) &^ 15 + } + expLen := 2 * bw * bh if len(f) != expLen { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) } - return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel}, nil + return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), bw: bw, frame: f, release: rel}, nil } func (f *FrameYUYV422) ColorModel() color.Model { @@ -39,7 +49,7 @@ func (f *FrameYUYV422) Bounds() image.Rectangle { } func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.b.Max.X*y*2 + (x&^1)*2 + index := f.bw * y * 2 + (x&^1) * 2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { diff --git a/server.go b/server.go index 9585a76..369a7cd 100644 --- a/server.go +++ b/server.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/aamcrae/imageserver/frame" "github.com/aamcrae/imageserver/snapshot" "github.com/aamcrae/webcam" ) @@ -18,7 +19,7 @@ import ( var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Camera resolution") -var format = flag.String("format", "YUYV 4:2:2", "Pixel format of camera") +var format = flag.String("format", "YUYV", "Pixel format of camera") var controls = flag.String("controls", "focus=190,power_line_frequency=1", "Control parameters for camera") var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") @@ -31,16 +32,25 @@ var cnames map[string]webcam.ControlID = map[string]webcam.ControlID{ "contrast": 0x00980901, } -func init() { - flag.Parse() -} - func main() { + flag.Parse() + s := strings.Split(*resolution, "x") + if len(s) != 2 { + log.Fatalf("%s: Illegal resolution", *resolution) + } + x, err := strconv.Atoi(s[0]) + if err != nil { + log.Fatalf("%s: illegal width", s[0], err) + } + y, err := strconv.Atoi(s[1]) + if err != nil { + log.Fatalf("%s: illegal height", s[1], err) + } if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } cm := snapshot.NewSnapper() - if err := cm.Open(*device, *format, *resolution); err != nil { + if err := cm.Open(*device, frame.FourCC(*format), x, y); err != nil { log.Fatalf("%s: %v", *device, err) } defer cm.Close() @@ -71,8 +81,8 @@ func main() { if *verbose { log.Printf("Starting server on %s", url) } - s := &http.Server{Addr: url} - log.Fatal(s.ListenAndServe()) + server := &http.Server{Addr: url} + log.Fatal(server.ListenAndServe()) } func readImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request) { diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 8348365..c8e489f 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -22,7 +22,7 @@ type Snapper struct { cam *webcam.Webcam Width int Height int - Format string + Format frame.FourCC Timeout uint32 Buffers uint32 newFrame func(int, int, []byte, func()) (frame.Frame, error) @@ -50,7 +50,11 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device, format, resolution string) error { +func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { + pf, err := frame.FourCCToPixelFormat(format) + if err != nil { + return err + } if c.cam != nil { c.Close() } @@ -62,41 +66,35 @@ func (c *Snapper) Open(device, format, resolution string) error { c.stop = make(chan struct{}, 1) c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. - format_desc := c.cam.GetSupportedFormats() - var pixelFormat webcam.PixelFormat - var found bool - for k, v := range format_desc { - if v == format { - found = true - pixelFormat = k - break - } - } - if !found { + mf := c.cam.GetSupportedFormats() + _, ok := mf[pf] + if !ok { return fmt.Errorf("%s: unsupported format: %s", device, format) } if c.newFrame, err = frame.GetFramer(format); err != nil { return err } - - // Build a map of resolution names from the description. - sizeMap := make(map[string]webcam.FrameSize) - for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { - sizeMap[value.GetString()] = value + var found bool + for _, value := range c.cam.GetSupportedFrameSizes(pf) { + if Match(value, x, y) { + found = true + break + } } - - sz, ok := sizeMap[resolution] - if !ok { - return fmt.Errorf("%s: unsupported resolution: %s (allowed: %v)", device, resolution, sizeMap) + if !found { + return fmt.Errorf("%s: unsupported resolution: %dx%d", device, x, y) } - _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) + pfs, w, h, err := c.cam.SetImageFormat(pf, uint32(x), uint32(y)) if err != nil { return err } c.Width = int(w) c.Height = int(h) + if pfs != pf || x != c.Width || y != c.Height { + fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, x, y, pfs, c.Width, c.Height) + } c.cam.SetBufferCount(c.Buffers) c.cam.SetAutoWhiteBalance(true) @@ -150,22 +148,6 @@ func (c *Snapper) capture() { } } -// Query returns a map of the supported formats and resolutions. -func (c *Snapper) Query() map[string][]string { - m := map[string][]string{} - formats := c.cam.GetSupportedFormats() - for f, fs := range formats { - r := []string{} - for _, value := range c.cam.GetSupportedFrameSizes(f) { - if value.StepWidth == 0 && value.StepHeight == 0 { - r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) - } - } - m[fs] = r - } - return m -} - // GetControl returns the current value of a camera control. func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { return c.cam.GetControl(id) @@ -175,3 +157,17 @@ func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { return c.cam.SetControl(id, value) } + +// Return true if frame size can accomodate request. +func Match(fs webcam.FrameSize, x, y int) bool { + return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(x)) && + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(y)) +} + +func canFit(min, max, step, val uint32) bool { + // Fixed size exact match. + if min == max && step == 0 && val == min { + return true + } + return step != 0 && val >= val && val <= max && ((val - min) % step) == 0 +} From c9570cede40a6afbbdd6e775846d99bc904a96b8 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 13:51:16 +1100 Subject: [PATCH 060/123] Use PixelFormat for framers. Add jpeg framer. --- frame.go | 28 +++++++++++++++++++++++++--- jpeg.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ mjpeg.go | 4 ++-- yuyv422.go | 18 ++++++++++++++---- 4 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 jpeg.go diff --git a/frame.go b/frame.go index ae5a30f..70758dc 100644 --- a/frame.go +++ b/frame.go @@ -4,25 +4,47 @@ package frame import ( "fmt" "image" + + "github.com/aamcrae/webcam" ) +type FourCC string + type Frame interface { image.Image Release() } -var frameHandlers = map[string]func(int, int, []byte, func()) (Frame, error){} +var frameHandlers = map[FourCC]func(int, int, []byte, func()) (Frame, error){} // RegisterFramer registers a frame handler for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format string, handler func(int, int, []byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, handler func(int, int, []byte, func()) (Frame, error)) { frameHandlers[format] = handler } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format string) (func(int, int, []byte, func()) (Frame, error), error) { +func GetFramer(format FourCC) (func(int, int, []byte, func()) (Frame, error), error) { if f, ok := frameHandlers[format]; ok { return f, nil } return nil, fmt.Errorf("No handler for format '%s'", format) } + +// PixelFormatToFourCC converts the v4l2 PixelFormat to a FourCC. +func PixelFormatToFourCC(pf webcam.PixelFormat) FourCC { + b := make([]byte, 4) + b[0] = byte(pf) + b[1] = byte(pf >> 8) + b[2] = byte(pf >> 16) + b[3] = byte(pf >> 24) + return FourCC(b) +} + +// FourCCToPixelFormat converts the four character string to a v4l2 PixelFormat. +func FourCCToPixelFormat(f FourCC) (webcam.PixelFormat, error) { + if len(f) != 4 { + return 0, fmt.Errorf("%s: Illegal FourCC", f) + } + return webcam.PixelFormat(uint32(f[0]) | uint32(f[1]) << 8 | uint32(f[2]) << 16 | uint32(f[3]) << 24), nil +} diff --git a/jpeg.go b/jpeg.go new file mode 100644 index 0000000..dedc26f --- /dev/null +++ b/jpeg.go @@ -0,0 +1,49 @@ +package frame + +import ( + "bytes" + "image" + "image/color" + "image/jpeg" +) + +type FrameJPEG struct { + img image.Image + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("JPEG", newFrameJPEG) +} + +// Wrap a jpeg block in a Frame so that it can be used as an image. +func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { + img, err := jpeg.Decode(bytes.NewBuffer(f)) + if err != nil { + if rel != nil { + rel() + } + return nil, err + } + return &FrameJPEG{img: img, release: rel}, nil +} + +func (f *FrameJPEG) ColorModel() color.Model { + return f.img.ColorModel() +} + +func (f *FrameJPEG) Bounds() image.Rectangle { + return f.img.Bounds() +} + +func (f *FrameJPEG) At(x, y int) color.Color { + return f.img.At(x, y) +} + +// Done with frame, release back to camera (if required). +func (f *FrameJPEG) Release() { + if f.release != nil { + f.release() + } +} diff --git a/mjpeg.go b/mjpeg.go index 79d4fab..f331c8c 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -72,7 +72,7 @@ var default_dht []byte = []byte{ // Register this framer for this format. func init() { - RegisterFramer("Motion-JPEG", newFrameMJPEG) + RegisterFramer("MJPG", newFrameMJPEG) } // Wrap a mjpeg block in a Frame so that it can be used as an image. @@ -155,7 +155,7 @@ func findConfig(f []byte) (map[byte][]int, error) { if l >= len(f)-2 { return nil, fmt.Errorf("unexpected EOF at location %d", l) } - l += int((f[l] << 8) + f[l+1]) + l += (int(f[l]) << 8) + int(f[l+1]) } return m, nil } diff --git a/yuyv422.go b/yuyv422.go index a83f6a8..f7e44c6 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -1,6 +1,7 @@ package frame import ( + "flag" "fmt" "image" "image/color" @@ -9,25 +10,34 @@ import ( type FrameYUYV422 struct { model color.Model b image.Rectangle + bw int frame []byte release func() } +var padded = flag.Bool("padded", false, "Frame has padding") + // Register this framer for this format. func init() { - RegisterFramer("YUYV 4:2:2", newFrameYUYV422) + RegisterFramer("YUYV", newFrameYUYV422) } // Wrap a raw webcam frame in a Frame so that it can be used as an image. func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - expLen := 2 * x * y + bw := x + bh := y + if *padded { + bw = (x + 31) &^ 31 + bh = (y + 15) &^ 15 + } + expLen := 2 * bw * bh if len(f) != expLen { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) } - return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), frame: f, release: rel}, nil + return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), bw: bw, frame: f, release: rel}, nil } func (f *FrameYUYV422) ColorModel() color.Model { @@ -39,7 +49,7 @@ func (f *FrameYUYV422) Bounds() image.Rectangle { } func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.b.Max.X*y*2 + (x&^1)*2 + index := f.bw * y * 2 + (x&^1) * 2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { From decd959ef715a6c4d4ebe27b16bb02da6385f20a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 13:51:16 +1100 Subject: [PATCH 061/123] Use PixelFormat for framers. Add jpeg framer. --- snapshot.go | 74 +++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/snapshot.go b/snapshot.go index 8348365..c8e489f 100644 --- a/snapshot.go +++ b/snapshot.go @@ -22,7 +22,7 @@ type Snapper struct { cam *webcam.Webcam Width int Height int - Format string + Format frame.FourCC Timeout uint32 Buffers uint32 newFrame func(int, int, []byte, func()) (frame.Frame, error) @@ -50,7 +50,11 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device, format, resolution string) error { +func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { + pf, err := frame.FourCCToPixelFormat(format) + if err != nil { + return err + } if c.cam != nil { c.Close() } @@ -62,41 +66,35 @@ func (c *Snapper) Open(device, format, resolution string) error { c.stop = make(chan struct{}, 1) c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. - format_desc := c.cam.GetSupportedFormats() - var pixelFormat webcam.PixelFormat - var found bool - for k, v := range format_desc { - if v == format { - found = true - pixelFormat = k - break - } - } - if !found { + mf := c.cam.GetSupportedFormats() + _, ok := mf[pf] + if !ok { return fmt.Errorf("%s: unsupported format: %s", device, format) } if c.newFrame, err = frame.GetFramer(format); err != nil { return err } - - // Build a map of resolution names from the description. - sizeMap := make(map[string]webcam.FrameSize) - for _, value := range c.cam.GetSupportedFrameSizes(pixelFormat) { - sizeMap[value.GetString()] = value + var found bool + for _, value := range c.cam.GetSupportedFrameSizes(pf) { + if Match(value, x, y) { + found = true + break + } } - - sz, ok := sizeMap[resolution] - if !ok { - return fmt.Errorf("%s: unsupported resolution: %s (allowed: %v)", device, resolution, sizeMap) + if !found { + return fmt.Errorf("%s: unsupported resolution: %dx%d", device, x, y) } - _, w, h, err := c.cam.SetImageFormat(pixelFormat, uint32(sz.MaxWidth), uint32(sz.MaxHeight)) + pfs, w, h, err := c.cam.SetImageFormat(pf, uint32(x), uint32(y)) if err != nil { return err } c.Width = int(w) c.Height = int(h) + if pfs != pf || x != c.Width || y != c.Height { + fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, x, y, pfs, c.Width, c.Height) + } c.cam.SetBufferCount(c.Buffers) c.cam.SetAutoWhiteBalance(true) @@ -150,22 +148,6 @@ func (c *Snapper) capture() { } } -// Query returns a map of the supported formats and resolutions. -func (c *Snapper) Query() map[string][]string { - m := map[string][]string{} - formats := c.cam.GetSupportedFormats() - for f, fs := range formats { - r := []string{} - for _, value := range c.cam.GetSupportedFrameSizes(f) { - if value.StepWidth == 0 && value.StepHeight == 0 { - r = append(r, fmt.Sprintf("%dx%d", value.MaxWidth, value.MaxHeight)) - } - } - m[fs] = r - } - return m -} - // GetControl returns the current value of a camera control. func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { return c.cam.GetControl(id) @@ -175,3 +157,17 @@ func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { return c.cam.SetControl(id, value) } + +// Return true if frame size can accomodate request. +func Match(fs webcam.FrameSize, x, y int) bool { + return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(x)) && + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(y)) +} + +func canFit(min, max, step, val uint32) bool { + // Fixed size exact match. + if min == max && step == 0 && val == min { + return true + } + return step != 0 && val >= val && val <= max && ((val - min) % step) == 0 +} From f0612f57f79d1b20285d0c733cab4840b95368e8 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:38:12 +1100 Subject: [PATCH 062/123] Refactored interface to framers. --- frame/bgr3.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ frame/frame.go | 14 +++++----- frame/jpeg.go | 21 ++++++++------ frame/mjpeg.go | 20 ++++++++------ frame/yuyv422.go | 44 +++++++++++++++++------------- snapshot/snapshot.go | 25 ++++++++--------- 6 files changed, 133 insertions(+), 56 deletions(-) create mode 100644 frame/bgr3.go diff --git a/frame/bgr3.go b/frame/bgr3.go new file mode 100644 index 0000000..6ece117 --- /dev/null +++ b/frame/bgr3.go @@ -0,0 +1,65 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fBGR3 struct { + model color.Model + b image.Rectangle + width int + frame []byte + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("BGR3", newFramerBGR3) +} + +// Return a function that is used as a framer for this format. +func newFramerBGR3(w, h int) func ([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameBGR3(size, bw, w, h, b, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameBGR3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fBGR3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil +} + +func (f *fBGR3) ColorModel() color.Model { + return f.model +} + +func (f *fBGR3) Bounds() image.Rectangle { + return f.b +} + +func (f *fBGR3) At(x, y int) color.Color { + i := f.width * y * 3 + x * 3 + return color.RGBA{f.frame[i + 2], f.frame[i + 1], f.frame[i], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fBGR3) Release() { + if f.release != nil { + f.release() + } +} diff --git a/frame/frame.go b/frame/frame.go index 70758dc..17fc6a6 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -15,18 +15,18 @@ type Frame interface { Release() } -var frameHandlers = map[FourCC]func(int, int, []byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func (int, int) (func([]byte, func()) (Frame, error)) {} -// RegisterFramer registers a frame handler for a format. +// RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, handler func(int, int, []byte, func()) (Frame, error)) { - frameHandlers[format] = handler +func RegisterFramer(format FourCC, factory func(int, int) (func ([]byte, func()) (Frame, error))) { + framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC) (func(int, int, []byte, func()) (Frame, error), error) { - if f, ok := frameHandlers[format]; ok { - return f, nil +func GetFramer(format FourCC, w, h int) (func([]byte, func()) (Frame, error), error) { + if factory, ok := framerFactoryMap[format]; ok { + return factory(w, h), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/frame/jpeg.go b/frame/jpeg.go index dedc26f..ad2b2b8 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -7,18 +7,23 @@ import ( "image/jpeg" ) -type FrameJPEG struct { +type fJPEG struct { img image.Image release func() } // Register this framer for this format. func init() { - RegisterFramer("JPEG", newFrameJPEG) + RegisterFramer("JPEG", newJPEGFramer) +} + +// Return a framer for JPEG. +func newJPEGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { + return jpegFramer } // Wrap a jpeg block in a Frame so that it can be used as an image. -func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { +func jpegFramer(f []byte, rel func()) (Frame, error) { img, err := jpeg.Decode(bytes.NewBuffer(f)) if err != nil { if rel != nil { @@ -26,23 +31,23 @@ func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { } return nil, err } - return &FrameJPEG{img: img, release: rel}, nil + return &fJPEG{img: img, release: rel}, nil } -func (f *FrameJPEG) ColorModel() color.Model { +func (f *fJPEG) ColorModel() color.Model { return f.img.ColorModel() } -func (f *FrameJPEG) Bounds() image.Rectangle { +func (f *fJPEG) Bounds() image.Rectangle { return f.img.Bounds() } -func (f *FrameJPEG) At(x, y int) color.Color { +func (f *fJPEG) At(x, y int) color.Color { return f.img.At(x, y) } // Done with frame, release back to camera (if required). -func (f *FrameJPEG) Release() { +func (f *fJPEG) Release() { if f.release != nil { f.release() } diff --git a/frame/mjpeg.go b/frame/mjpeg.go index f331c8c..1fe0cb4 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -8,7 +8,7 @@ import ( "image/jpeg" ) -type FrameMJPEG struct { +type fMJPEG struct { img image.Image release func() } @@ -72,13 +72,17 @@ var default_dht []byte = []byte{ // Register this framer for this format. func init() { - RegisterFramer("MJPG", newFrameMJPEG) + RegisterFramer("MJPG", newMJPGFramer) +} + +func newMJPGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { + return mjpegFramer } // Wrap a mjpeg block in a Frame so that it can be used as an image. // The standard jpeg decoding does not work if there are no Huffman tables, // so check the frame and add a default table if required. -func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { +func mjpegFramer(f []byte, rel func()) (Frame, error) { img, err := decodeMJPEG(f) if err != nil { if rel != nil { @@ -86,7 +90,7 @@ func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { } return nil, err } - return &FrameMJPEG{img: img, release: rel}, nil + return &fMJPEG{img: img, release: rel}, nil } // decodeMJPEG decodes the frame into an image. @@ -115,20 +119,20 @@ func decodeMJPEG(f []byte) (image.Image, error) { return jpeg.Decode(buf) } -func (f *FrameMJPEG) ColorModel() color.Model { +func (f *fMJPEG) ColorModel() color.Model { return f.img.ColorModel() } -func (f *FrameMJPEG) Bounds() image.Rectangle { +func (f *fMJPEG) Bounds() image.Rectangle { return f.img.Bounds() } -func (f *FrameMJPEG) At(x, y int) color.Color { +func (f *fMJPEG) At(x, y int) color.Color { return f.img.At(x, y) } // Done with frame, release back to camera (if required). -func (f *FrameMJPEG) Release() { +func (f *fMJPEG) Release() { if f.release != nil { f.release() } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index f7e44c6..ad046ef 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -7,49 +7,55 @@ import ( "image/color" ) -type FrameYUYV422 struct { +type fYUYV422 struct { model color.Model b image.Rectangle - bw int + width int frame []byte release func() } var padded = flag.Bool("padded", false, "Frame has padding") -// Register this framer for this format. +// Register a framer factory for this format. func init() { - RegisterFramer("YUYV", newFrameYUYV422) + RegisterFramer("YUYV", newFramerYUYV422) } -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - bw := x - bh := y +func newFramerYUYV422(w, h int) func ([]byte, func()) (Frame, error) { + var size, bw int if *padded { - bw = (x + 31) &^ 31 - bh = (y + 15) &^ 15 + bw = (w + 31) &^ 31 + size = 2 * bw * ((h + 15) &^ 15) + } else { + size = 2 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameYUYV422(size, bw, w, h, b, rel) } - expLen := 2 * bw * bh - if len(f) != expLen { +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { if rel != nil { defer rel() } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), bw: bw, frame: f, release: rel}, nil + return &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil } -func (f *FrameYUYV422) ColorModel() color.Model { +func (f *fYUYV422) ColorModel() color.Model { return f.model } -func (f *FrameYUYV422) Bounds() image.Rectangle { +func (f *fYUYV422) Bounds() image.Rectangle { return f.b } -func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.bw * y * 2 + (x&^1) * 2 +func (f *fYUYV422) At(x, y int) color.Color { + index := f.width * y * 2 + (x&^1) * 2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { @@ -58,7 +64,7 @@ func (f *FrameYUYV422) At(x, y int) color.Color { } // Done with frame, release back to camera (if required). -func (f *FrameYUYV422) Release() { +func (f *fYUYV422) Release() { if f.release != nil { f.release() } diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index c8e489f..9fbbb9a 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -25,7 +25,7 @@ type Snapper struct { Format frame.FourCC Timeout uint32 Buffers uint32 - newFrame func(int, int, []byte, func()) (frame.Frame, error) + framer func([]byte, func()) (frame.Frame, error) stop chan struct{} stream chan snap } @@ -50,7 +50,7 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { +func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { pf, err := frame.FourCCToPixelFormat(format) if err != nil { return err @@ -71,29 +71,26 @@ func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { if !ok { return fmt.Errorf("%s: unsupported format: %s", device, format) } - if c.newFrame, err = frame.GetFramer(format); err != nil { - return err - } var found bool for _, value := range c.cam.GetSupportedFrameSizes(pf) { - if Match(value, x, y) { + if Match(value, w, h) { found = true break } } if !found { - return fmt.Errorf("%s: unsupported resolution: %dx%d", device, x, y) + return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - - pfs, w, h, err := c.cam.SetImageFormat(pf, uint32(x), uint32(y)) + if c.framer, err = frame.GetFramer(format, w, h); err != nil { + return err + } + npf, nw, nh, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err } - c.Width = int(w) - c.Height = int(h) - if pfs != pf || x != c.Width || y != c.Height { - fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, x, y, pfs, c.Width, c.Height) + if npf != pf || w != int(nw) || h != int(nh) { + fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } c.cam.SetBufferCount(c.Buffers) @@ -111,7 +108,7 @@ func (c *Snapper) Snap() (frame.Frame, error) { if !ok { return nil, fmt.Errorf("No frame received") } - return c.newFrame(c.Width, c.Height, snap.frame, func() { + return c.framer(snap.frame, func() { c.cam.ReleaseFrame(snap.index) }) } From 0bebb1143b1337374d72e197b336022d1c6fd8bc Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:38:12 +1100 Subject: [PATCH 063/123] Refactored interface to framers. --- bgr3.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ frame.go | 14 ++++++------ jpeg.go | 21 +++++++++++------- mjpeg.go | 20 ++++++++++------- yuyv422.go | 44 ++++++++++++++++++++---------------- 5 files changed, 122 insertions(+), 42 deletions(-) create mode 100644 bgr3.go diff --git a/bgr3.go b/bgr3.go new file mode 100644 index 0000000..6ece117 --- /dev/null +++ b/bgr3.go @@ -0,0 +1,65 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fBGR3 struct { + model color.Model + b image.Rectangle + width int + frame []byte + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("BGR3", newFramerBGR3) +} + +// Return a function that is used as a framer for this format. +func newFramerBGR3(w, h int) func ([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameBGR3(size, bw, w, h, b, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameBGR3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fBGR3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil +} + +func (f *fBGR3) ColorModel() color.Model { + return f.model +} + +func (f *fBGR3) Bounds() image.Rectangle { + return f.b +} + +func (f *fBGR3) At(x, y int) color.Color { + i := f.width * y * 3 + x * 3 + return color.RGBA{f.frame[i + 2], f.frame[i + 1], f.frame[i], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fBGR3) Release() { + if f.release != nil { + f.release() + } +} diff --git a/frame.go b/frame.go index 70758dc..17fc6a6 100644 --- a/frame.go +++ b/frame.go @@ -15,18 +15,18 @@ type Frame interface { Release() } -var frameHandlers = map[FourCC]func(int, int, []byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func (int, int) (func([]byte, func()) (Frame, error)) {} -// RegisterFramer registers a frame handler for a format. +// RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, handler func(int, int, []byte, func()) (Frame, error)) { - frameHandlers[format] = handler +func RegisterFramer(format FourCC, factory func(int, int) (func ([]byte, func()) (Frame, error))) { + framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC) (func(int, int, []byte, func()) (Frame, error), error) { - if f, ok := frameHandlers[format]; ok { - return f, nil +func GetFramer(format FourCC, w, h int) (func([]byte, func()) (Frame, error), error) { + if factory, ok := framerFactoryMap[format]; ok { + return factory(w, h), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/jpeg.go b/jpeg.go index dedc26f..ad2b2b8 100644 --- a/jpeg.go +++ b/jpeg.go @@ -7,18 +7,23 @@ import ( "image/jpeg" ) -type FrameJPEG struct { +type fJPEG struct { img image.Image release func() } // Register this framer for this format. func init() { - RegisterFramer("JPEG", newFrameJPEG) + RegisterFramer("JPEG", newJPEGFramer) +} + +// Return a framer for JPEG. +func newJPEGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { + return jpegFramer } // Wrap a jpeg block in a Frame so that it can be used as an image. -func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { +func jpegFramer(f []byte, rel func()) (Frame, error) { img, err := jpeg.Decode(bytes.NewBuffer(f)) if err != nil { if rel != nil { @@ -26,23 +31,23 @@ func newFrameJPEG(x int, y int, f []byte, rel func()) (Frame, error) { } return nil, err } - return &FrameJPEG{img: img, release: rel}, nil + return &fJPEG{img: img, release: rel}, nil } -func (f *FrameJPEG) ColorModel() color.Model { +func (f *fJPEG) ColorModel() color.Model { return f.img.ColorModel() } -func (f *FrameJPEG) Bounds() image.Rectangle { +func (f *fJPEG) Bounds() image.Rectangle { return f.img.Bounds() } -func (f *FrameJPEG) At(x, y int) color.Color { +func (f *fJPEG) At(x, y int) color.Color { return f.img.At(x, y) } // Done with frame, release back to camera (if required). -func (f *FrameJPEG) Release() { +func (f *fJPEG) Release() { if f.release != nil { f.release() } diff --git a/mjpeg.go b/mjpeg.go index f331c8c..1fe0cb4 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -8,7 +8,7 @@ import ( "image/jpeg" ) -type FrameMJPEG struct { +type fMJPEG struct { img image.Image release func() } @@ -72,13 +72,17 @@ var default_dht []byte = []byte{ // Register this framer for this format. func init() { - RegisterFramer("MJPG", newFrameMJPEG) + RegisterFramer("MJPG", newMJPGFramer) +} + +func newMJPGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { + return mjpegFramer } // Wrap a mjpeg block in a Frame so that it can be used as an image. // The standard jpeg decoding does not work if there are no Huffman tables, // so check the frame and add a default table if required. -func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { +func mjpegFramer(f []byte, rel func()) (Frame, error) { img, err := decodeMJPEG(f) if err != nil { if rel != nil { @@ -86,7 +90,7 @@ func newFrameMJPEG(x int, y int, f []byte, rel func()) (Frame, error) { } return nil, err } - return &FrameMJPEG{img: img, release: rel}, nil + return &fMJPEG{img: img, release: rel}, nil } // decodeMJPEG decodes the frame into an image. @@ -115,20 +119,20 @@ func decodeMJPEG(f []byte) (image.Image, error) { return jpeg.Decode(buf) } -func (f *FrameMJPEG) ColorModel() color.Model { +func (f *fMJPEG) ColorModel() color.Model { return f.img.ColorModel() } -func (f *FrameMJPEG) Bounds() image.Rectangle { +func (f *fMJPEG) Bounds() image.Rectangle { return f.img.Bounds() } -func (f *FrameMJPEG) At(x, y int) color.Color { +func (f *fMJPEG) At(x, y int) color.Color { return f.img.At(x, y) } // Done with frame, release back to camera (if required). -func (f *FrameMJPEG) Release() { +func (f *fMJPEG) Release() { if f.release != nil { f.release() } diff --git a/yuyv422.go b/yuyv422.go index f7e44c6..ad046ef 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -7,49 +7,55 @@ import ( "image/color" ) -type FrameYUYV422 struct { +type fYUYV422 struct { model color.Model b image.Rectangle - bw int + width int frame []byte release func() } var padded = flag.Bool("padded", false, "Frame has padding") -// Register this framer for this format. +// Register a framer factory for this format. func init() { - RegisterFramer("YUYV", newFrameYUYV422) + RegisterFramer("YUYV", newFramerYUYV422) } -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func newFrameYUYV422(x int, y int, f []byte, rel func()) (Frame, error) { - bw := x - bh := y +func newFramerYUYV422(w, h int) func ([]byte, func()) (Frame, error) { + var size, bw int if *padded { - bw = (x + 31) &^ 31 - bh = (y + 15) &^ 15 + bw = (w + 31) &^ 31 + size = 2 * bw * ((h + 15) &^ 15) + } else { + size = 2 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameYUYV422(size, bw, w, h, b, rel) } - expLen := 2 * bw * bh - if len(f) != expLen { +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { if rel != nil { defer rel() } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", expLen, len(f)) + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &FrameYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, x, y), bw: bw, frame: f, release: rel}, nil + return &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil } -func (f *FrameYUYV422) ColorModel() color.Model { +func (f *fYUYV422) ColorModel() color.Model { return f.model } -func (f *FrameYUYV422) Bounds() image.Rectangle { +func (f *fYUYV422) Bounds() image.Rectangle { return f.b } -func (f *FrameYUYV422) At(x, y int) color.Color { - index := f.bw * y * 2 + (x&^1) * 2 +func (f *fYUYV422) At(x, y int) color.Color { + index := f.width * y * 2 + (x&^1) * 2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { @@ -58,7 +64,7 @@ func (f *FrameYUYV422) At(x, y int) color.Color { } // Done with frame, release back to camera (if required). -func (f *FrameYUYV422) Release() { +func (f *fYUYV422) Release() { if f.release != nil { f.release() } From 34afa54899a2abc984f0388c3407254722e79f52 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:38:12 +1100 Subject: [PATCH 064/123] Refactored interface to framers. --- snapshot.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/snapshot.go b/snapshot.go index c8e489f..9fbbb9a 100644 --- a/snapshot.go +++ b/snapshot.go @@ -25,7 +25,7 @@ type Snapper struct { Format frame.FourCC Timeout uint32 Buffers uint32 - newFrame func(int, int, []byte, func()) (frame.Frame, error) + framer func([]byte, func()) (frame.Frame, error) stop chan struct{} stream chan snap } @@ -50,7 +50,7 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { +func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { pf, err := frame.FourCCToPixelFormat(format) if err != nil { return err @@ -71,29 +71,26 @@ func (c *Snapper) Open(device string, format frame.FourCC, x, y int) error { if !ok { return fmt.Errorf("%s: unsupported format: %s", device, format) } - if c.newFrame, err = frame.GetFramer(format); err != nil { - return err - } var found bool for _, value := range c.cam.GetSupportedFrameSizes(pf) { - if Match(value, x, y) { + if Match(value, w, h) { found = true break } } if !found { - return fmt.Errorf("%s: unsupported resolution: %dx%d", device, x, y) + return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - - pfs, w, h, err := c.cam.SetImageFormat(pf, uint32(x), uint32(y)) + if c.framer, err = frame.GetFramer(format, w, h); err != nil { + return err + } + npf, nw, nh, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err } - c.Width = int(w) - c.Height = int(h) - if pfs != pf || x != c.Width || y != c.Height { - fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, x, y, pfs, c.Width, c.Height) + if npf != pf || w != int(nw) || h != int(nh) { + fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } c.cam.SetBufferCount(c.Buffers) @@ -111,7 +108,7 @@ func (c *Snapper) Snap() (frame.Frame, error) { if !ok { return nil, fmt.Errorf("No frame received") } - return c.newFrame(c.Width, c.Height, snap.frame, func() { + return c.framer(snap.frame, func() { c.cam.ReleaseFrame(snap.index) }) } From 7055439474e89dba60f22a929fe8ac7692836592 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:40:30 +1100 Subject: [PATCH 065/123] Formatted. --- frame/bgr3.go | 8 ++++---- frame/frame.go | 6 +++--- frame/jpeg.go | 2 +- frame/mjpeg.go | 2 +- frame/yuyv422.go | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frame/bgr3.go b/frame/bgr3.go index 6ece117..23fd4ec 100644 --- a/frame/bgr3.go +++ b/frame/bgr3.go @@ -9,7 +9,7 @@ import ( type fBGR3 struct { model color.Model b image.Rectangle - width int + width int frame []byte release func() } @@ -20,7 +20,7 @@ func init() { } // Return a function that is used as a framer for this format. -func newFramerBGR3(w, h int) func ([]byte, func()) (Frame, error) { +func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { var size, bw int if *padded { bw = (w + 31) &^ 31 @@ -53,8 +53,8 @@ func (f *fBGR3) Bounds() image.Rectangle { } func (f *fBGR3) At(x, y int) color.Color { - i := f.width * y * 3 + x * 3 - return color.RGBA{f.frame[i + 2], f.frame[i + 1], f.frame[i], 0xFF} + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i+2], f.frame[i+1], f.frame[i], 0xFF} } // Done with frame, release back to camera (if required). diff --git a/frame/frame.go b/frame/frame.go index 17fc6a6..add92a7 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -15,11 +15,11 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func (int, int) (func([]byte, func()) (Frame, error)) {} +var framerFactoryMap = map[FourCC]func(int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int) (func ([]byte, func()) (Frame, error))) { +func RegisterFramer(format FourCC, factory func(int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } @@ -46,5 +46,5 @@ func FourCCToPixelFormat(f FourCC) (webcam.PixelFormat, error) { if len(f) != 4 { return 0, fmt.Errorf("%s: Illegal FourCC", f) } - return webcam.PixelFormat(uint32(f[0]) | uint32(f[1]) << 8 | uint32(f[2]) << 16 | uint32(f[3]) << 24), nil + return webcam.PixelFormat(uint32(f[0]) | uint32(f[1])<<8 | uint32(f[2])<<16 | uint32(f[3])<<24), nil } diff --git a/frame/jpeg.go b/frame/jpeg.go index ad2b2b8..dd8494a 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -18,7 +18,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { +func newJPEGFramer(w, h int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/frame/mjpeg.go b/frame/mjpeg.go index 1fe0cb4..def47df 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -75,7 +75,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { +func newMJPGFramer(w, h int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index ad046ef..fcd5607 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -10,7 +10,7 @@ import ( type fYUYV422 struct { model color.Model b image.Rectangle - width int + width int frame []byte release func() } @@ -22,7 +22,7 @@ func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h int) func ([]byte, func()) (Frame, error) { +func newFramerYUYV422(w, h int) func([]byte, func()) (Frame, error) { var size, bw int if *padded { bw = (w + 31) &^ 31 @@ -55,7 +55,7 @@ func (f *fYUYV422) Bounds() image.Rectangle { } func (f *fYUYV422) At(x, y int) color.Color { - index := f.width * y * 2 + (x&^1) * 2 + index := f.width*y*2 + (x&^1)*2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { From 0149c948b8c23c2b70896658828d8ff39bf1a5b6 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:40:30 +1100 Subject: [PATCH 066/123] Formatted. --- bgr3.go | 8 ++++---- frame.go | 6 +++--- jpeg.go | 2 +- mjpeg.go | 2 +- yuyv422.go | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bgr3.go b/bgr3.go index 6ece117..23fd4ec 100644 --- a/bgr3.go +++ b/bgr3.go @@ -9,7 +9,7 @@ import ( type fBGR3 struct { model color.Model b image.Rectangle - width int + width int frame []byte release func() } @@ -20,7 +20,7 @@ func init() { } // Return a function that is used as a framer for this format. -func newFramerBGR3(w, h int) func ([]byte, func()) (Frame, error) { +func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { var size, bw int if *padded { bw = (w + 31) &^ 31 @@ -53,8 +53,8 @@ func (f *fBGR3) Bounds() image.Rectangle { } func (f *fBGR3) At(x, y int) color.Color { - i := f.width * y * 3 + x * 3 - return color.RGBA{f.frame[i + 2], f.frame[i + 1], f.frame[i], 0xFF} + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i+2], f.frame[i+1], f.frame[i], 0xFF} } // Done with frame, release back to camera (if required). diff --git a/frame.go b/frame.go index 17fc6a6..add92a7 100644 --- a/frame.go +++ b/frame.go @@ -15,11 +15,11 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func (int, int) (func([]byte, func()) (Frame, error)) {} +var framerFactoryMap = map[FourCC]func(int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int) (func ([]byte, func()) (Frame, error))) { +func RegisterFramer(format FourCC, factory func(int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } @@ -46,5 +46,5 @@ func FourCCToPixelFormat(f FourCC) (webcam.PixelFormat, error) { if len(f) != 4 { return 0, fmt.Errorf("%s: Illegal FourCC", f) } - return webcam.PixelFormat(uint32(f[0]) | uint32(f[1]) << 8 | uint32(f[2]) << 16 | uint32(f[3]) << 24), nil + return webcam.PixelFormat(uint32(f[0]) | uint32(f[1])<<8 | uint32(f[2])<<16 | uint32(f[3])<<24), nil } diff --git a/jpeg.go b/jpeg.go index ad2b2b8..dd8494a 100644 --- a/jpeg.go +++ b/jpeg.go @@ -18,7 +18,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { +func newJPEGFramer(w, h int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/mjpeg.go b/mjpeg.go index 1fe0cb4..def47df 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -75,7 +75,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h int) (func ([]byte, func()) (Frame, error)) { +func newMJPGFramer(w, h int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/yuyv422.go b/yuyv422.go index ad046ef..fcd5607 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -10,7 +10,7 @@ import ( type fYUYV422 struct { model color.Model b image.Rectangle - width int + width int frame []byte release func() } @@ -22,7 +22,7 @@ func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h int) func ([]byte, func()) (Frame, error) { +func newFramerYUYV422(w, h int) func([]byte, func()) (Frame, error) { var size, bw int if *padded { bw = (w + 31) &^ 31 @@ -55,7 +55,7 @@ func (f *fYUYV422) Bounds() image.Rectangle { } func (f *fYUYV422) At(x, y int) color.Color { - index := f.width * y * 2 + (x&^1) * 2 + index := f.width*y*2 + (x&^1)*2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { From cf06d1bf7e04d340cb40b073f43530da2a5f838e Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:52:33 +1100 Subject: [PATCH 067/123] Added RGB3 handler. --- frame/rgb3.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 frame/rgb3.go diff --git a/frame/rgb3.go b/frame/rgb3.go new file mode 100644 index 0000000..227d1a4 --- /dev/null +++ b/frame/rgb3.go @@ -0,0 +1,65 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fRGB3 struct { + model color.Model + b image.Rectangle + width int + frame []byte + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("RGB3", newFramerRGB3) +} + +// Return a function that is used as a framer for this format. +func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameRGB3(size, bw, w, h, b, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameRGB3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fRGB3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil +} + +func (f *fRGB3) ColorModel() color.Model { + return f.model +} + +func (f *fRGB3) Bounds() image.Rectangle { + return f.b +} + +func (f *fRGB3) At(x, y int) color.Color { + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i], f.frame[i+1], f.frame[i+2], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fRGB3) Release() { + if f.release != nil { + f.release() + } +} From bc16b1014b6ad229600e8302e2573715f6d9545f Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 15:52:33 +1100 Subject: [PATCH 068/123] Added RGB3 handler. --- rgb3.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 rgb3.go diff --git a/rgb3.go b/rgb3.go new file mode 100644 index 0000000..227d1a4 --- /dev/null +++ b/rgb3.go @@ -0,0 +1,65 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fRGB3 struct { + model color.Model + b image.Rectangle + width int + frame []byte + release func() +} + +// Register this framer for this format. +func init() { + RegisterFramer("RGB3", newFramerRGB3) +} + +// Return a function that is used as a framer for this format. +func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(b []byte, rel func()) (Frame, error) { + return frameRGB3(size, bw, w, h, b, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameRGB3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fRGB3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil +} + +func (f *fRGB3) ColorModel() color.Model { + return f.model +} + +func (f *fRGB3) Bounds() image.Rectangle { + return f.b +} + +func (f *fRGB3) At(x, y int) color.Color { + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i], f.frame[i+1], f.frame[i+2], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fRGB3) Release() { + if f.release != nil { + f.release() + } +} From 2f903636e553f27d0cc4d0bf5955f797dfa25223 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 16:25:48 +1100 Subject: [PATCH 069/123] Make RGB processing common. --- frame/bgr3.go | 65 ----------------------------------- frame/rgb.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ frame/rgb3.go | 65 ----------------------------------- snapshot/snapshot.go | 9 ++--- 4 files changed, 83 insertions(+), 136 deletions(-) delete mode 100644 frame/bgr3.go create mode 100644 frame/rgb.go delete mode 100644 frame/rgb3.go diff --git a/frame/bgr3.go b/frame/bgr3.go deleted file mode 100644 index 23fd4ec..0000000 --- a/frame/bgr3.go +++ /dev/null @@ -1,65 +0,0 @@ -package frame - -import ( - "fmt" - "image" - "image/color" -) - -type fBGR3 struct { - model color.Model - b image.Rectangle - width int - frame []byte - release func() -} - -// Register this framer for this format. -func init() { - RegisterFramer("BGR3", newFramerBGR3) -} - -// Return a function that is used as a framer for this format. -func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } - return func(b []byte, rel func()) (Frame, error) { - return frameBGR3(size, bw, w, h, b, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameBGR3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - return &fBGR3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil -} - -func (f *fBGR3) ColorModel() color.Model { - return f.model -} - -func (f *fBGR3) Bounds() image.Rectangle { - return f.b -} - -func (f *fBGR3) At(x, y int) color.Color { - i := f.width*y*3 + x*3 - return color.RGBA{f.frame[i+2], f.frame[i+1], f.frame[i], 0xFF} -} - -// Done with frame, release back to camera (if required). -func (f *fBGR3) Release() { - if f.release != nil { - f.release() - } -} diff --git a/frame/rgb.go b/frame/rgb.go new file mode 100644 index 0000000..0d8b37f --- /dev/null +++ b/frame/rgb.go @@ -0,0 +1,80 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fRGB struct { + model color.Model + b image.Rectangle + width int + roffs int + goffs int + boffs int + frame []byte + release func() +} + +// Register framers for these formats. +func init() { + RegisterFramer("RGB3", newFramerRGB3) + RegisterFramer("BGR3", newFramerBGR3) +} + +// Return a function that is used as a framer for RGB3. +func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, 0, 1, 2) +} + +// Return a function that is used as a framer for BGR3. +func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, 2, 1, 0) +} + +// Return a function that is used as a generic RGB framer. +func newRGBFramer(w, h, r, g, b int) func([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(buf []byte, rel func()) (Frame, error) { + return frameRGB(size, bw, w, h, r, g, b, buf, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel}, nil +} + +func (f *fRGB) ColorModel() color.Model { + return f.model +} + +func (f *fRGB) Bounds() image.Rectangle { + return f.b +} + +func (f *fRGB) At(x, y int) color.Color { + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i+f.roffs], f.frame[i+f.goffs], f.frame[i+f.boffs], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fRGB) Release() { + if f.release != nil { + f.release() + } +} diff --git a/frame/rgb3.go b/frame/rgb3.go deleted file mode 100644 index 227d1a4..0000000 --- a/frame/rgb3.go +++ /dev/null @@ -1,65 +0,0 @@ -package frame - -import ( - "fmt" - "image" - "image/color" -) - -type fRGB3 struct { - model color.Model - b image.Rectangle - width int - frame []byte - release func() -} - -// Register this framer for this format. -func init() { - RegisterFramer("RGB3", newFramerRGB3) -} - -// Return a function that is used as a framer for this format. -func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } - return func(b []byte, rel func()) (Frame, error) { - return frameRGB3(size, bw, w, h, b, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameRGB3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - return &fRGB3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil -} - -func (f *fRGB3) ColorModel() color.Model { - return f.model -} - -func (f *fRGB3) Bounds() image.Rectangle { - return f.b -} - -func (f *fRGB3) At(x, y int) color.Color { - i := f.width*y*3 + x*3 - return color.RGBA{f.frame[i], f.frame[i+1], f.frame[i+2], 0xFF} -} - -// Done with frame, release back to camera (if required). -func (f *fRGB3) Release() { - if f.release != nil { - f.release() - } -} diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 9fbbb9a..1f0ff32 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -20,9 +20,6 @@ type snap struct { type Snapper struct { cam *webcam.Webcam - Width int - Height int - Format frame.FourCC Timeout uint32 Buffers uint32 framer func([]byte, func()) (frame.Frame, error) @@ -156,9 +153,9 @@ func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { } // Return true if frame size can accomodate request. -func Match(fs webcam.FrameSize, x, y int) bool { - return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(x)) && - canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(y)) +func Match(fs webcam.FrameSize, w, h int) bool { + return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) && + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) } func canFit(min, max, step, val uint32) bool { From 5b40548b7e0a984b91ccb312eba16bd9774d2e54 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 16:25:48 +1100 Subject: [PATCH 070/123] Make RGB processing common. --- bgr3.go | 65 ---------------------------------------------- rgb.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rgb3.go | 65 ---------------------------------------------- 3 files changed, 80 insertions(+), 130 deletions(-) delete mode 100644 bgr3.go create mode 100644 rgb.go delete mode 100644 rgb3.go diff --git a/bgr3.go b/bgr3.go deleted file mode 100644 index 23fd4ec..0000000 --- a/bgr3.go +++ /dev/null @@ -1,65 +0,0 @@ -package frame - -import ( - "fmt" - "image" - "image/color" -) - -type fBGR3 struct { - model color.Model - b image.Rectangle - width int - frame []byte - release func() -} - -// Register this framer for this format. -func init() { - RegisterFramer("BGR3", newFramerBGR3) -} - -// Return a function that is used as a framer for this format. -func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } - return func(b []byte, rel func()) (Frame, error) { - return frameBGR3(size, bw, w, h, b, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameBGR3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - return &fBGR3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil -} - -func (f *fBGR3) ColorModel() color.Model { - return f.model -} - -func (f *fBGR3) Bounds() image.Rectangle { - return f.b -} - -func (f *fBGR3) At(x, y int) color.Color { - i := f.width*y*3 + x*3 - return color.RGBA{f.frame[i+2], f.frame[i+1], f.frame[i], 0xFF} -} - -// Done with frame, release back to camera (if required). -func (f *fBGR3) Release() { - if f.release != nil { - f.release() - } -} diff --git a/rgb.go b/rgb.go new file mode 100644 index 0000000..0d8b37f --- /dev/null +++ b/rgb.go @@ -0,0 +1,80 @@ +package frame + +import ( + "fmt" + "image" + "image/color" +) + +type fRGB struct { + model color.Model + b image.Rectangle + width int + roffs int + goffs int + boffs int + frame []byte + release func() +} + +// Register framers for these formats. +func init() { + RegisterFramer("RGB3", newFramerRGB3) + RegisterFramer("BGR3", newFramerBGR3) +} + +// Return a function that is used as a framer for RGB3. +func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, 0, 1, 2) +} + +// Return a function that is used as a framer for BGR3. +func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, 2, 1, 0) +} + +// Return a function that is used as a generic RGB framer. +func newRGBFramer(w, h, r, g, b int) func([]byte, func()) (Frame, error) { + var size, bw int + if *padded { + bw = (w + 31) &^ 31 + size = 3 * bw * ((h + 15) &^ 15) + } else { + size = 3 * h * w + } + return func(buf []byte, rel func()) (Frame, error) { + return frameRGB(size, bw, w, h, r, g, b, buf, rel) + } +} + +// Wrap a raw webcam frame in a Frame so that it can be used as an image. +func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { + if len(b) != size { + if rel != nil { + defer rel() + } + return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) + } + return &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel}, nil +} + +func (f *fRGB) ColorModel() color.Model { + return f.model +} + +func (f *fRGB) Bounds() image.Rectangle { + return f.b +} + +func (f *fRGB) At(x, y int) color.Color { + i := f.width*y*3 + x*3 + return color.RGBA{f.frame[i+f.roffs], f.frame[i+f.goffs], f.frame[i+f.boffs], 0xFF} +} + +// Done with frame, release back to camera (if required). +func (f *fRGB) Release() { + if f.release != nil { + f.release() + } +} diff --git a/rgb3.go b/rgb3.go deleted file mode 100644 index 227d1a4..0000000 --- a/rgb3.go +++ /dev/null @@ -1,65 +0,0 @@ -package frame - -import ( - "fmt" - "image" - "image/color" -) - -type fRGB3 struct { - model color.Model - b image.Rectangle - width int - frame []byte - release func() -} - -// Register this framer for this format. -func init() { - RegisterFramer("RGB3", newFramerRGB3) -} - -// Return a function that is used as a framer for this format. -func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } - return func(b []byte, rel func()) (Frame, error) { - return frameRGB3(size, bw, w, h, b, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameRGB3(size, bw, w, h int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - return &fRGB3{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil -} - -func (f *fRGB3) ColorModel() color.Model { - return f.model -} - -func (f *fRGB3) Bounds() image.Rectangle { - return f.b -} - -func (f *fRGB3) At(x, y int) color.Color { - i := f.width*y*3 + x*3 - return color.RGBA{f.frame[i], f.frame[i+1], f.frame[i+2], 0xFF} -} - -// Done with frame, release back to camera (if required). -func (f *fRGB3) Release() { - if f.release != nil { - f.release() - } -} From 26ab9f8e27fc1a68c931d8fea53215f4a4f79eca Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 18 Jan 2019 16:25:48 +1100 Subject: [PATCH 071/123] Make RGB processing common. --- snapshot.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/snapshot.go b/snapshot.go index 9fbbb9a..1f0ff32 100644 --- a/snapshot.go +++ b/snapshot.go @@ -20,9 +20,6 @@ type snap struct { type Snapper struct { cam *webcam.Webcam - Width int - Height int - Format frame.FourCC Timeout uint32 Buffers uint32 framer func([]byte, func()) (frame.Frame, error) @@ -156,9 +153,9 @@ func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { } // Return true if frame size can accomodate request. -func Match(fs webcam.FrameSize, x, y int) bool { - return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(x)) && - canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(y)) +func Match(fs webcam.FrameSize, w, h int) bool { + return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) && + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) } func canFit(min, max, step, val uint32) bool { From 50dae968888e26bfe507e3ef5c43b58f21d3bcef Mon Sep 17 00:00:00 2001 From: Andrew McRae <37428808+aamcrae@users.noreply.github.com> Date: Mon, 28 Jan 2019 16:26:33 +1100 Subject: [PATCH 072/123] Create LICENSE --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 604c5a45e46de5c9b3d9dc395c22f4c058a96a40 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 28 Jan 2019 16:59:46 +1100 Subject: [PATCH 073/123] Add CONTRIBUTING file. --- CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..939e534 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). From dcf3e231c6b128a82584590de8b49005e983042d Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 05:52:03 +1100 Subject: [PATCH 074/123] Default controls to none. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 369a7cd..d07a0b3 100644 --- a/server.go +++ b/server.go @@ -20,7 +20,7 @@ var port = flag.Int("port", 8080, "Web server port number") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Camera resolution") var format = flag.String("format", "YUYV", "Pixel format of camera") -var controls = flag.String("controls", "focus=190,power_line_frequency=1", +var controls = flag.String("controls", "", "Control parameters for camera") var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") From 543a685b2c395c8a363c59b1102498a4d6f00950 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 07:45:54 +1100 Subject: [PATCH 075/123] Add finalizer to frames. --- frame/frame.go | 3 +++ frame/jpeg.go | 9 ++++++++- frame/mjpeg.go | 9 ++++++++- frame/rgb.go | 11 +++++++++-- frame/yuyv422.go | 9 ++++++++- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/frame/frame.go b/frame/frame.go index add92a7..82ed1e6 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -10,6 +10,9 @@ import ( type FourCC string +// Release is called when the frame is no longer in use. +// The implementation may set a finalizer on the frame as a precaution +// in case Release is not called (which would cause a kernel resource leak). type Frame interface { image.Image Release() diff --git a/frame/jpeg.go b/frame/jpeg.go index dd8494a..3411cd6 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "image/jpeg" + "runtime" ) type fJPEG struct { @@ -31,7 +32,11 @@ func jpegFramer(f []byte, rel func()) (Frame, error) { } return nil, err } - return &fJPEG{img: img, release: rel}, nil + fr := &fJPEG{img: img, release: rel} + runtime.SetFinalizer(fr, func(obj Frame) { + obj.Release() + }) + return fr, nil } func (f *fJPEG) ColorModel() color.Model { @@ -50,5 +55,7 @@ func (f *fJPEG) At(x, y int) color.Color { func (f *fJPEG) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/frame/mjpeg.go b/frame/mjpeg.go index def47df..405d405 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -6,6 +6,7 @@ import ( "image" "image/color" "image/jpeg" + "runtime" ) type fMJPEG struct { @@ -90,7 +91,11 @@ func mjpegFramer(f []byte, rel func()) (Frame, error) { } return nil, err } - return &fMJPEG{img: img, release: rel}, nil + fr := &fMJPEG{img: img, release: rel} + runtime.SetFinalizer(fr, func(obj Frame) { + obj.Release() + }) + return fr, nil } // decodeMJPEG decodes the frame into an image. @@ -135,6 +140,8 @@ func (f *fMJPEG) At(x, y int) color.Color { func (f *fMJPEG) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/frame/rgb.go b/frame/rgb.go index 0d8b37f..db77cf6 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "image/color" + "runtime" ) type fRGB struct { @@ -55,8 +56,12 @@ func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, e } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, - roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel}, nil + f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel} + runtime.SetFinalizer(f, func(obj Frame) { + obj.Release() + }) + return f, nil } func (f *fRGB) ColorModel() color.Model { @@ -76,5 +81,7 @@ func (f *fRGB) At(x, y int) color.Color { func (f *fRGB) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index fcd5607..2a286cc 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -5,6 +5,7 @@ import ( "fmt" "image" "image/color" + "runtime" ) type fYUYV422 struct { @@ -43,7 +44,11 @@ func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil + f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel} + runtime.SetFinalizer(f, func(obj Frame) { + obj.Release() + }) + return f, nil } func (f *fYUYV422) ColorModel() color.Model { @@ -67,5 +72,7 @@ func (f *fYUYV422) At(x, y int) color.Color { func (f *fYUYV422) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } From eda67e9d4b563fcba83fc99d018586c78030d9bc Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 07:45:54 +1100 Subject: [PATCH 076/123] Add finalizer to frames. --- frame.go | 3 +++ jpeg.go | 9 ++++++++- mjpeg.go | 9 ++++++++- rgb.go | 11 +++++++++-- yuyv422.go | 9 ++++++++- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/frame.go b/frame.go index add92a7..82ed1e6 100644 --- a/frame.go +++ b/frame.go @@ -10,6 +10,9 @@ import ( type FourCC string +// Release is called when the frame is no longer in use. +// The implementation may set a finalizer on the frame as a precaution +// in case Release is not called (which would cause a kernel resource leak). type Frame interface { image.Image Release() diff --git a/jpeg.go b/jpeg.go index dd8494a..3411cd6 100644 --- a/jpeg.go +++ b/jpeg.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "image/jpeg" + "runtime" ) type fJPEG struct { @@ -31,7 +32,11 @@ func jpegFramer(f []byte, rel func()) (Frame, error) { } return nil, err } - return &fJPEG{img: img, release: rel}, nil + fr := &fJPEG{img: img, release: rel} + runtime.SetFinalizer(fr, func(obj Frame) { + obj.Release() + }) + return fr, nil } func (f *fJPEG) ColorModel() color.Model { @@ -50,5 +55,7 @@ func (f *fJPEG) At(x, y int) color.Color { func (f *fJPEG) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/mjpeg.go b/mjpeg.go index def47df..405d405 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -6,6 +6,7 @@ import ( "image" "image/color" "image/jpeg" + "runtime" ) type fMJPEG struct { @@ -90,7 +91,11 @@ func mjpegFramer(f []byte, rel func()) (Frame, error) { } return nil, err } - return &fMJPEG{img: img, release: rel}, nil + fr := &fMJPEG{img: img, release: rel} + runtime.SetFinalizer(fr, func(obj Frame) { + obj.Release() + }) + return fr, nil } // decodeMJPEG decodes the frame into an image. @@ -135,6 +140,8 @@ func (f *fMJPEG) At(x, y int) color.Color { func (f *fMJPEG) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/rgb.go b/rgb.go index 0d8b37f..db77cf6 100644 --- a/rgb.go +++ b/rgb.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "image/color" + "runtime" ) type fRGB struct { @@ -55,8 +56,12 @@ func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, e } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, - roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel}, nil + f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel} + runtime.SetFinalizer(f, func(obj Frame) { + obj.Release() + }) + return f, nil } func (f *fRGB) ColorModel() color.Model { @@ -76,5 +81,7 @@ func (f *fRGB) At(x, y int) color.Color { func (f *fRGB) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } diff --git a/yuyv422.go b/yuyv422.go index fcd5607..2a286cc 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -5,6 +5,7 @@ import ( "fmt" "image" "image/color" + "runtime" ) type fYUYV422 struct { @@ -43,7 +44,11 @@ func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - return &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel}, nil + f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel} + runtime.SetFinalizer(f, func(obj Frame) { + obj.Release() + }) + return f, nil } func (f *fYUYV422) ColorModel() color.Model { @@ -67,5 +72,7 @@ func (f *fYUYV422) At(x, y int) color.Color { func (f *fYUYV422) Release() { if f.release != nil { f.release() + // Make sure it only gets called once. + f.release = nil } } From 4af642d0798285a83d466c8f11ced7b25ae3e33c Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 08:50:45 +1100 Subject: [PATCH 077/123] Flag option for image name. --- server.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index 369a7cd..0565a57 100644 --- a/server.go +++ b/server.go @@ -17,6 +17,7 @@ import ( ) var port = flag.Int("port", 8080, "Web server port number") +var path = flag.String("path", "image.jpg", "Image filename path") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Camera resolution") var format = flag.String("format", "YUYV", "Pixel format of camera") @@ -74,8 +75,8 @@ func main() { } } } - http.Handle("/image", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - readImage(cm, w, r) + http.Handle("/image.jpg", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + publishImage(cm, w, r) })) url := fmt.Sprintf(":%d", *port) if *verbose { @@ -85,7 +86,7 @@ func main() { log.Fatal(server.ListenAndServe()) } -func readImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request) { +func publishImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request) { if *verbose { log.Printf("URL request: %v", r.URL) } From ebc9ba80b30a269b3901bba271d3f37e75b400b2 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 09:11:14 +1100 Subject: [PATCH 078/123] Allow png and gif. --- server.go | 58 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/server.go b/server.go index e4435d9..80f47f2 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,8 @@ package main import ( "flag" "fmt" + "image/gif" + "image/png" "image/jpeg" "log" "net/http" @@ -41,11 +43,11 @@ func main() { } x, err := strconv.Atoi(s[0]) if err != nil { - log.Fatalf("%s: illegal width", s[0], err) + log.Fatalf("%s: illegal width: %v", s[0], err) } y, err := strconv.Atoi(s[1]) if err != nil { - log.Fatalf("%s: illegal height", s[1], err) + log.Fatalf("%s: illegal height: %v", s[1], err) } if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) @@ -75,8 +77,27 @@ func main() { } } } - http.Handle("/image.jpg", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - publishImage(cm, w, r) + var encode func(http.ResponseWriter, frame.Frame) error + if strings.HasSuffix(*path, "jpg") || strings.HasSuffix(*path, "jpeg") { + encode = encodeJpeg + } else if strings.HasSuffix(*path, "png") { + encode = encodePNG + } else if strings.HasSuffix(*path, "gif") { + encode = encodeGIF + } + http.Handle(fmt.Sprintf("/%s", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *verbose { + log.Printf("URL request: %v", r.URL) + } + f, err := cm.Snap() + if err != nil { + log.Fatalf("Getframe: %v", err) + } + if err := encode(w, f); err != nil { + log.Printf("Error writing image: %v\n", err) + } else if *verbose { + log.Printf("Wrote image successfully\n") + } })) url := fmt.Sprintf(":%d", *port) if *verbose { @@ -86,19 +107,20 @@ func main() { log.Fatal(server.ListenAndServe()) } -func publishImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request) { - if *verbose { - log.Printf("URL request: %v", r.URL) - } - frame, err := cm.Snap() - if err != nil { - log.Fatalf("Getframe: %v", err) - } - defer frame.Release() +func encodeJpeg(w http.ResponseWriter, f frame.Frame) error { + defer f.Release() w.Header().Set("Content-Type", "image/jpeg") - if err := jpeg.Encode(w, frame, nil); err != nil { - log.Printf("Error writing image: %v\n", err) - } else if *verbose { - log.Printf("Wrote image successfully\n") - } + return jpeg.Encode(w, f, nil) +} + +func encodePNG(w http.ResponseWriter, f frame.Frame) error { + defer f.Release() + w.Header().Set("Content-Type", "image/png") + return png.Encode(w, f) +} + +func encodeGIF(w http.ResponseWriter, f frame.Frame) error { + defer f.Release() + w.Header().Set("Content-Type", "image/gif") + return gif.Encode(w, f, nil) } From ec22b8109d90e71f5ac73544ced7635d931002f0 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 29 Mar 2019 09:42:27 +1100 Subject: [PATCH 079/123] Handle multiple image types. --- imageserver.service | 2 +- server.go | 72 ++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/imageserver.service b/imageserver.service index 1df80de..d3ff823 100644 --- a/imageserver.service +++ b/imageserver.service @@ -6,7 +6,7 @@ After=network.target User=root Type=simple TimeoutStopSec=10 -ExecStart=/usr/local/bin/imageserver +ExecStart=/usr/local/bin/imageserver --padded Restart=on-failure RestartSec=15s diff --git a/server.go b/server.go index 80f47f2..31254d0 100644 --- a/server.go +++ b/server.go @@ -19,7 +19,7 @@ import ( ) var port = flag.Int("port", 8080, "Web server port number") -var path = flag.String("path", "image.jpg", "Image filename path") +var path = flag.String("path", "image", "Image base filename") var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Camera resolution") var format = flag.String("format", "YUYV", "Pixel format of camera") @@ -77,27 +77,29 @@ func main() { } } } - var encode func(http.ResponseWriter, frame.Frame) error - if strings.HasSuffix(*path, "jpg") || strings.HasSuffix(*path, "jpeg") { - encode = encodeJpeg - } else if strings.HasSuffix(*path, "png") { - encode = encodePNG - } else if strings.HasSuffix(*path, "gif") { - encode = encodeGIF + encodeJpeg := func(w http.ResponseWriter, f frame.Frame) error { + w.Header().Set("Content-Type", "image/jpeg") + return jpeg.Encode(w, f, nil) } - http.Handle(fmt.Sprintf("/%s", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if *verbose { - log.Printf("URL request: %v", r.URL) - } - f, err := cm.Snap() - if err != nil { - log.Fatalf("Getframe: %v", err) - } - if err := encode(w, f); err != nil { - log.Printf("Error writing image: %v\n", err) - } else if *verbose { - log.Printf("Wrote image successfully\n") - } + encodePNG := func(w http.ResponseWriter, f frame.Frame) error { + w.Header().Set("Content-Type", "image/png") + return png.Encode(w, f) + } + encodeGIF := func(w http.ResponseWriter, f frame.Frame) error { + w.Header().Set("Content-Type", "image/gif") + return gif.Encode(w, f, nil) + } + http.Handle(fmt.Sprintf("/%s.jpg", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + publishImage(cm, w, r, encodeJpeg) + })) + http.Handle(fmt.Sprintf("/%s.jpeg", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + publishImage(cm, w, r, encodeJpeg) + })) + http.Handle(fmt.Sprintf("/%s.png", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + publishImage(cm, w, r, encodePNG) + })) + http.Handle(fmt.Sprintf("/%s.gif", *path), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + publishImage(cm, w, r, encodeGIF) })) url := fmt.Sprintf(":%d", *port) if *verbose { @@ -107,20 +109,18 @@ func main() { log.Fatal(server.ListenAndServe()) } -func encodeJpeg(w http.ResponseWriter, f frame.Frame) error { - defer f.Release() - w.Header().Set("Content-Type", "image/jpeg") - return jpeg.Encode(w, f, nil) -} - -func encodePNG(w http.ResponseWriter, f frame.Frame) error { - defer f.Release() - w.Header().Set("Content-Type", "image/png") - return png.Encode(w, f) -} - -func encodeGIF(w http.ResponseWriter, f frame.Frame) error { +func publishImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request, encode func(http.ResponseWriter, frame.Frame) error) { + if *verbose { + log.Printf("URL request: %v", r.URL) + } + f, err := cm.Snap() + if err != nil { + log.Fatalf("Getframe: %v", err) + } defer f.Release() - w.Header().Set("Content-Type", "image/gif") - return gif.Encode(w, f, nil) + if err := encode(w, f); err != nil { + log.Printf("Error writing image: %v\n", err) + } else if *verbose { + log.Printf("Wrote image successfully\n") + } } From b823a5c7244b46519a18c355225596056c1d0897 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 06:52:01 +1100 Subject: [PATCH 080/123] Add some comments. --- snapshot/snapshot.go | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 1f0ff32..1d3a37b 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -131,6 +131,7 @@ func (c *Snapper) capture() { select { // Only executed if stream is ready to receive. case c.stream <- snap{frame, index}: + // Signal to stop streaming. case <-c.stop: // Finish up. c.cam.ReleaseFrame(index) From 4e3a2738fd9dcd4323326415158e54b788a9d22a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 06:52:01 +1100 Subject: [PATCH 081/123] Add some comments. --- snapshot.go | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshot.go b/snapshot.go index 1f0ff32..1d3a37b 100644 --- a/snapshot.go +++ b/snapshot.go @@ -131,6 +131,7 @@ func (c *Snapper) capture() { select { // Only executed if stream is ready to receive. case c.stream <- snap{frame, index}: + // Signal to stop streaming. case <-c.stop: // Finish up. c.cam.ReleaseFrame(index) From c37b2954db5760d502f0f8d0c35ac77f8c10e3a7 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:22:31 +1100 Subject: [PATCH 082/123] Use stride for line width. --- frame/frame.go | 8 ++++---- frame/jpeg.go | 2 +- frame/mjpeg.go | 2 +- frame/rgb.go | 27 ++++++++++----------------- frame/yuyv422.go | 22 ++++++---------------- snapshot/snapshot.go | 8 ++++---- 6 files changed, 26 insertions(+), 43 deletions(-) diff --git a/frame/frame.go b/frame/frame.go index 82ed1e6..cec738d 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -18,18 +18,18 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func(int, int) func([]byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func(int, int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int) func([]byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, factory func(int, int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC, w, h int) (func([]byte, func()) (Frame, error), error) { +func GetFramer(format FourCC, w, h, stride int) (func([]byte, func()) (Frame, error), error) { if factory, ok := framerFactoryMap[format]; ok { - return factory(w, h), nil + return factory(w, h, stride), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/frame/jpeg.go b/frame/jpeg.go index 3411cd6..e0cf889 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -19,7 +19,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h int) func([]byte, func()) (Frame, error) { +func newJPEGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/frame/mjpeg.go b/frame/mjpeg.go index 405d405..571b902 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -76,7 +76,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h int) func([]byte, func()) (Frame, error) { +func newMJPGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/frame/rgb.go b/frame/rgb.go index db77cf6..4316dc2 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -10,7 +10,7 @@ import ( type fRGB struct { model color.Model b image.Rectangle - width int + stride int roffs int goffs int boffs int @@ -25,38 +25,31 @@ func init() { } // Return a function that is used as a framer for RGB3. -func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, 0, 1, 2) +func newFramerRGB3(w, h, stride int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, 0, 1, 2) } // Return a function that is used as a framer for BGR3. -func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, 2, 1, 0) +func newFramerBGR3(w, h, stride int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, 2, 1, 0) } // Return a function that is used as a generic RGB framer. -func newRGBFramer(w, h, r, g, b int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } +func newRGBFramer(w, h, stride, r, g, b int) func([]byte, func()) (Frame, error) { return func(buf []byte, rel func()) (Frame, error) { - return frameRGB(size, bw, w, h, r, g, b, buf, rel) + return frameRGB(h * stride, stride, w, h, r, g, b, buf, rel) } } // Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { +func frameRGB(size, stride, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { if len(b) != size { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), stride: stride, roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel} runtime.SetFinalizer(f, func(obj Frame) { obj.Release() @@ -73,7 +66,7 @@ func (f *fRGB) Bounds() image.Rectangle { } func (f *fRGB) At(x, y int) color.Color { - i := f.width*y*3 + x*3 + i := f.stride*y + x*3 return color.RGBA{f.frame[i+f.roffs], f.frame[i+f.goffs], f.frame[i+f.boffs], 0xFF} } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index 2a286cc..0f35cd2 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -1,7 +1,6 @@ package frame import ( - "flag" "fmt" "image" "image/color" @@ -11,40 +10,31 @@ import ( type fYUYV422 struct { model color.Model b image.Rectangle - width int + stride int frame []byte release func() } -var padded = flag.Bool("padded", false, "Frame has padding") - // Register a framer factory for this format. func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 2 * bw * ((h + 15) &^ 15) - } else { - size = 2 * h * w - } +func newFramerYUYV422(w, h, stride int) func([]byte, func()) (Frame, error) { return func(b []byte, rel func()) (Frame, error) { - return frameYUYV422(size, bw, w, h, b, rel) + return frameYUYV422(h * stride, stride, w, h, b, rel) } } // Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { +func frameYUYV422(size, stride, w, h int, b []byte, rel func()) (Frame, error) { if len(b) != size { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel} + f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), stride: stride, frame: b, release: rel} runtime.SetFinalizer(f, func(obj Frame) { obj.Release() }) @@ -60,7 +50,7 @@ func (f *fYUYV422) Bounds() image.Rectangle { } func (f *fYUYV422) At(x, y int) color.Color { - index := f.width*y*2 + (x&^1)*2 + index := f.stride*y + (x&^1)*2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 1d3a37b..5925640 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -78,10 +78,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if !found { return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - if c.framer, err = frame.GetFramer(format, w, h); err != nil { - return err - } - npf, nw, nh, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) + npf, nw, nh, stride, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err @@ -89,6 +86,9 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } + if c.framer, err = frame.GetFramer(format, w, h, int(stride)); err != nil { + return err + } c.cam.SetBufferCount(c.Buffers) c.cam.SetAutoWhiteBalance(true) From e665af2a2591ca093e9fcdb1c3466046e3e15050 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:22:31 +1100 Subject: [PATCH 083/123] Use stride for line width. --- frame.go | 8 ++++---- jpeg.go | 2 +- mjpeg.go | 2 +- rgb.go | 27 ++++++++++----------------- yuyv422.go | 22 ++++++---------------- 5 files changed, 22 insertions(+), 39 deletions(-) diff --git a/frame.go b/frame.go index 82ed1e6..cec738d 100644 --- a/frame.go +++ b/frame.go @@ -18,18 +18,18 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func(int, int) func([]byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func(int, int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int) func([]byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, factory func(int, int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC, w, h int) (func([]byte, func()) (Frame, error), error) { +func GetFramer(format FourCC, w, h, stride int) (func([]byte, func()) (Frame, error), error) { if factory, ok := framerFactoryMap[format]; ok { - return factory(w, h), nil + return factory(w, h, stride), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/jpeg.go b/jpeg.go index 3411cd6..e0cf889 100644 --- a/jpeg.go +++ b/jpeg.go @@ -19,7 +19,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h int) func([]byte, func()) (Frame, error) { +func newJPEGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/mjpeg.go b/mjpeg.go index 405d405..571b902 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -76,7 +76,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h int) func([]byte, func()) (Frame, error) { +func newMJPGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/rgb.go b/rgb.go index db77cf6..4316dc2 100644 --- a/rgb.go +++ b/rgb.go @@ -10,7 +10,7 @@ import ( type fRGB struct { model color.Model b image.Rectangle - width int + stride int roffs int goffs int boffs int @@ -25,38 +25,31 @@ func init() { } // Return a function that is used as a framer for RGB3. -func newFramerRGB3(w, h int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, 0, 1, 2) +func newFramerRGB3(w, h, stride int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, 0, 1, 2) } // Return a function that is used as a framer for BGR3. -func newFramerBGR3(w, h int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, 2, 1, 0) +func newFramerBGR3(w, h, stride int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, 2, 1, 0) } // Return a function that is used as a generic RGB framer. -func newRGBFramer(w, h, r, g, b int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 3 * bw * ((h + 15) &^ 15) - } else { - size = 3 * h * w - } +func newRGBFramer(w, h, stride, r, g, b int) func([]byte, func()) (Frame, error) { return func(buf []byte, rel func()) (Frame, error) { - return frameRGB(size, bw, w, h, r, g, b, buf, rel) + return frameRGB(h * stride, stride, w, h, r, g, b, buf, rel) } } // Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameRGB(size, bw, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { +func frameRGB(size, stride, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { if len(b) != size { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), width: bw, + f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), stride: stride, roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel} runtime.SetFinalizer(f, func(obj Frame) { obj.Release() @@ -73,7 +66,7 @@ func (f *fRGB) Bounds() image.Rectangle { } func (f *fRGB) At(x, y int) color.Color { - i := f.width*y*3 + x*3 + i := f.stride*y + x*3 return color.RGBA{f.frame[i+f.roffs], f.frame[i+f.goffs], f.frame[i+f.boffs], 0xFF} } diff --git a/yuyv422.go b/yuyv422.go index 2a286cc..0f35cd2 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -1,7 +1,6 @@ package frame import ( - "flag" "fmt" "image" "image/color" @@ -11,40 +10,31 @@ import ( type fYUYV422 struct { model color.Model b image.Rectangle - width int + stride int frame []byte release func() } -var padded = flag.Bool("padded", false, "Frame has padding") - // Register a framer factory for this format. func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h int) func([]byte, func()) (Frame, error) { - var size, bw int - if *padded { - bw = (w + 31) &^ 31 - size = 2 * bw * ((h + 15) &^ 15) - } else { - size = 2 * h * w - } +func newFramerYUYV422(w, h, stride int) func([]byte, func()) (Frame, error) { return func(b []byte, rel func()) (Frame, error) { - return frameYUYV422(size, bw, w, h, b, rel) + return frameYUYV422(h * stride, stride, w, h, b, rel) } } // Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameYUYV422(size, bw, w, h int, b []byte, rel func()) (Frame, error) { +func frameYUYV422(size, stride, w, h int, b []byte, rel func()) (Frame, error) { if len(b) != size { if rel != nil { defer rel() } return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) } - f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), width: bw, frame: b, release: rel} + f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), stride: stride, frame: b, release: rel} runtime.SetFinalizer(f, func(obj Frame) { obj.Release() }) @@ -60,7 +50,7 @@ func (f *fYUYV422) Bounds() image.Rectangle { } func (f *fYUYV422) At(x, y int) color.Color { - index := f.width*y*2 + (x&^1)*2 + index := f.stride*y + (x&^1)*2 if x&1 == 0 { return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} } else { From 6a86f0ab387bb9af31f9d2b6424ee1f591674f40 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:22:31 +1100 Subject: [PATCH 084/123] Use stride for line width. --- snapshot.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snapshot.go b/snapshot.go index 1d3a37b..5925640 100644 --- a/snapshot.go +++ b/snapshot.go @@ -78,10 +78,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if !found { return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - if c.framer, err = frame.GetFramer(format, w, h); err != nil { - return err - } - npf, nw, nh, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) + npf, nw, nh, stride, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err @@ -89,6 +86,9 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } + if c.framer, err = frame.GetFramer(format, w, h, int(stride)); err != nil { + return err + } c.cam.SetBufferCount(c.Buffers) c.cam.SetAutoWhiteBalance(true) From f5f251579be37746b56a86e269f0d8d12a0c5a04 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:23:15 +1100 Subject: [PATCH 085/123] Retrieve stride. --- v4l2.go | 3 ++- webcam.go | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/v4l2.go b/v4l2.go index e573783..bc2a198 100644 --- a/v4l2.go +++ b/v4l2.go @@ -288,7 +288,7 @@ func getFrameSize(fd uintptr, index uint32, code uint32) (frameSize FrameSize, e return } -func setImageFormat(fd uintptr, formatcode *uint32, width *uint32, height *uint32) (err error) { +func setImageFormat(fd uintptr, formatcode, width, height, stride *uint32) (err error) { format := &v4l2_format{ _type: V4L2_BUF_TYPE_VIDEO_CAPTURE, @@ -326,6 +326,7 @@ func setImageFormat(fd uintptr, formatcode *uint32, width *uint32, height *uint3 *width = pixReverse.Width *height = pixReverse.Height *formatcode = pixReverse.Pixelformat + *stride = pixReverse.Bytesperline return diff --git a/webcam.go b/webcam.go index 19d3559..e29fe7d 100644 --- a/webcam.go +++ b/webcam.go @@ -109,18 +109,19 @@ func (w *Webcam) GetSupportedFrameSizes(f PixelFormat) []FrameSize { // Note, that device driver can change that values. // Resulting values are returned by a function // alongside with an error if any -func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelFormat, uint32, uint32, error) { +func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelFormat, uint32, uint32, uint32, error) { code := uint32(f) cw := width ch := height + var stride uint32 - err := setImageFormat(w.fd, &code, &width, &height) + err := setImageFormat(w.fd, &code, &width, &height, &stride) if err != nil { - return 0, 0, 0, err + return 0, 0, 0, 0, err } else { - return PixelFormat(code), cw, ch, nil + return PixelFormat(code), cw, ch, stride, nil } } From a087daaec8ac9b1f6c502a7cf47228ccd78fd935 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:48:03 +1100 Subject: [PATCH 086/123] Use retrieved stride and image size. --- frame/frame.go | 8 ++++---- frame/jpeg.go | 2 +- frame/mjpeg.go | 2 +- frame/rgb.go | 15 ++++++++------- frame/yuyv422.go | 5 +++-- imageserver.service | 2 +- snapshot/snapshot.go | 4 ++-- 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/frame/frame.go b/frame/frame.go index cec738d..a387765 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -18,18 +18,18 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func(int, int, int) func([]byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func(int, int, int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int, int) func([]byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, factory func(int, int, int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC, w, h, stride int) (func([]byte, func()) (Frame, error), error) { +func GetFramer(format FourCC, w, h, stride, size int) (func([]byte, func()) (Frame, error), error) { if factory, ok := framerFactoryMap[format]; ok { - return factory(w, h, stride), nil + return factory(w, h, stride, size), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/frame/jpeg.go b/frame/jpeg.go index e0cf889..138062a 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -19,7 +19,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { +func newJPEGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/frame/mjpeg.go b/frame/mjpeg.go index 571b902..fc7275c 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -76,7 +76,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { +func newMJPGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/frame/rgb.go b/frame/rgb.go index 4316dc2..99b6826 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -10,7 +10,8 @@ import ( type fRGB struct { model color.Model b image.Rectangle - stride int + stride int + size int roffs int goffs int boffs int @@ -25,19 +26,19 @@ func init() { } // Return a function that is used as a framer for RGB3. -func newFramerRGB3(w, h, stride int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, 0, 1, 2) +func newFramerRGB3(w, h, stride, size int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, size, 0, 1, 2) } // Return a function that is used as a framer for BGR3. -func newFramerBGR3(w, h, stride int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, 2, 1, 0) +func newFramerBGR3(w, h, stride, size int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, size, 2, 1, 0) } // Return a function that is used as a generic RGB framer. -func newRGBFramer(w, h, stride, r, g, b int) func([]byte, func()) (Frame, error) { +func newRGBFramer(w, h, stride, size, r, g, b int) func([]byte, func()) (Frame, error) { return func(buf []byte, rel func()) (Frame, error) { - return frameRGB(h * stride, stride, w, h, r, g, b, buf, rel) + return frameRGB(size, stride, w, h, r, g, b, buf, rel) } } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index 0f35cd2..fbd5f8b 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -11,6 +11,7 @@ type fYUYV422 struct { model color.Model b image.Rectangle stride int + size int frame []byte release func() } @@ -20,9 +21,9 @@ func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h, stride int) func([]byte, func()) (Frame, error) { +func newFramerYUYV422(w, h, stride, size int) func([]byte, func()) (Frame, error) { return func(b []byte, rel func()) (Frame, error) { - return frameYUYV422(h * stride, stride, w, h, b, rel) + return frameYUYV422(size, stride, w, h, b, rel) } } diff --git a/imageserver.service b/imageserver.service index d3ff823..1df80de 100644 --- a/imageserver.service +++ b/imageserver.service @@ -6,7 +6,7 @@ After=network.target User=root Type=simple TimeoutStopSec=10 -ExecStart=/usr/local/bin/imageserver --padded +ExecStart=/usr/local/bin/imageserver Restart=on-failure RestartSec=15s diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 5925640..20da08c 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -78,7 +78,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if !found { return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - npf, nw, nh, stride, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) + npf, nw, nh, stride, size, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err @@ -86,7 +86,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } - if c.framer, err = frame.GetFramer(format, w, h, int(stride)); err != nil { + if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil { return err } From cc1eb09141ddd83e1fd9df34d48d4b231db1284a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:48:03 +1100 Subject: [PATCH 087/123] Use retrieved stride and image size. --- frame.go | 8 ++++---- jpeg.go | 2 +- mjpeg.go | 2 +- rgb.go | 15 ++++++++------- yuyv422.go | 5 +++-- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frame.go b/frame.go index cec738d..a387765 100644 --- a/frame.go +++ b/frame.go @@ -18,18 +18,18 @@ type Frame interface { Release() } -var framerFactoryMap = map[FourCC]func(int, int, int) func([]byte, func()) (Frame, error){} +var framerFactoryMap = map[FourCC]func(int, int, int, int) func([]byte, func()) (Frame, error){} // RegisterFramer registers a framer factory for a format. // Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int, int) func([]byte, func()) (Frame, error)) { +func RegisterFramer(format FourCC, factory func(int, int, int, int) func([]byte, func()) (Frame, error)) { framerFactoryMap[format] = factory } // GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC, w, h, stride int) (func([]byte, func()) (Frame, error), error) { +func GetFramer(format FourCC, w, h, stride, size int) (func([]byte, func()) (Frame, error), error) { if factory, ok := framerFactoryMap[format]; ok { - return factory(w, h, stride), nil + return factory(w, h, stride, size), nil } return nil, fmt.Errorf("No handler for format '%s'", format) } diff --git a/jpeg.go b/jpeg.go index e0cf889..138062a 100644 --- a/jpeg.go +++ b/jpeg.go @@ -19,7 +19,7 @@ func init() { } // Return a framer for JPEG. -func newJPEGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { +func newJPEGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { return jpegFramer } diff --git a/mjpeg.go b/mjpeg.go index 571b902..fc7275c 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -76,7 +76,7 @@ func init() { RegisterFramer("MJPG", newMJPGFramer) } -func newMJPGFramer(w, h, stride int) func([]byte, func()) (Frame, error) { +func newMJPGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { return mjpegFramer } diff --git a/rgb.go b/rgb.go index 4316dc2..99b6826 100644 --- a/rgb.go +++ b/rgb.go @@ -10,7 +10,8 @@ import ( type fRGB struct { model color.Model b image.Rectangle - stride int + stride int + size int roffs int goffs int boffs int @@ -25,19 +26,19 @@ func init() { } // Return a function that is used as a framer for RGB3. -func newFramerRGB3(w, h, stride int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, 0, 1, 2) +func newFramerRGB3(w, h, stride, size int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, size, 0, 1, 2) } // Return a function that is used as a framer for BGR3. -func newFramerBGR3(w, h, stride int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, 2, 1, 0) +func newFramerBGR3(w, h, stride, size int) func([]byte, func()) (Frame, error) { + return newRGBFramer(w, h, stride, size, 2, 1, 0) } // Return a function that is used as a generic RGB framer. -func newRGBFramer(w, h, stride, r, g, b int) func([]byte, func()) (Frame, error) { +func newRGBFramer(w, h, stride, size, r, g, b int) func([]byte, func()) (Frame, error) { return func(buf []byte, rel func()) (Frame, error) { - return frameRGB(h * stride, stride, w, h, r, g, b, buf, rel) + return frameRGB(size, stride, w, h, r, g, b, buf, rel) } } diff --git a/yuyv422.go b/yuyv422.go index 0f35cd2..fbd5f8b 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -11,6 +11,7 @@ type fYUYV422 struct { model color.Model b image.Rectangle stride int + size int frame []byte release func() } @@ -20,9 +21,9 @@ func init() { RegisterFramer("YUYV", newFramerYUYV422) } -func newFramerYUYV422(w, h, stride int) func([]byte, func()) (Frame, error) { +func newFramerYUYV422(w, h, stride, size int) func([]byte, func()) (Frame, error) { return func(b []byte, rel func()) (Frame, error) { - return frameYUYV422(h * stride, stride, w, h, b, rel) + return frameYUYV422(size, stride, w, h, b, rel) } } From b60fbb3e75d2768cb1a7da54981ab3d14fc06970 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:48:03 +1100 Subject: [PATCH 088/123] Use retrieved stride and image size. --- snapshot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapshot.go b/snapshot.go index 5925640..20da08c 100644 --- a/snapshot.go +++ b/snapshot.go @@ -78,7 +78,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if !found { return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) } - npf, nw, nh, stride, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) + npf, nw, nh, stride, size, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) if err != nil { return err @@ -86,7 +86,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } - if c.framer, err = frame.GetFramer(format, w, h, int(stride)); err != nil { + if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil { return err } From 76120cdef6cb7c22dc688f146481700eb490e300 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 07:55:12 +1100 Subject: [PATCH 089/123] Use stride and image size. --- examples/getcontrols/getcontrols.go | 2 +- examples/http_mjpeg_streamer/webcam.go | 4 ++-- examples/stdout_streamer/stdout_streamer.go | 4 ++-- v4l2.go | 3 ++- webcam.go | 9 +++++---- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index eefd062..9da10f5 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -5,7 +5,7 @@ import ( "flag" "fmt" - "github.com/blackjack/webcam" + "github.com/aamcrae/webcam" ) var device = flag.String("input", "/dev/video0", "Input video device") diff --git a/examples/http_mjpeg_streamer/webcam.go b/examples/http_mjpeg_streamer/webcam.go index adfa08c..e99bfcf 100644 --- a/examples/http_mjpeg_streamer/webcam.go +++ b/examples/http_mjpeg_streamer/webcam.go @@ -17,7 +17,7 @@ import ( "strconv" "time" - "github.com/blackjack/webcam" + "github.com/aamcrae/webcam" ) const ( @@ -118,7 +118,7 @@ FMT: } fmt.Fprintln(os.Stderr, "Requesting", format_desc[format], size.GetString()) - f, w, h, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) + f, w, h, _, _, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) if err != nil { log.Println("SetImageFormat return error", err) return diff --git a/examples/stdout_streamer/stdout_streamer.go b/examples/stdout_streamer/stdout_streamer.go index af961c1..c7549b3 100644 --- a/examples/stdout_streamer/stdout_streamer.go +++ b/examples/stdout_streamer/stdout_streamer.go @@ -6,7 +6,7 @@ // Example usage: go run stdout_streamer.go | vlc - package main -import "github.com/blackjack/webcam" +import "github.com/aamcrae/webcam" import "os" import "fmt" import "sort" @@ -74,7 +74,7 @@ func main() { choice = readChoice(fmt.Sprintf("Choose format [1-%d]: ", len(frames))) size := frames[choice-1] - f, w, h, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) + f, w, h, _, _, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) if err != nil { panic(err.Error()) diff --git a/v4l2.go b/v4l2.go index bc2a198..9b6e9db 100644 --- a/v4l2.go +++ b/v4l2.go @@ -288,7 +288,7 @@ func getFrameSize(fd uintptr, index uint32, code uint32) (frameSize FrameSize, e return } -func setImageFormat(fd uintptr, formatcode, width, height, stride *uint32) (err error) { +func setImageFormat(fd uintptr, formatcode, width, height, stride, size *uint32) (err error) { format := &v4l2_format{ _type: V4L2_BUF_TYPE_VIDEO_CAPTURE, @@ -327,6 +327,7 @@ func setImageFormat(fd uintptr, formatcode, width, height, stride *uint32) (err *height = pixReverse.Height *formatcode = pixReverse.Pixelformat *stride = pixReverse.Bytesperline + *size = pixReverse.Sizeimage return diff --git a/webcam.go b/webcam.go index e29fe7d..0e4e89f 100644 --- a/webcam.go +++ b/webcam.go @@ -109,19 +109,20 @@ func (w *Webcam) GetSupportedFrameSizes(f PixelFormat) []FrameSize { // Note, that device driver can change that values. // Resulting values are returned by a function // alongside with an error if any -func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelFormat, uint32, uint32, uint32, error) { +func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelFormat, uint32, uint32, uint32, uint32, error) { code := uint32(f) cw := width ch := height var stride uint32 + var size uint32 - err := setImageFormat(w.fd, &code, &width, &height, &stride) + err := setImageFormat(w.fd, &code, &width, &height, &stride, &size) if err != nil { - return 0, 0, 0, 0, err + return 0, 0, 0, 0, 0, err } else { - return PixelFormat(code), cw, ch, stride, nil + return PixelFormat(code), cw, ch, stride, size, nil } } From 8abf46ea04bd035d873d24fe1b9b414e15b896c0 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:33:40 +1100 Subject: [PATCH 090/123] Verbose display of control setting. --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index 31254d0..c0f03fd 100644 --- a/server.go +++ b/server.go @@ -72,6 +72,9 @@ func main() { if err != nil { log.Fatalf("Bad control value: %s (%v)", control, err) } + if *verbose { + fmt.Printf("Setting control '%s' to %d\n", s[0], val) + } if err = cm.SetControl(id, int32(val)); err != nil { log.Fatalf("SetControl error: %s (%v)", control, err) } From e46bfadd3ac0ecb635d983675bcdf7c0bb76a19b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:37:55 +1100 Subject: [PATCH 091/123] Added copyright header and ran go fmt. --- frame/frame.go | 14 ++++++++++++++ frame/jpeg.go | 14 ++++++++++++++ frame/mjpeg.go | 14 ++++++++++++++ frame/rgb.go | 22 ++++++++++++++++++---- frame/yuyv422.go | 16 +++++++++++++++- server.go | 18 ++++++++++++++++-- snapshot/snapshot.go | 30 ++++++++++++++++++++++-------- 7 files changed, 113 insertions(+), 15 deletions(-) diff --git a/frame/frame.go b/frame/frame.go index a387765..3083189 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // package frame wraps raw webcam frames as an image. package frame diff --git a/frame/jpeg.go b/frame/jpeg.go index 138062a..1eb4e5e 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( diff --git a/frame/mjpeg.go b/frame/mjpeg.go index fc7275c..4028ed3 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( diff --git a/frame/rgb.go b/frame/rgb.go index 99b6826..f5fdddf 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( @@ -11,10 +25,10 @@ type fRGB struct { model color.Model b image.Rectangle stride int - size int - roffs int - goffs int - boffs int + size int + roffs int + goffs int + boffs int frame []byte release func() } diff --git a/frame/yuyv422.go b/frame/yuyv422.go index fbd5f8b..861e3aa 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( @@ -11,7 +25,7 @@ type fYUYV422 struct { model color.Model b image.Rectangle stride int - size int + size int frame []byte release func() } diff --git a/server.go b/server.go index 31254d0..a321072 100644 --- a/server.go +++ b/server.go @@ -1,12 +1,26 @@ -// Program that serves jpeg images taken from a webcam. +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Program that serves images taken from a webcam. package main import ( "flag" "fmt" "image/gif" - "image/png" "image/jpeg" + "image/png" "log" "net/http" "strconv" diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 20da08c..643e10b 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // package snapshot is a webcam stills capture module. package snapshot @@ -19,12 +33,12 @@ type snap struct { } type Snapper struct { - cam *webcam.Webcam - Timeout uint32 - Buffers uint32 - framer func([]byte, func()) (frame.Frame, error) - stop chan struct{} - stream chan snap + cam *webcam.Webcam + Timeout uint32 + Buffers uint32 + framer func([]byte, func()) (frame.Frame, error) + stop chan struct{} + stream chan snap } // NewSnapper creates a new Snapper. @@ -156,7 +170,7 @@ func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { // Return true if frame size can accomodate request. func Match(fs webcam.FrameSize, w, h int) bool { return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) && - canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) } func canFit(min, max, step, val uint32) bool { @@ -164,5 +178,5 @@ func canFit(min, max, step, val uint32) bool { if min == max && step == 0 && val == min { return true } - return step != 0 && val >= val && val <= max && ((val - min) % step) == 0 + return step != 0 && val >= val && val <= max && ((val-min)%step) == 0 } From 91980eae2c4cec20344897abce8d020c7df44b46 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:37:55 +1100 Subject: [PATCH 092/123] Added copyright header and ran go fmt. --- frame.go | 14 ++++++++++++++ jpeg.go | 14 ++++++++++++++ mjpeg.go | 14 ++++++++++++++ rgb.go | 22 ++++++++++++++++++---- yuyv422.go | 16 +++++++++++++++- 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/frame.go b/frame.go index a387765..3083189 100644 --- a/frame.go +++ b/frame.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // package frame wraps raw webcam frames as an image. package frame diff --git a/jpeg.go b/jpeg.go index 138062a..1eb4e5e 100644 --- a/jpeg.go +++ b/jpeg.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( diff --git a/mjpeg.go b/mjpeg.go index fc7275c..4028ed3 100644 --- a/mjpeg.go +++ b/mjpeg.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( diff --git a/rgb.go b/rgb.go index 99b6826..f5fdddf 100644 --- a/rgb.go +++ b/rgb.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( @@ -11,10 +25,10 @@ type fRGB struct { model color.Model b image.Rectangle stride int - size int - roffs int - goffs int - boffs int + size int + roffs int + goffs int + boffs int frame []byte release func() } diff --git a/yuyv422.go b/yuyv422.go index fbd5f8b..861e3aa 100644 --- a/yuyv422.go +++ b/yuyv422.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package frame import ( @@ -11,7 +25,7 @@ type fYUYV422 struct { model color.Model b image.Rectangle stride int - size int + size int frame []byte release func() } From d74bdc0d3f2d72b2d12309fc4a239992f6b661b7 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:37:55 +1100 Subject: [PATCH 093/123] Added copyright header and ran go fmt. --- snapshot.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/snapshot.go b/snapshot.go index 20da08c..643e10b 100644 --- a/snapshot.go +++ b/snapshot.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // package snapshot is a webcam stills capture module. package snapshot @@ -19,12 +33,12 @@ type snap struct { } type Snapper struct { - cam *webcam.Webcam - Timeout uint32 - Buffers uint32 - framer func([]byte, func()) (frame.Frame, error) - stop chan struct{} - stream chan snap + cam *webcam.Webcam + Timeout uint32 + Buffers uint32 + framer func([]byte, func()) (frame.Frame, error) + stop chan struct{} + stream chan snap } // NewSnapper creates a new Snapper. @@ -156,7 +170,7 @@ func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { // Return true if frame size can accomodate request. func Match(fs webcam.FrameSize, w, h int) bool { return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) && - canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) + canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) } func canFit(min, max, step, val uint32) bool { @@ -164,5 +178,5 @@ func canFit(min, max, step, val uint32) bool { if min == max && step == 0 && val == min { return true } - return step != 0 && val >= val && val <= max && ((val - min) % step) == 0 + return step != 0 && val >= val && val <= max && ((val-min)%step) == 0 } From 4ac3757eb81daba300b3addfb6e4f347dc9d81dc Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:45:52 +1100 Subject: [PATCH 094/123] Move of frame code to webcam. --- frame.go => frame/frame.go | 0 jpeg.go => frame/jpeg.go | 0 mjpeg.go => frame/mjpeg.go | 0 rgb.go => frame/rgb.go | 0 yuyv422.go => frame/yuyv422.go | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename frame.go => frame/frame.go (100%) rename jpeg.go => frame/jpeg.go (100%) rename mjpeg.go => frame/mjpeg.go (100%) rename rgb.go => frame/rgb.go (100%) rename yuyv422.go => frame/yuyv422.go (100%) diff --git a/frame.go b/frame/frame.go similarity index 100% rename from frame.go rename to frame/frame.go diff --git a/jpeg.go b/frame/jpeg.go similarity index 100% rename from jpeg.go rename to frame/jpeg.go diff --git a/mjpeg.go b/frame/mjpeg.go similarity index 100% rename from mjpeg.go rename to frame/mjpeg.go diff --git a/rgb.go b/frame/rgb.go similarity index 100% rename from rgb.go rename to frame/rgb.go diff --git a/yuyv422.go b/frame/yuyv422.go similarity index 100% rename from yuyv422.go rename to frame/yuyv422.go From b4feb89430e3b08dd100e57f2fbb8a4f43ba2e86 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:53:36 +1100 Subject: [PATCH 095/123] Move snapshot to webcam repo. --- snapshot.go => snapshot/snapshot.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename snapshot.go => snapshot/snapshot.go (100%) diff --git a/snapshot.go b/snapshot/snapshot.go similarity index 100% rename from snapshot.go rename to snapshot/snapshot.go From 9af416a4d1a582433184692dec949d94dfd217db Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:55:53 +1100 Subject: [PATCH 096/123] Fix module path. --- snapshot/snapshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 643e10b..7c80cde 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -18,7 +18,7 @@ package snapshot import ( "fmt" - "github.com/aamcrae/imageserver/frame" + "github.com/aamcrae/webcam/frame" "github.com/aamcrae/webcam" ) From 7c24922490eb66f4fed3a88918792a3f26e62441 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 08:58:25 +1100 Subject: [PATCH 097/123] Moved frame and snapshot to webcam repo. --- frame/frame.go | 67 ---------------- frame/jpeg.go | 75 ----------------- frame/mjpeg.go | 186 ------------------------------------------- frame/rgb.go | 95 ---------------------- frame/yuyv422.go | 83 ------------------- server.go | 4 +- snapshot/snapshot.go | 182 ------------------------------------------ 7 files changed, 2 insertions(+), 690 deletions(-) delete mode 100644 frame/frame.go delete mode 100644 frame/jpeg.go delete mode 100644 frame/mjpeg.go delete mode 100644 frame/rgb.go delete mode 100644 frame/yuyv422.go delete mode 100644 snapshot/snapshot.go diff --git a/frame/frame.go b/frame/frame.go deleted file mode 100644 index 3083189..0000000 --- a/frame/frame.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// package frame wraps raw webcam frames as an image. -package frame - -import ( - "fmt" - "image" - - "github.com/aamcrae/webcam" -) - -type FourCC string - -// Release is called when the frame is no longer in use. -// The implementation may set a finalizer on the frame as a precaution -// in case Release is not called (which would cause a kernel resource leak). -type Frame interface { - image.Image - Release() -} - -var framerFactoryMap = map[FourCC]func(int, int, int, int) func([]byte, func()) (Frame, error){} - -// RegisterFramer registers a framer factory for a format. -// Note that only one handler can be registered for any single format. -func RegisterFramer(format FourCC, factory func(int, int, int, int) func([]byte, func()) (Frame, error)) { - framerFactoryMap[format] = factory -} - -// GetFramer returns a function that wraps the frame for this format. -func GetFramer(format FourCC, w, h, stride, size int) (func([]byte, func()) (Frame, error), error) { - if factory, ok := framerFactoryMap[format]; ok { - return factory(w, h, stride, size), nil - } - return nil, fmt.Errorf("No handler for format '%s'", format) -} - -// PixelFormatToFourCC converts the v4l2 PixelFormat to a FourCC. -func PixelFormatToFourCC(pf webcam.PixelFormat) FourCC { - b := make([]byte, 4) - b[0] = byte(pf) - b[1] = byte(pf >> 8) - b[2] = byte(pf >> 16) - b[3] = byte(pf >> 24) - return FourCC(b) -} - -// FourCCToPixelFormat converts the four character string to a v4l2 PixelFormat. -func FourCCToPixelFormat(f FourCC) (webcam.PixelFormat, error) { - if len(f) != 4 { - return 0, fmt.Errorf("%s: Illegal FourCC", f) - } - return webcam.PixelFormat(uint32(f[0]) | uint32(f[1])<<8 | uint32(f[2])<<16 | uint32(f[3])<<24), nil -} diff --git a/frame/jpeg.go b/frame/jpeg.go deleted file mode 100644 index 1eb4e5e..0000000 --- a/frame/jpeg.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package frame - -import ( - "bytes" - "image" - "image/color" - "image/jpeg" - "runtime" -) - -type fJPEG struct { - img image.Image - release func() -} - -// Register this framer for this format. -func init() { - RegisterFramer("JPEG", newJPEGFramer) -} - -// Return a framer for JPEG. -func newJPEGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { - return jpegFramer -} - -// Wrap a jpeg block in a Frame so that it can be used as an image. -func jpegFramer(f []byte, rel func()) (Frame, error) { - img, err := jpeg.Decode(bytes.NewBuffer(f)) - if err != nil { - if rel != nil { - rel() - } - return nil, err - } - fr := &fJPEG{img: img, release: rel} - runtime.SetFinalizer(fr, func(obj Frame) { - obj.Release() - }) - return fr, nil -} - -func (f *fJPEG) ColorModel() color.Model { - return f.img.ColorModel() -} - -func (f *fJPEG) Bounds() image.Rectangle { - return f.img.Bounds() -} - -func (f *fJPEG) At(x, y int) color.Color { - return f.img.At(x, y) -} - -// Done with frame, release back to camera (if required). -func (f *fJPEG) Release() { - if f.release != nil { - f.release() - // Make sure it only gets called once. - f.release = nil - } -} diff --git a/frame/mjpeg.go b/frame/mjpeg.go deleted file mode 100644 index 4028ed3..0000000 --- a/frame/mjpeg.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package frame - -import ( - "bytes" - "fmt" - "image" - "image/color" - "image/jpeg" - "runtime" -) - -type fMJPEG struct { - img image.Image - release func() -} - -const ( - sectionFlag = 0xFF - soiMarker = 0xd8 - eoiMarker = 0xd9 - dhtMarker = 0xc4 - sosMarker = 0xda -) - -// Default Huffman tables. -var default_dht []byte = []byte{ - 0xff, 0xc4, 0x01, 0xa2, - - 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - - 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - - 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, - 0x04, 0x00, 0x00, 0x01, 0x7d, - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, - 0x13, 0x51, 0x61, 0x07, - 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, - 0x15, 0x52, 0xd1, 0xf0, - 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, - 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, - 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, - 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, - - 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, - 0x04, 0x00, 0x01, 0x02, 0x77, - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, - 0x51, 0x07, 0x61, 0x71, - 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, - 0x23, 0x33, 0x52, 0xf0, - 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, - 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, - 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, - 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, -} - -// Register this framer for this format. -func init() { - RegisterFramer("MJPG", newMJPGFramer) -} - -func newMJPGFramer(w, h, stride, size int) func([]byte, func()) (Frame, error) { - return mjpegFramer -} - -// Wrap a mjpeg block in a Frame so that it can be used as an image. -// The standard jpeg decoding does not work if there are no Huffman tables, -// so check the frame and add a default table if required. -func mjpegFramer(f []byte, rel func()) (Frame, error) { - img, err := decodeMJPEG(f) - if err != nil { - if rel != nil { - rel() - } - return nil, err - } - fr := &fMJPEG{img: img, release: rel} - runtime.SetFinalizer(fr, func(obj Frame) { - obj.Release() - }) - return fr, nil -} - -// decodeMJPEG decodes the frame into an image. -func decodeMJPEG(f []byte) (image.Image, error) { - sect, err := findConfig(f) - if err != nil { - return nil, err - } - _, ok := sect[dhtMarker] - var buf *bytes.Buffer - if !ok { - s, ok := sect[sosMarker] - if !ok { - return nil, fmt.Errorf("no scan data in image") - } - // Insert the default Huffman table before the start - // of the scan data. - ins := s[0] - buf = new(bytes.Buffer) - buf.Write(f[:ins]) - buf.Write(default_dht) - buf.Write(f[ins:]) - } else { - buf = bytes.NewBuffer(f) - } - return jpeg.Decode(buf) -} - -func (f *fMJPEG) ColorModel() color.Model { - return f.img.ColorModel() -} - -func (f *fMJPEG) Bounds() image.Rectangle { - return f.img.Bounds() -} - -func (f *fMJPEG) At(x, y int) color.Color { - return f.img.At(x, y) -} - -// Done with frame, release back to camera (if required). -func (f *fMJPEG) Release() { - if f.release != nil { - f.release() - // Make sure it only gets called once. - f.release = nil - } -} - -// findConfig returns a map of the different config markers and their location. -func findConfig(f []byte) (map[byte][]int, error) { - m := make(map[byte][]int) - for l := 0; l < len(f)-1; { - if f[l] != sectionFlag { - return nil, fmt.Errorf("No section marker at location %d", l) - } - l++ - marker := f[l] - m[marker] = append(m[marker], l-1) - l++ - if marker == soiMarker || marker == eoiMarker { - continue - } - if marker == sosMarker { - break - } - // next 2 bytes are length of the section (big-endian). - if l >= len(f)-2 { - return nil, fmt.Errorf("unexpected EOF at location %d", l) - } - l += (int(f[l]) << 8) + int(f[l+1]) - } - return m, nil -} diff --git a/frame/rgb.go b/frame/rgb.go deleted file mode 100644 index f5fdddf..0000000 --- a/frame/rgb.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package frame - -import ( - "fmt" - "image" - "image/color" - "runtime" -) - -type fRGB struct { - model color.Model - b image.Rectangle - stride int - size int - roffs int - goffs int - boffs int - frame []byte - release func() -} - -// Register framers for these formats. -func init() { - RegisterFramer("RGB3", newFramerRGB3) - RegisterFramer("BGR3", newFramerBGR3) -} - -// Return a function that is used as a framer for RGB3. -func newFramerRGB3(w, h, stride, size int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, size, 0, 1, 2) -} - -// Return a function that is used as a framer for BGR3. -func newFramerBGR3(w, h, stride, size int) func([]byte, func()) (Frame, error) { - return newRGBFramer(w, h, stride, size, 2, 1, 0) -} - -// Return a function that is used as a generic RGB framer. -func newRGBFramer(w, h, stride, size, r, g, b int) func([]byte, func()) (Frame, error) { - return func(buf []byte, rel func()) (Frame, error) { - return frameRGB(size, stride, w, h, r, g, b, buf, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameRGB(size, stride, w, h, rof, gof, bof int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - f := &fRGB{model: color.RGBAModel, b: image.Rect(0, 0, w, h), stride: stride, - roffs: rof, goffs: gof, boffs: bof, frame: b, release: rel} - runtime.SetFinalizer(f, func(obj Frame) { - obj.Release() - }) - return f, nil -} - -func (f *fRGB) ColorModel() color.Model { - return f.model -} - -func (f *fRGB) Bounds() image.Rectangle { - return f.b -} - -func (f *fRGB) At(x, y int) color.Color { - i := f.stride*y + x*3 - return color.RGBA{f.frame[i+f.roffs], f.frame[i+f.goffs], f.frame[i+f.boffs], 0xFF} -} - -// Done with frame, release back to camera (if required). -func (f *fRGB) Release() { - if f.release != nil { - f.release() - // Make sure it only gets called once. - f.release = nil - } -} diff --git a/frame/yuyv422.go b/frame/yuyv422.go deleted file mode 100644 index 861e3aa..0000000 --- a/frame/yuyv422.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package frame - -import ( - "fmt" - "image" - "image/color" - "runtime" -) - -type fYUYV422 struct { - model color.Model - b image.Rectangle - stride int - size int - frame []byte - release func() -} - -// Register a framer factory for this format. -func init() { - RegisterFramer("YUYV", newFramerYUYV422) -} - -func newFramerYUYV422(w, h, stride, size int) func([]byte, func()) (Frame, error) { - return func(b []byte, rel func()) (Frame, error) { - return frameYUYV422(size, stride, w, h, b, rel) - } -} - -// Wrap a raw webcam frame in a Frame so that it can be used as an image. -func frameYUYV422(size, stride, w, h int, b []byte, rel func()) (Frame, error) { - if len(b) != size { - if rel != nil { - defer rel() - } - return nil, fmt.Errorf("Wrong frame length (exp: %d, read %d)", size, len(b)) - } - f := &fYUYV422{model: color.YCbCrModel, b: image.Rect(0, 0, w, h), stride: stride, frame: b, release: rel} - runtime.SetFinalizer(f, func(obj Frame) { - obj.Release() - }) - return f, nil -} - -func (f *fYUYV422) ColorModel() color.Model { - return f.model -} - -func (f *fYUYV422) Bounds() image.Rectangle { - return f.b -} - -func (f *fYUYV422) At(x, y int) color.Color { - index := f.stride*y + (x&^1)*2 - if x&1 == 0 { - return color.YCbCr{f.frame[index], f.frame[index+1], f.frame[index+3]} - } else { - return color.YCbCr{f.frame[index+2], f.frame[index+1], f.frame[index+3]} - } -} - -// Done with frame, release back to camera (if required). -func (f *fYUYV422) Release() { - if f.release != nil { - f.release() - // Make sure it only gets called once. - f.release = nil - } -} diff --git a/server.go b/server.go index f9ea0b0..b0a4def 100644 --- a/server.go +++ b/server.go @@ -27,8 +27,8 @@ import ( "strings" "time" - "github.com/aamcrae/imageserver/frame" - "github.com/aamcrae/imageserver/snapshot" + "github.com/aamcrae/webcam/frame" + "github.com/aamcrae/webcam/snapshot" "github.com/aamcrae/webcam" ) diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go deleted file mode 100644 index 643e10b..0000000 --- a/snapshot/snapshot.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// package snapshot is a webcam stills capture module. -package snapshot - -import ( - "fmt" - - "github.com/aamcrae/imageserver/frame" - "github.com/aamcrae/webcam" -) - -const ( - defaultTimeout = 5 - defaultBuffers = 16 -) - -type snap struct { - frame []byte - index uint32 -} - -type Snapper struct { - cam *webcam.Webcam - Timeout uint32 - Buffers uint32 - framer func([]byte, func()) (frame.Frame, error) - stop chan struct{} - stream chan snap -} - -// NewSnapper creates a new Snapper. -func NewSnapper() *Snapper { - return &Snapper{Timeout: defaultTimeout, Buffers: defaultBuffers} -} - -// Close releases all current frames and shuts down the webcam. -func (c *Snapper) Close() { - if c.cam != nil { - c.stop <- struct{}{} - // Flush any remaining frames. - for f := range c.stream { - c.cam.ReleaseFrame(f.index) - } - c.cam.StopStreaming() - c.cam.Close() - c.cam = nil - } -} - -// Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { - pf, err := frame.FourCCToPixelFormat(format) - if err != nil { - return err - } - if c.cam != nil { - c.Close() - } - cam, err := webcam.Open(device) - if err != nil { - return err - } - c.cam = cam - c.stop = make(chan struct{}, 1) - c.stream = make(chan snap, 0) - // Get the supported formats and their descriptions. - mf := c.cam.GetSupportedFormats() - _, ok := mf[pf] - if !ok { - return fmt.Errorf("%s: unsupported format: %s", device, format) - } - var found bool - for _, value := range c.cam.GetSupportedFrameSizes(pf) { - if Match(value, w, h) { - found = true - break - } - } - if !found { - return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h) - } - npf, nw, nh, stride, size, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h)) - - if err != nil { - return err - } - if npf != pf || w != int(nw) || h != int(nh) { - fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) - } - if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil { - return err - } - - c.cam.SetBufferCount(c.Buffers) - c.cam.SetAutoWhiteBalance(true) - if err := c.cam.StartStreaming(); err != nil { - return err - } - go c.capture() - return nil -} - -// Snap returns one frame from the camera. -func (c *Snapper) Snap() (frame.Frame, error) { - snap, ok := <-c.stream - if !ok { - return nil, fmt.Errorf("No frame received") - } - return c.framer(snap.frame, func() { - c.cam.ReleaseFrame(snap.index) - }) -} - -// capture continually reads frames and either discards them or -// sends them to a channel that is ready to receive them. -func (c *Snapper) capture() { - for { - err := c.cam.WaitForFrame(c.Timeout) - - switch err.(type) { - case nil: - case *webcam.Timeout: - continue - default: - panic(err) - } - - frame, index, err := c.cam.GetFrame() - if err != nil { - panic(err) - } - select { - // Only executed if stream is ready to receive. - case c.stream <- snap{frame, index}: - // Signal to stop streaming. - case <-c.stop: - // Finish up. - c.cam.ReleaseFrame(index) - close(c.stream) - return - default: - c.cam.ReleaseFrame(index) - } - } -} - -// GetControl returns the current value of a camera control. -func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) { - return c.cam.GetControl(id) -} - -// SetControl sets the selected camera control. -func (c *Snapper) SetControl(id webcam.ControlID, value int32) error { - return c.cam.SetControl(id, value) -} - -// Return true if frame size can accomodate request. -func Match(fs webcam.FrameSize, w, h int) bool { - return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) && - canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h)) -} - -func canFit(min, max, step, val uint32) bool { - // Fixed size exact match. - if min == max && step == 0 && val == min { - return true - } - return step != 0 && val >= val && val <= max && ((val-min)%step) == 0 -} From 4b4bab362bdf609fb71efedcfb5060148ccfece1 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 09:03:55 +1100 Subject: [PATCH 098/123] Moved snapshot to frame. --- {snapshot => frame}/snapshot.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) rename {snapshot => frame}/snapshot.go (90%) diff --git a/snapshot/snapshot.go b/frame/snapshot.go similarity index 90% rename from snapshot/snapshot.go rename to frame/snapshot.go index 7c80cde..ba425d2 100644 --- a/snapshot/snapshot.go +++ b/frame/snapshot.go @@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// package snapshot is a webcam stills capture module. -package snapshot +package frame import ( "fmt" - "github.com/aamcrae/webcam/frame" "github.com/aamcrae/webcam" ) @@ -36,7 +34,7 @@ type Snapper struct { cam *webcam.Webcam Timeout uint32 Buffers uint32 - framer func([]byte, func()) (frame.Frame, error) + framer func([]byte, func()) (Frame, error) stop chan struct{} stream chan snap } @@ -61,8 +59,8 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { - pf, err := frame.FourCCToPixelFormat(format) +func (c *Snapper) Open(device string, format FourCC, w, h int) error { + pf, err := FourCCToPixelFormat(format) if err != nil { return err } @@ -100,7 +98,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } - if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil { + if c.framer, err = GetFramer(format, w, h, int(stride), int(size)); err != nil { return err } @@ -114,7 +112,7 @@ func (c *Snapper) Open(device string, format frame.FourCC, w, h int) error { } // Snap returns one frame from the camera. -func (c *Snapper) Snap() (frame.Frame, error) { +func (c *Snapper) Snap() (Frame, error) { snap, ok := <-c.stream if !ok { return nil, fmt.Errorf("No frame received") From d9eccdab0b8b02d920f3705050d216513e60694d Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 09:04:43 +1100 Subject: [PATCH 099/123] New reference for snapshot. --- server.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index b0a4def..029e98c 100644 --- a/server.go +++ b/server.go @@ -28,7 +28,6 @@ import ( "time" "github.com/aamcrae/webcam/frame" - "github.com/aamcrae/webcam/snapshot" "github.com/aamcrae/webcam" ) @@ -66,7 +65,7 @@ func main() { if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } - cm := snapshot.NewSnapper() + cm := frame.NewSnapper() if err := cm.Open(*device, frame.FourCC(*format), x, y); err != nil { log.Fatalf("%s: %v", *device, err) } @@ -126,7 +125,7 @@ func main() { log.Fatal(server.ListenAndServe()) } -func publishImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request, encode func(http.ResponseWriter, frame.Frame) error) { +func publishImage(cm *frame.Snapper, w http.ResponseWriter, r *http.Request, encode func(http.ResponseWriter, frame.Frame) error) { if *verbose { log.Printf("URL request: %v", r.URL) } From 9a935573adbdc23b978fcff240029e72800fcd30 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 09:33:11 +1100 Subject: [PATCH 100/123] Better error handling. --- server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 029e98c..dd0308d 100644 --- a/server.go +++ b/server.go @@ -131,7 +131,8 @@ func publishImage(cm *frame.Snapper, w http.ResponseWriter, r *http.Request, enc } f, err := cm.Snap() if err != nil { - log.Fatalf("Getframe: %v", err) + log.Printf("Getframe: %v", err) + return } defer f.Release() if err := encode(w, f); err != nil { From 519d49312c618e0ebe0592291e12fe80979135de Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 09:44:55 +1100 Subject: [PATCH 101/123] Fix error handling. --- server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.go b/server.go index dd0308d..43c3f21 100644 --- a/server.go +++ b/server.go @@ -132,11 +132,13 @@ func publishImage(cm *frame.Snapper, w http.ResponseWriter, r *http.Request, enc f, err := cm.Snap() if err != nil { log.Printf("Getframe: %v", err) + w.WriteHeader(http.StatusInternalServerError) return } defer f.Release() if err := encode(w, f); err != nil { log.Printf("Error writing image: %v\n", err) + w.WriteHeader(http.StatusInternalServerError) } else if *verbose { log.Printf("Wrote image successfully\n") } From 044868d896e0e30bc110deaf5efe59c855329802 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 30 Mar 2019 10:11:45 +1100 Subject: [PATCH 102/123] Add more controls, and allow control list. --- server.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 43c3f21..4eb9259 100644 --- a/server.go +++ b/server.go @@ -37,7 +37,7 @@ var device = flag.String("input", "/dev/video0", "Input video device") var resolution = flag.String("resolution", "800x600", "Camera resolution") var format = flag.String("format", "YUYV", "Pixel format of camera") var controls = flag.String("controls", "", - "Control parameters for camera") + "Control parameters for camera (use --controls=list to list controls)") var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") @@ -46,6 +46,12 @@ var cnames map[string]webcam.ControlID = map[string]webcam.ControlID{ "power_line_frequency": 0x00980918, "brightness": 0x00980900, "contrast": 0x00980901, + "autoiso": 0x009a0918, + "autoexp": 0x009a0901, + "saturation": 0x00980902, + "sharpness": 0x0098091b, + "rotate": 0x00980922, + "stabilization": 0x009a0916, } func main() { @@ -62,6 +68,13 @@ func main() { if err != nil { log.Fatalf("%s: illegal height: %v", s[1], err) } + if *controls == "list" { + fmt.Printf("Control list (not all cameras may support all options):\n") + for c, _ := range cnames { + fmt.Printf(" %s\n", c) + } + return + } if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } From 2769f32f3771d8691618ba113f1862022727c58a Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 3 Apr 2019 15:49:14 +1100 Subject: [PATCH 103/123] No need to keep size in frame structure. --- frame/rgb.go | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/rgb.go b/frame/rgb.go index f5fdddf..f1e421f 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -25,7 +25,6 @@ type fRGB struct { model color.Model b image.Rectangle stride int - size int roffs int goffs int boffs int From 86827882f87042c73d523926b8bc0d54b6d40578 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 3 Apr 2019 16:04:25 +1100 Subject: [PATCH 104/123] Add close if an error occurs. --- frame/snapshot.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frame/snapshot.go b/frame/snapshot.go index ba425d2..9605e1f 100644 --- a/frame/snapshot.go +++ b/frame/snapshot.go @@ -59,7 +59,7 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format FourCC, w, h int) error { +func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { pf, err := FourCCToPixelFormat(format) if err != nil { return err @@ -71,6 +71,12 @@ func (c *Snapper) Open(device string, format FourCC, w, h int) error { if err != nil { return err } + // Add a defer function that closes the camera in the event of an error. + defer func() { + if ret != nil { + c.Close() + } + }() c.cam = cam c.stop = make(chan struct{}, 1) c.stream = make(chan snap, 0) From f63d95c35194b19271a54b04948287abe481a8df Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 15:22:47 +1100 Subject: [PATCH 105/123] Fix comparison. --- frame/snapshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/snapshot.go b/frame/snapshot.go index 9605e1f..2bdc90f 100644 --- a/frame/snapshot.go +++ b/frame/snapshot.go @@ -182,5 +182,5 @@ func canFit(min, max, step, val uint32) bool { if min == max && step == 0 && val == min { return true } - return step != 0 && val >= val && val <= max && ((val-min)%step) == 0 + return step != 0 && val >= min && val <= max && ((val-min)%step) == 0 } From aca197aac1db3d3abd06b47503bff9421b6b111d Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 15:31:02 +1100 Subject: [PATCH 106/123] Close input channel on error. --- .gitignore | 3 +++ frame/snapshot.go | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9e2154 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +examples/getcontrols/getcontrols +examples/http_mjpeg_streamer/http_mjpeg_streamer +examples/stdout_streamer/stdout_streamer diff --git a/frame/snapshot.go b/frame/snapshot.go index 2bdc90f..00e7d5b 100644 --- a/frame/snapshot.go +++ b/frame/snapshot.go @@ -74,6 +74,7 @@ func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { // Add a defer function that closes the camera in the event of an error. defer func() { if ret != nil { + close(c.stream) c.Close() } }() From 75f326fccddd420d42e618359e091b140143451b Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:08:12 +1100 Subject: [PATCH 107/123] Fix service name --- imageserver.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imageserver.service b/imageserver.service index 1df80de..2a4ef46 100644 --- a/imageserver.service +++ b/imageserver.service @@ -1,5 +1,5 @@ [Unit] -Description=MeterMan image server +Description=webcam image server After=network.target [Service] From 76678a020cc337c07b3c372da12233a614c115b3 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:15:35 +1100 Subject: [PATCH 108/123] go fmt --- server.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server.go b/server.go index 4eb9259..a112b13 100644 --- a/server.go +++ b/server.go @@ -27,8 +27,8 @@ import ( "strings" "time" - "github.com/aamcrae/webcam/frame" "github.com/aamcrae/webcam" + "github.com/aamcrae/webcam/frame" ) var port = flag.Int("port", 8080, "Web server port number") @@ -47,11 +47,11 @@ var cnames map[string]webcam.ControlID = map[string]webcam.ControlID{ "brightness": 0x00980900, "contrast": 0x00980901, "autoiso": 0x009a0918, - "autoexp": 0x009a0901, - "saturation": 0x00980902, - "sharpness": 0x0098091b, - "rotate": 0x00980922, - "stabilization": 0x009a0916, + "autoexp": 0x009a0901, + "saturation": 0x00980902, + "sharpness": 0x0098091b, + "rotate": 0x00980922, + "stabilization": 0x009a0916, } func main() { From 550be1506e1a2e2f973736c463bed7d75da5cf6d Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:26:53 +1100 Subject: [PATCH 109/123] Fix comments. --- frame/snapshot.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frame/snapshot.go b/frame/snapshot.go index 00e7d5b..3091b67 100644 --- a/frame/snapshot.go +++ b/frame/snapshot.go @@ -82,8 +82,7 @@ func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { c.stop = make(chan struct{}, 1) c.stream = make(chan snap, 0) // Get the supported formats and their descriptions. - mf := c.cam.GetSupportedFormats() - _, ok := mf[pf] + _, ok := c.cam.GetSupportedFormats()[pf] if !ok { return fmt.Errorf("%s: unsupported format: %s", device, format) } @@ -129,8 +128,8 @@ func (c *Snapper) Snap() (Frame, error) { }) } -// capture continually reads frames and either discards them or -// sends them to a channel that is ready to receive them. +// capture continually reads frames and either discards the frames or +// sends them to a channel that is ready. func (c *Snapper) capture() { for { err := c.cam.WaitForFrame(c.Timeout) From 6bddef7d3a2e4f9ccbe28e98f27efa19498d3d28 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:28:46 +1100 Subject: [PATCH 110/123] Move files. --- CONTRIBUTING.md | 28 --- LICENSE | 201 ------------------ README.md => examples/imageserver/README.md | 0 .../imageserver/imageserver.service | 0 server.go => examples/imageserver/server.go | 0 5 files changed, 229 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE rename README.md => examples/imageserver/README.md (100%) rename imageserver.service => examples/imageserver/imageserver.service (100%) rename server.go => examples/imageserver/server.go (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 939e534..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,28 +0,0 @@ -# How to Contribute - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - -## Contributor License Agreement - -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to to see -your current agreements on file or to sign a new one. - -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. - -## Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/examples/imageserver/README.md similarity index 100% rename from README.md rename to examples/imageserver/README.md diff --git a/imageserver.service b/examples/imageserver/imageserver.service similarity index 100% rename from imageserver.service rename to examples/imageserver/imageserver.service diff --git a/server.go b/examples/imageserver/server.go similarity index 100% rename from server.go rename to examples/imageserver/server.go From db5e36a87bea732913f4fe3895f62267a994817f Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:29:07 +1100 Subject: [PATCH 111/123] Rm .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 765aad7..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -imageserver From 3f48f2509d084afe295cc80b1b1e4224c8bf218e Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Fri, 5 Apr 2019 16:31:39 +1100 Subject: [PATCH 112/123] Add imageserver to ,gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e9e2154..bfa9083 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ examples/getcontrols/getcontrols examples/http_mjpeg_streamer/http_mjpeg_streamer examples/stdout_streamer/stdout_streamer +examples/imageserver/imageserver From d05d7d2e8f8caa40ad9b3e5c48ca7c0a82a3101e Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 2 Dec 2019 16:25:38 +1100 Subject: [PATCH 113/123] Add imageserver to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e9e2154..bfa9083 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ examples/getcontrols/getcontrols examples/http_mjpeg_streamer/http_mjpeg_streamer examples/stdout_streamer/stdout_streamer +examples/imageserver/imageserver From a8fde7f093b81af0ba4069ddd13c0c957b8c70f6 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 4 Feb 2020 13:40:11 +1100 Subject: [PATCH 114/123] Move snapshot to separate directory. --- examples/imageserver/server.go | 5 +++-- {frame => snapshot}/snapshot.go | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) rename {frame => snapshot}/snapshot.go (89%) diff --git a/examples/imageserver/server.go b/examples/imageserver/server.go index a112b13..6dff208 100644 --- a/examples/imageserver/server.go +++ b/examples/imageserver/server.go @@ -29,6 +29,7 @@ import ( "github.com/aamcrae/webcam" "github.com/aamcrae/webcam/frame" + "github.com/aamcrae/webcam/snapshot" ) var port = flag.Int("port", 8080, "Web server port number") @@ -78,7 +79,7 @@ func main() { if *startDelay != 0 { time.Sleep(time.Duration(*startDelay) * time.Second) } - cm := frame.NewSnapper() + cm := snapshot.NewSnapper() if err := cm.Open(*device, frame.FourCC(*format), x, y); err != nil { log.Fatalf("%s: %v", *device, err) } @@ -138,7 +139,7 @@ func main() { log.Fatal(server.ListenAndServe()) } -func publishImage(cm *frame.Snapper, w http.ResponseWriter, r *http.Request, encode func(http.ResponseWriter, frame.Frame) error) { +func publishImage(cm *snapshot.Snapper, w http.ResponseWriter, r *http.Request, encode func(http.ResponseWriter, frame.Frame) error) { if *verbose { log.Printf("URL request: %v", r.URL) } diff --git a/frame/snapshot.go b/snapshot/snapshot.go similarity index 89% rename from frame/snapshot.go rename to snapshot/snapshot.go index 3091b67..035c388 100644 --- a/frame/snapshot.go +++ b/snapshot/snapshot.go @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package frame +// snapshot extracts image frames from a video stream. +package snapshot import ( "fmt" "github.com/aamcrae/webcam" + "github.com/aamcrae/webcam/frame" ) const ( @@ -26,7 +28,7 @@ const ( ) type snap struct { - frame []byte + frm []byte index uint32 } @@ -34,7 +36,7 @@ type Snapper struct { cam *webcam.Webcam Timeout uint32 Buffers uint32 - framer func([]byte, func()) (Frame, error) + framer func([]byte, func()) (frame.Frame, error) stop chan struct{} stream chan snap } @@ -59,8 +61,8 @@ func (c *Snapper) Close() { } // Open initialises the webcam ready for use, and begins streaming. -func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { - pf, err := FourCCToPixelFormat(format) +func (c *Snapper) Open(device string, format frame.FourCC, w, h int) (ret error) { + pf, err := frame.FourCCToPixelFormat(format) if err != nil { return err } @@ -104,7 +106,7 @@ func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { if npf != pf || w != int(nw) || h != int(nh) { fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh) } - if c.framer, err = GetFramer(format, w, h, int(stride), int(size)); err != nil { + if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil { return err } @@ -118,12 +120,12 @@ func (c *Snapper) Open(device string, format FourCC, w, h int) (ret error) { } // Snap returns one frame from the camera. -func (c *Snapper) Snap() (Frame, error) { +func (c *Snapper) Snap() (frame.Frame, error) { snap, ok := <-c.stream if !ok { return nil, fmt.Errorf("No frame received") } - return c.framer(snap.frame, func() { + return c.framer(snap.frm, func() { c.cam.ReleaseFrame(snap.index) }) } From 9d1c9d7c07c6c86f491f800ac6238c1834f9a497 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 7 Jul 2020 11:35:40 +1000 Subject: [PATCH 115/123] server.go: Add delay after streaming starts. Add a delay after streaming starts and before the controls are set. Some cameras don't respond immediately. --- examples/imageserver/server.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/imageserver/server.go b/examples/imageserver/server.go index a112b13..cc6c563 100644 --- a/examples/imageserver/server.go +++ b/examples/imageserver/server.go @@ -38,7 +38,7 @@ var resolution = flag.String("resolution", "800x600", "Camera resolution") var format = flag.String("format", "YUYV", "Pixel format of camera") var controls = flag.String("controls", "", "Control parameters for camera (use --controls=list to list controls)") -var startDelay = flag.Int("delay", 0, "Delay at start (seconds)") +var startDelay = flag.Int("delay", 2, "Delay at start (seconds)") var verbose = flag.Bool("v", false, "Log more information") var cnames map[string]webcam.ControlID = map[string]webcam.ControlID{ @@ -82,6 +82,9 @@ func main() { if err := cm.Open(*device, frame.FourCC(*format), x, y); err != nil { log.Fatalf("%s: %v", *device, err) } + if *startDelay != 0 { + time.Sleep(time.Duration(*startDelay) * time.Second) + } defer cm.Close() // Set camera controls. if len(*controls) != 0 { From 29f5691572c3bba23deebb7110ee9da038aeb10e Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 28 Jul 2020 20:13:09 +1000 Subject: [PATCH 116/123] getcontrols: Sort controls by name --- examples/getcontrols/getcontrols.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 9da10f5..c0b2b16 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -4,12 +4,24 @@ package main import ( "flag" "fmt" + "sort" "github.com/aamcrae/webcam" ) var device = flag.String("input", "/dev/video0", "Input video device") +type control struct { + id webcam.ControlID + name string + min, max int32 +} +type ByName []control + +func (a ByName) Len() int { return len(a) } +func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByName) Less(i, j int) bool { return a[i].name < a[j].name } + func main() { flag.Parse() cam, err := webcam.Open(*device) @@ -34,7 +46,18 @@ func main() { cmap := cam.GetControls() fmt.Println("Available controls: ") - for id, c := range cmap { - fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", id, c.Name, c.Min, c.Max) + var clist []control + for id, cm := range cmap { + var c control + c.id = id + c.name = cm.Name + c.min = cm.Min + c.max = cm.Max + clist = append(clist, c) + } + sort.Sort(ByName(clist)) + for _, cl := range clist { + fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", cl.id, + cl.name, cl.min, cl.max) } } From 109e8c20867d2702a0e935b1cc0d78102f786bd3 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 28 Jul 2020 20:37:22 +1000 Subject: [PATCH 117/123] getcontrols: Fix panic arguments. --- examples/getcontrols/getcontrols.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index c0b2b16..564a0d9 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -26,7 +26,7 @@ func main() { flag.Parse() cam, err := webcam.Open(*device) if err != nil { - panic(err.Error()) + panic(fmt.Errorf("%s: %v", *device, err.Error())) } defer cam.Close() From 0ad1b6c8522527da39f0a2b429f7ea9602f224d3 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 28 Jul 2020 21:54:00 +1000 Subject: [PATCH 118/123] getcontrols: use byName --- examples/getcontrols/getcontrols.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 564a0d9..13421b9 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -16,11 +16,11 @@ type control struct { name string min, max int32 } -type ByName []control +type byName []control -func (a ByName) Len() int { return len(a) } -func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByName) Less(i, j int) bool { return a[i].name < a[j].name } +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].name < a[j].name } func main() { flag.Parse() @@ -55,7 +55,7 @@ func main() { c.max = cm.Max clist = append(clist, c) } - sort.Sort(ByName(clist)) + sort.Sort(byName(clist)) for _, cl := range clist { fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", cl.id, cl.name, cl.min, cl.max) From eb2233728d2634b7fe7e839c19e6f016b687a2ac Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Tue, 28 Jul 2020 21:57:26 +1000 Subject: [PATCH 119/123] webcam: go vet and go fmt --- examples/getcontrols/getcontrols.go | 4 ++-- webcam.go | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 13421b9..3f83fe1 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -12,8 +12,8 @@ import ( var device = flag.String("input", "/dev/video0", "Input video device") type control struct { - id webcam.ControlID - name string + id webcam.ControlID + name string min, max int32 } type byName []control diff --git a/webcam.go b/webcam.go index 0e4e89f..76b7474 100644 --- a/webcam.go +++ b/webcam.go @@ -6,8 +6,6 @@ package webcam import ( "errors" "golang.org/x/sys/unix" - "reflect" - "unsafe" ) // Webcam object @@ -282,11 +280,3 @@ func (w *Webcam) SetAutoWhiteBalance(val bool) error { } return setControl(w.fd, V4L2_CID_AUTO_WHITE_BALANCE, v) } - -func gobytes(p unsafe.Pointer, n int) []byte { - - h := reflect.SliceHeader{uintptr(p), n, n} - s := *(*[]byte)(unsafe.Pointer(&h)) - - return s -} From da587d15c52c9ac3cfbe7acb6300e275e88f15a9 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 29 Jul 2020 10:08:54 +1000 Subject: [PATCH 120/123] getcontrols: Simplify sort. --- examples/getcontrols/getcontrols.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 3f83fe1..7a71aa1 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -16,11 +16,6 @@ type control struct { name string min, max int32 } -type byName []control - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { return a[i].name < a[j].name } func main() { flag.Parse() @@ -55,7 +50,9 @@ func main() { c.max = cm.Max clist = append(clist, c) } - sort.Sort(byName(clist)) + sort.Slice(clist, func(i, j int) bool { + return clist[i].name < clist[j].name + }) for _, cl := range clist { fmt.Printf("ID:%08x %-32s Min: %4d Max: %5d\n", cl.id, cl.name, cl.min, cl.max) From 4bfe5bc295197c04f73e1ff8277337e7660ec64c Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Sat, 24 Oct 2020 15:24:37 +1100 Subject: [PATCH 121/123] imageserver: Add arguments to service --- examples/imageserver/imageserver.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/imageserver/imageserver.service b/examples/imageserver/imageserver.service index 2a4ef46..b4036a9 100644 --- a/examples/imageserver/imageserver.service +++ b/examples/imageserver/imageserver.service @@ -6,7 +6,7 @@ After=network.target User=root Type=simple TimeoutStopSec=10 -ExecStart=/usr/local/bin/imageserver +ExecStart=/usr/local/bin/imageserver --delay=2 --controls=focus=175,power_line_frequency=1 Restart=on-failure RestartSec=15s From b40db1db5aefe597406d6f178b5f14229bf203a0 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 25 Nov 2020 12:03:11 +1100 Subject: [PATCH 122/123] Change import path ready for pull request --- examples/getcontrols/getcontrols.go | 2 +- examples/http_mjpeg_streamer/webcam.go | 2 +- examples/imageserver/server.go | 6 +++--- examples/stdout_streamer/stdout_streamer.go | 2 +- frame/frame.go | 2 +- snapshot/snapshot.go | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/getcontrols/getcontrols.go b/examples/getcontrols/getcontrols.go index 7a71aa1..e5d5324 100644 --- a/examples/getcontrols/getcontrols.go +++ b/examples/getcontrols/getcontrols.go @@ -6,7 +6,7 @@ import ( "fmt" "sort" - "github.com/aamcrae/webcam" + "github.com/blackjack/webcam" ) var device = flag.String("input", "/dev/video0", "Input video device") diff --git a/examples/http_mjpeg_streamer/webcam.go b/examples/http_mjpeg_streamer/webcam.go index e99bfcf..54a031a 100644 --- a/examples/http_mjpeg_streamer/webcam.go +++ b/examples/http_mjpeg_streamer/webcam.go @@ -17,7 +17,7 @@ import ( "strconv" "time" - "github.com/aamcrae/webcam" + "github.com/blackjack/webcam" ) const ( diff --git a/examples/imageserver/server.go b/examples/imageserver/server.go index 0aa7a9a..a9c6f29 100644 --- a/examples/imageserver/server.go +++ b/examples/imageserver/server.go @@ -27,9 +27,9 @@ import ( "strings" "time" - "github.com/aamcrae/webcam" - "github.com/aamcrae/webcam/frame" - "github.com/aamcrae/webcam/snapshot" + "github.com/blackjack/webcam" + "github.com/blackjack/webcam/frame" + "github.com/blackjack/webcam/snapshot" ) var port = flag.Int("port", 8080, "Web server port number") diff --git a/examples/stdout_streamer/stdout_streamer.go b/examples/stdout_streamer/stdout_streamer.go index c7549b3..0d3391f 100644 --- a/examples/stdout_streamer/stdout_streamer.go +++ b/examples/stdout_streamer/stdout_streamer.go @@ -6,7 +6,7 @@ // Example usage: go run stdout_streamer.go | vlc - package main -import "github.com/aamcrae/webcam" +import "github.com/blackjack/webcam" import "os" import "fmt" import "sort" diff --git a/frame/frame.go b/frame/frame.go index 3083189..d6010dc 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -19,7 +19,7 @@ import ( "fmt" "image" - "github.com/aamcrae/webcam" + "github.com/blackjack/webcam" ) type FourCC string diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 035c388..c1161c0 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -18,8 +18,8 @@ package snapshot import ( "fmt" - "github.com/aamcrae/webcam" - "github.com/aamcrae/webcam/frame" + "github.com/blackjack/webcam" + "github.com/blackjack/webcam/frame" ) const ( From 84935a1271919e1bca876b7c5100cd82dddcc132 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Wed, 25 Nov 2020 13:44:42 +1100 Subject: [PATCH 123/123] Update file headers for existing licence --- examples/imageserver/server.go | 14 -------------- frame/frame.go | 14 -------------- frame/jpeg.go | 14 -------------- frame/mjpeg.go | 14 -------------- frame/rgb.go | 14 -------------- frame/yuyv422.go | 14 -------------- snapshot/snapshot.go | 14 -------------- 7 files changed, 98 deletions(-) diff --git a/examples/imageserver/server.go b/examples/imageserver/server.go index a9c6f29..fc9be9e 100644 --- a/examples/imageserver/server.go +++ b/examples/imageserver/server.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // Program that serves images taken from a webcam. package main diff --git a/frame/frame.go b/frame/frame.go index d6010dc..da73e5e 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // package frame wraps raw webcam frames as an image. package frame diff --git a/frame/jpeg.go b/frame/jpeg.go index 1eb4e5e..138062a 100644 --- a/frame/jpeg.go +++ b/frame/jpeg.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package frame import ( diff --git a/frame/mjpeg.go b/frame/mjpeg.go index 4028ed3..fc7275c 100644 --- a/frame/mjpeg.go +++ b/frame/mjpeg.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package frame import ( diff --git a/frame/rgb.go b/frame/rgb.go index f1e421f..4142c96 100644 --- a/frame/rgb.go +++ b/frame/rgb.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package frame import ( diff --git a/frame/yuyv422.go b/frame/yuyv422.go index 861e3aa..fecc967 100644 --- a/frame/yuyv422.go +++ b/frame/yuyv422.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package frame import ( diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index c1161c0..d87965d 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -1,17 +1,3 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // snapshot extracts image frames from a video stream. package snapshot