From c67729332cbf1b2400b60fc3605560c1008f0d36 Mon Sep 17 00:00:00 2001 From: ston1th Date: Sun, 10 Apr 2016 11:06:26 +0200 Subject: [PATCH] initial commit --- Godeps/Godeps.json | 10 + Godeps/Readme | 5 + Godeps/_workspace/.gitignore | 2 + .../src/github.com/creack/goselect/.gitignore | 29 ++ .../src/github.com/creack/goselect/Dockerfile | 5 + .../src/github.com/creack/goselect/LICENSE | 22 + .../src/github.com/creack/goselect/README.md | 30 ++ .../creack/goselect/example/main.go | 369 +++++++++++++++ .../src/github.com/creack/goselect/fdset.go | 33 ++ .../github.com/creack/goselect/fdset_32.go | 10 + .../github.com/creack/goselect/fdset_64.go | 10 + .../github.com/creack/goselect/fdset_doc.go | 93 ++++ .../creack/goselect/fdset_freebsd.go | 33 ++ .../creack/goselect/fdset_unsupported.go | 20 + .../creack/goselect/fdset_windows.go | 57 +++ .../src/github.com/creack/goselect/select.go | 28 ++ .../creack/goselect/select_linux.go | 10 + .../creack/goselect/select_other.go | 9 + .../creack/goselect/select_unsupported.go | 16 + .../creack/goselect/select_windows.go | 13 + .../creack/goselect/zselect_windows.go | 41 ++ LICENSE | 24 + README.md | 5 + examples/main.go | 252 ++++++++++ webcam.go | 442 ++++++++++++++++++ webcam_test.go | 44 ++ 26 files changed, 1612 insertions(+) create mode 100644 Godeps/Godeps.json create mode 100644 Godeps/Readme create mode 100644 Godeps/_workspace/.gitignore create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/.gitignore create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/Dockerfile create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/LICENSE create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/README.md create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/example/main.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/select.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/select_linux.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/select_other.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/select_windows.go create mode 100644 Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/main.go create mode 100644 webcam.go create mode 100644 webcam_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..464d9c7 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,10 @@ +{ + "ImportPath": "git.giftfish.de/ston1th/webcam", + "GoVersion": "go1.6", + "Deps": [ + { + "ImportPath": "github.com/creack/goselect", + "Rev": "2b9baf3927bb887c7a904663aa1a1c2dcf02ca61" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/github.com/creack/goselect/.gitignore b/Godeps/_workspace/src/github.com/creack/goselect/.gitignore new file mode 100644 index 0000000..6f4dace --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +go-select* +goselect* +example-* +example/example diff --git a/Godeps/_workspace/src/github.com/creack/goselect/Dockerfile b/Godeps/_workspace/src/github.com/creack/goselect/Dockerfile new file mode 100644 index 0000000..d03b5a9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/Dockerfile @@ -0,0 +1,5 @@ +FROM google/golang:stable +MAINTAINER Guillaume J. Charmes +CMD /tmp/a.out +ADD . /src +RUN cd /src && go build -o /tmp/a.out diff --git a/Godeps/_workspace/src/github.com/creack/goselect/LICENSE b/Godeps/_workspace/src/github.com/creack/goselect/LICENSE new file mode 100644 index 0000000..95c600a --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Guillaume J. Charmes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Godeps/_workspace/src/github.com/creack/goselect/README.md b/Godeps/_workspace/src/github.com/creack/goselect/README.md new file mode 100644 index 0000000..10fb7f6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/README.md @@ -0,0 +1,30 @@ +# go-select + +select(2) implementation in Go + +## Supported platforms + +| | 386 | amd64 | arm | +|---------------|-----|-------|-----| +| **linux** | yes | yes | yes | +| **darwin** | yes | yes | n/a | +| **freebsd** | yes | yes | yes | +| **openbsd** | yes | yes | yes | +| **netbsd** | yes | yes | yes | +| **dragonfly** | n/a | yes | n/a | +| **solaris** | n/a | no | n/a | +| **plan9** | no | no | n/a | +| **windows** | yes | yes | n/a | +| **android** | n/a | n/a | no | + +*n/a: platform not supported by Go + +Go on `plan9` and `solaris` do not implement `syscall.Select` not `syscall.SYS_SELECT`. + +## Cross compile + +Using davecheney's https://github.com/davecheney/golang-crosscompile + +``` +export PLATFORMS="darwin/386 darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm windows/386 windows/amd64 openbsd/386 openbsd/amd64 netbsd/386 netbsd/amd64 dragonfly/amd64 plan9/386 plan9/amd64 solaris/amd64" +``` diff --git a/Godeps/_workspace/src/github.com/creack/goselect/example/main.go b/Godeps/_workspace/src/github.com/creack/goselect/example/main.go new file mode 100644 index 0000000..86d8d16 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/example/main.go @@ -0,0 +1,369 @@ +package main + +import ( + "fmt" + "io" + "log" + "net" + "os" + "sync" + "time" + + "github.com/creack/goselect" +) + +type fder interface { + Fd() uintptr +} + +// ErrNotFder . +var ErrNotFder = fmt.Errorf("Not a fder") + +type readFder interface { + io.Reader + fder +} + +type writeFder interface { + io.Reader + fder +} + +// SelectReader . +type selectReader struct { + readFder + ready chan struct{} + stop chan struct{} +} + +func (r *selectReader) Read(buf []byte) (int, error) { + select { + case <-r.stop: + return 0, io.EOF + case <-r.ready: + return r.readFder.Read(buf) + } +} + +// Select . +type Select struct { + readers []*selectReader + writers []writeFder + controlPipeR *os.File + controlPipeW *os.File +} + +// NewSelect . +func NewSelect() (*Select, error) { + r, w, err := os.Pipe() + if err != nil { + return nil, err + } + s := &Select{ + readers: []*selectReader{}, + writers: []writeFder{}, + controlPipeR: r, + controlPipeW: w, + } + return s, nil +} + +func (s *Select) run() { + rFDSet := &goselect.FDSet{} + var max uintptr + var fd uintptr + for { + // TODO: this can be cached and changed only upon controlePipe event. + rFDSet.Zero() + fd = s.controlPipeR.Fd() + max = fd + rFDSet.Set(fd) + for _, r := range s.readers { + fd = r.Fd() + if max < fd { + max = fd + } + rFDSet.Set(fd) + } + println("-------> preselect") + if err := goselect.Select(int(max)+1, rFDSet, nil, nil, -1); err != nil { + log.Fatal(err) + } + for i := uintptr(0); i < goselect.FD_SETSIZE; i++ { + if rFDSet.IsSet(i) { + println(i, "is ready") + } + } + println("<-------- postselect") + for _, r := range s.readers { + if rFDSet.IsSet(r.Fd()) { + func(r *selectReader) { r.ready <- struct{}{} }(r) + } + } + if rFDSet.IsSet(s.controlPipeR.Fd()) { + buf := make([]byte, 1024) + n, err := s.controlPipeR.Read(buf) + if err != nil { + if err == io.EOF { + break + } + log.Fatal(err) + } + fmt.Printf("----> control: %s\n", buf[:n]) + } + } +} + +// NewReader . +func (s *Select) NewReader(r io.Reader) (io.Reader, error) { + rr, ok := r.(readFder) + if !ok { + return nil, ErrNotFder + } + ret := &selectReader{ + readFder: rr, + ready: make(chan struct{}), + stop: make(chan struct{}), + } + s.readers = append(s.readers, ret) + if _, err := s.controlPipeW.Write([]byte("newreader")); err != nil { + return nil, err + } + return ret, nil +} + +func test6() error { + s, err := NewSelect() + if err != nil { + return err + } + _ = s + + fmt.Printf("%d\n", os.Getpid()) + const MAX = 4 + rs := []io.Reader{} + ws := []io.Writer{} + for i := 0; i < MAX; i++ { + r, w, _ := os.Pipe() + println(i, "--->", r.Fd(), w.Fd()) + rs = append(rs, r) + ws = append(ws, w) + } + + c := make(chan struct{}) + wg := sync.WaitGroup{} + for i := 0; i < MAX; i++ { + wg.Add(1) + go func() { + time.Sleep(2 * time.Second) + <-c + wg.Done() + }() + } + time.Sleep(4 * time.Second) + close(c) + wg.Wait() + for i := 0; i < MAX; i++ { + time.Sleep(5 * time.Second) + wg.Add(1) + go func(r io.Reader, i int) { + rr, _ := s.NewReader(r) + r = rr + buf := make([]byte, 1024) + for { + n, err := r.Read(buf) + if err != nil { + log.Printf("[%d] error read: %s\n", i, err) + break + } + _ = n + fmt.Printf("[%d] %s\n", i, buf[:n]) + } + wg.Done() + }(rs[i], i) + } + for i := 0; i < 1; i++ { + for i := 0; i < MAX; i++ { + fmt.Fprintf(ws[i], "{%d} hello", i) + } + time.Sleep(5 * time.Second) + } + wg.Wait() + return nil +} + +func test4() error { + rFDSet := &goselect.FDSet{} + buf := make([]byte, 1024) + for { + // TODO: this can be cached and changed only upon controlePipe event. + rFDSet.Zero() + rFDSet.Set(os.Stdin.Fd()) + if err := goselect.Select(int(os.Stdin.Fd())+1, rFDSet, nil, nil, -1); err != nil { + return err + } + for i := uintptr(0); i < goselect.FD_SETSIZE; i++ { + if rFDSet.IsSet(i) { + println(i, "is ready") + } + } + if rFDSet.IsSet(os.Stdin.Fd()) { + n, err := os.Stdin.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + fmt.Printf("---->: %s\n", buf[:n]) + } + } + return nil +} + +type Client struct { + f *os.File + queue [][]byte +} + +func (c *Client) Push(msg []byte) { + c.queue = append(c.queue, msg) + println(len(c.queue)) +} + +func (c *Client) Pop() []byte { + if len(c.queue) == 0 { + return nil + } + tmp := c.queue[0] + c.queue = c.queue[1:] + return tmp +} + +func testServer() error { + l, err := net.Listen("tcp", "0.0.0.0:8080") + if err != nil { + return err + } + ll, ok := l.(*net.TCPListener) + if !ok { + return fmt.Errorf("wrong listener type") + } + + llf, err := ll.File() + if err != nil { + return err + } + + rFDSet := &goselect.FDSet{} + wFDSet := &goselect.FDSet{} + + buf := make([]byte, 1024) + var max, fd uintptr + + clients := []*Client{} + + for { + // TODO: this can be cached and changed only upon controlePipe event. + rFDSet.Zero() + wFDSet.Zero() + fd = llf.Fd() + max = fd + rFDSet.Set(fd) + + for _, c := range clients { + fd = c.f.Fd() + rFDSet.Set(fd) + if len(c.queue) > 0 { + wFDSet.Set(fd) + } + if max < fd { + max = fd + } + } + + if err := goselect.Select(int(max)+1, rFDSet, wFDSet, nil, -1); err != nil { + return err + } + println("-->") + + // Watch for new clients + if rFDSet.IsSet(llf.Fd()) { + c, err := ll.AcceptTCP() + if err != nil { + return err + } + f, err := c.File() + if err != nil { + return err + } + fd = f.Fd() + println("New client:", fd) + clients = append(clients, &Client{f: f}) + } + + // Watch for client activity + for _, c := range clients { + fd = c.f.Fd() + if rFDSet.IsSet(fd) { + n, err := c.f.Read(buf) + if err != nil { + return err + } + fmt.Printf("%s", buf[:n]) + for _, cc := range clients { + if c != cc { + cc.Push(buf[:n]) + } + } + } + } + + // Send message to clients + for _, c := range clients { + fd = c.f.Fd() + if wFDSet.IsSet(fd) { + msg := c.Pop() + fmt.Printf("%d write ready: %v\n", fd, msg) + if msg != nil { + _, err := c.f.Write(msg) + if err != nil { + return err + } + } + } + } + } +} + +func main() { + if err := testServer(); err != nil { + log.Fatal(err) + } +} + +func test5() error { + wg := sync.WaitGroup{} + fmt.Printf("%d\n", os.Getpid()) + for i := 0; i < 200; i++ { + r, w, _ := os.Pipe() + go func(w io.Writer) { + for { + w.Write([]byte("hello")) + time.Sleep(time.Second) + } + }(w) + wg.Add(1) + go func(r io.Reader) { + buf := make([]byte, 1024) + for { + _, err := r.Read(buf) + if err != nil { + log.Fatal(err) + } + } + }(r) + } + wg.Wait() + return nil +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset.go new file mode 100644 index 0000000..2ff3f6c --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset.go @@ -0,0 +1,33 @@ +// +build !freebsd,!windows,!plan9 + +package goselect + +import "syscall" + +const FD_SETSIZE = syscall.FD_SETSIZE + +// FDSet wraps syscall.FdSet with convenience methods +type FDSet syscall.FdSet + +// Set adds the fd to the set +func (fds *FDSet) Set(fd uintptr) { + fds.Bits[fd/NFDBITS] |= (1 << (fd % NFDBITS)) +} + +// Clear remove the fd from the set +func (fds *FDSet) Clear(fd uintptr) { + fds.Bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS)) +} + +// IsSet check if the given fd is set +func (fds *FDSet) IsSet(fd uintptr) bool { + return fds.Bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0 +} + +// Keep a null set to avoid reinstatiation +var nullFdSet = &FDSet{} + +// Zero empties the Set +func (fds *FDSet) Zero() { + copy(fds.Bits[:], (nullFdSet).Bits[:]) +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go new file mode 100644 index 0000000..8b90d21 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go @@ -0,0 +1,10 @@ +// +build darwin openbsd netbsd 386 arm + +package goselect + +// darwin, netbsd and openbsd uses uint32 on both amd64 and 386 + +const ( + // NFDBITS is the amount of bits per mask + NFDBITS = 4 * 8 +) diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go new file mode 100644 index 0000000..4211c66 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go @@ -0,0 +1,10 @@ +// +build amd64,!darwin,!netbsd,!openbsd + +package goselect + +// darwin, netbsd and openbsd uses uint32 on both amd64 and 386 + +const ( + // NFDBITS is the amount of bits per mask + NFDBITS = 8 * 8 +) diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go new file mode 100644 index 0000000..d9f15a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go @@ -0,0 +1,93 @@ +package goselect + +/** +From: XCode's MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/select.h +-- +// darwin/amd64 / 386 +sizeof(__int32_t) == 4 +-- + +typedef __int32_t __fd_mask; + +#define FD_SETSIZE 1024 +#define __NFDBITS (sizeof(__fd_mask) * 8) +#define __howmany(x, y) ((((x) % (y)) == 0) ? ((x) / (y)) : (((x) / (y)) + 1)) + +typedef struct fd_set { + __fd_mask fds_bits[__howmany(__FD_SETSIZE, __NFDBITS)]; +} fd_set; + +#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS)) +#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n)) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n)) +#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0) +*/ + +/** +From: /usr/include/i386-linux-gnu/sys/select.h +-- +// linux/i686 +sizeof(long int) == 4 +-- + +typedef long int __fd_mask; + +#define FD_SETSIZE 1024 +#define __NFDBITS (sizeof(__fd_mask) * 8) + + +typedef struct fd_set { + __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; +} fd_set; + +#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS)) +#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n)) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n)) +#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0) +*/ + +/** +From: /usr/include/x86_64-linux-gnu/sys/select.h +-- +// linux/amd64 +sizeof(long int) == 8 +-- + +typedef long int __fd_mask; + +#define FD_SETSIZE 1024 +#define __NFDBITS (sizeof(__fd_mask) * 8) + + +typedef struct fd_set { + __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; +} fd_set; + +#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS)) +#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n)) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n)) +#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0) +*/ + +/** +From: /usr/include/sys/select.h +-- +// freebsd/amd64 +sizeof(unsigned long) == 8 +-- + +typedef unsigned long __fd_mask; + +#define FD_SETSIZE 1024U +#define __NFDBITS (sizeof(__fd_mask) * 8) +#define _howmany(x, y) (((x) + ((y) - 1)) / (y)) + +typedef struct fd_set { + __fd_mask fds_bits[_howmany(FD_SETSIZE, __NFDBITS)]; +} fd_set; + +#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS)) +#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n)) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n)) +#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0) +*/ diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go new file mode 100644 index 0000000..03f7b91 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go @@ -0,0 +1,33 @@ +// +build freebsd + +package goselect + +import "syscall" + +const FD_SETSIZE = syscall.FD_SETSIZE + +// FDSet wraps syscall.FdSet with convenience methods +type FDSet syscall.FdSet + +// Set adds the fd to the set +func (fds *FDSet) Set(fd uintptr) { + fds.X__fds_bits[fd/NFDBITS] |= (1 << (fd % NFDBITS)) +} + +// Clear remove the fd from the set +func (fds *FDSet) Clear(fd uintptr) { + fds.X__fds_bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS)) +} + +// IsSet check if the given fd is set +func (fds *FDSet) IsSet(fd uintptr) bool { + return fds.X__fds_bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0 +} + +// Keep a null set to avoid reinstatiation +var nullFdSet = &FDSet{} + +// Zero empties the Set +func (fds *FDSet) Zero() { + copy(fds.X__fds_bits[:], (nullFdSet).X__fds_bits[:]) +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go new file mode 100644 index 0000000..cbd8d58 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go @@ -0,0 +1,20 @@ +// +build plan9 + +package goselect + +const FD_SETSIZE = 0 + +// FDSet wraps syscall.FdSet with convenience methods +type FDSet struct{} + +// Set adds the fd to the set +func (fds *FDSet) Set(fd uintptr) {} + +// Clear remove the fd from the set +func (fds *FDSet) Clear(fd uintptr) {} + +// IsSet check if the given fd is set +func (fds *FDSet) IsSet(fd uintptr) bool { return false } + +// Zero empties the Set +func (fds *FDSet) Zero() {} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go b/Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go new file mode 100644 index 0000000..ee34919 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go @@ -0,0 +1,57 @@ +// +build windows + +package goselect + +import "syscall" + +const FD_SETSIZE = 64 + +// FDSet extracted from mingw libs source code +type FDSet struct { + fd_count uint + fd_array [FD_SETSIZE]uintptr +} + +// Set adds the fd to the set +func (fds *FDSet) Set(fd uintptr) { + var i uint + for i = 0; i < fds.fd_count; i++ { + if fds.fd_array[i] == fd { + break + } + } + if i == fds.fd_count { + if fds.fd_count < FD_SETSIZE { + fds.fd_array[i] = fd + fds.fd_count++ + } + } +} + +// Clear remove the fd from the set +func (fds *FDSet) Clear(fd uintptr) { + var i uint + for i = 0; i < fds.fd_count; i++ { + if fds.fd_array[i] == fd { + for i < fds.fd_count-1 { + fds.fd_array[i] = fds.fd_array[i+1] + i++ + } + fds.fd_count-- + break + } + } +} + +// IsSet check if the given fd is set +func (fds *FDSet) IsSet(fd uintptr) bool { + if isset, err := __WSAFDIsSet(syscall.Handle(fd), fds); err == nil && isset != 0 { + return true + } + return false +} + +// Zero empties the Set +func (fds *FDSet) Zero() { + fds.fd_count = 0 +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/select.go b/Godeps/_workspace/src/github.com/creack/goselect/select.go new file mode 100644 index 0000000..7f2875e --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/select.go @@ -0,0 +1,28 @@ +package goselect + +import ( + "syscall" + "time" +) + +// Select wraps syscall.Select with Go types +func Select(n int, r, w, e *FDSet, timeout time.Duration) error { + var timeval *syscall.Timeval + if timeout >= 0 { + t := syscall.NsecToTimeval(timeout.Nanoseconds()) + timeval = &t + } + + return sysSelect(n, r, w, e, timeval) +} + +// RetrySelect wraps syscall.Select with Go types, and retries a number of times, with a given retryDelay. +func RetrySelect(n int, r, w, e *FDSet, timeout time.Duration, retries int, retryDelay time.Duration) (err error) { + for i := 0; i < retries; i++ { + if err = Select(n, r, w, e, timeout); err != syscall.EINTR { + return err + } + time.Sleep(retryDelay) + } + return err +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/select_linux.go b/Godeps/_workspace/src/github.com/creack/goselect/select_linux.go new file mode 100644 index 0000000..acd569e --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/select_linux.go @@ -0,0 +1,10 @@ +// +build linux + +package goselect + +import "syscall" + +func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error { + _, err := syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout) + return err +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/select_other.go b/Godeps/_workspace/src/github.com/creack/goselect/select_other.go new file mode 100644 index 0000000..6c82081 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/select_other.go @@ -0,0 +1,9 @@ +// +build !linux,!windows,!plan9,!solaris + +package goselect + +import "syscall" + +func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error { + return syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout) +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go b/Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go new file mode 100644 index 0000000..bea0ad9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go @@ -0,0 +1,16 @@ +// +build plan9 solaris + +package goselect + +import ( + "fmt" + "runtime" + "syscall" +) + +// ErrUnsupported . +var ErrUnsupported = fmt.Errorf("Platofrm %s/%s unsupported", runtime.GOOS, runtime.GOARCH) + +func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error { + return ErrUnsupported +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/select_windows.go b/Godeps/_workspace/src/github.com/creack/goselect/select_windows.go new file mode 100644 index 0000000..0fb70d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/select_windows.go @@ -0,0 +1,13 @@ +// +build windows + +package goselect + +import "syscall" + +//sys _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) = ws2_32.select +//sys __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) = ws2_32.__WSAFDIsSet + +func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error { + _, err := _select(n, r, w, e, timeout) + return err +} diff --git a/Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go b/Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go new file mode 100644 index 0000000..c3b10e1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go @@ -0,0 +1,41 @@ +// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package goselect + +import "unsafe" +import "syscall" + +var _ unsafe.Pointer + +var ( + modws2_32 = syscall.NewLazyDLL("ws2_32.dll") + + procselect = modws2_32.NewProc("select") + proc__WSAFDIsSet = modws2_32.NewProc("__WSAFDIsSet") +) + +func _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) { + r0, _, e1 := syscall.Syscall6(procselect.Addr(), 5, uintptr(nfds), uintptr(unsafe.Pointer(readfds)), uintptr(unsafe.Pointer(writefds)), uintptr(unsafe.Pointer(exceptfds)), uintptr(unsafe.Pointer(timeout)), 0) + total = int(r0) + if total == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) { + r0, _, e1 := syscall.Syscall(proc__WSAFDIsSet.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(fdset)), 0) + isset = int(r0) + if isset == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..456dc37 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, ston1th +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of the authors and/or contributors may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..290d57a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Webcam library written in pure Go (no cgo bindings) + +This library is for interacting with webcams, using the video4linux (V4L2) framework. + +It is derived work from https://github.com/blackjack/webcam diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 0000000..bc38761 --- /dev/null +++ b/examples/main.go @@ -0,0 +1,252 @@ +package main + +import ( + //"bytes" + //"encoding/hex" + "fmt" + "image" + "os" + //"image/jpeg" + //"net/http" + "git.giftfish.de/ston1th/webcam" + "time" +) + +var frame []byte +var img image.Image + +func main() { + cam, err := webcam.Open("/dev/video0", time.Second*5) + if err != nil { + panic(err.Error()) + } + defer cam.Close() + + formats := cam.GetPixelFormats() + println("Available formats: ") + for k, value := range formats { + fmt.Fprintln(os.Stderr, k, value) + } + + //choice := readChoice(fmt.Sprintf("Choose format [1-%d]: ", len(formats))) + //format := formats[1] + format := uint32(1196444237) // MJPEG + //format := PixelFormat(1448695129) // YUV + + fmt.Fprintf(os.Stderr, "Supported frame sizes for format %s\n", formats[format]) + fs := cam.GetFrameSizes(format) + + for _, f := range fs { + fmt.Fprintln(os.Stderr, f) + } + //choice = readChoice(fmt.Sprintf("Choose format [1-%d]: ", len(frames))) + s := fs[5] + f, w, h, err := cam.SetImageFormat(format, s.MaxWidth, s.MaxHeight) + + if err != nil { + panic(err.Error()) + } else { + fmt.Fprintf(os.Stderr, "Resulting image format: %s (%dx%d)\n", formats[f], w, h) + } + + fmt.Fprintf(os.Stderr, "Press Enter to start streaming\n") + fmt.Scanf("\n") + err = cam.StartStreaming() + if err != nil { + panic(err.Error()) + } + + for { + err = cam.WaitForFrame() + switch err.(type) { + case nil: + case webcam.TimeoutErr: + continue + default: + panic(err.Error()) + } + frm, err := cam.ReadFrame() + if err == nil { + //print(".") + os.Stdout.Write(frm) + os.Stdout.Sync() + } + } +} + +/* + buf := bytes.NewBuffer(frm) + img, err = jpeg.Decode(buf) + if err != nil { + panic(err.Error()) + } + + http.HandleFunc("/", handler) + panic(http.ListenAndServe(":8090", nil)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + err := jpeg.Encode(w, img, nil) + println(err) +} + +/* + imgchan := make(chan *image.Image) + pr, pw := io.Pipe() + + go func() { + for { + frm, err := cam.ReadFrame() + if err == nil { + pw.Write(frm) + } + time.Sleep(time.Second) + } + }() + + go func() { + img, err := mjpeg.Decode(pr) + if err != nil { + fmt.Println(err) + } + }() + + dec <- imgchan + + http.HandleFunc("/", handler) + http.HandleFunc("/frame", handler2) + panic(http.ListenAndServe(":8090", nil)) +} + +func handler2(w http.ResponseWriter, r *http.Request) { + _, err := w.Write(frame) + println(err) +} + +func handler(w http.ResponseWriter, r *http.Request) { + err := jpeg.Encode(w, *dec, nil) + println(err) +}*/ + +/*package main + +import ( + "bytes" + fmt "fmt" + "image" + "image/jpeg" + "net/http" +) + +var frame []byte +var dec image.Image + +func main() { + cam, err := Open("/dev/video0") + if err != nil { + panic(err.Error()) + } + defer cam.Close() + + err = cam.StartStreaming() + if err != nil { + panic(err.Error()) + } + + for { + frame, err = cam.ReadFrame() + if err == nil { + break + } + } + + dec, err = jpeg.Decode(bytes.NewBuffer(frame)) + if err != nil { + fmt.Println(err) + } + + http.HandleFunc("/", handler) + panic(http.ListenAndServe(":8090", nil)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + err := jpeg.Encode(w, dec, nil) + println(err) +}*/ + +/*import ( + "fmt" + "image" + "image/color" + "image/jpeg" + "net/http" + "os" +) + +const X = 800 +const Y = 600 + +func div(f1, f2 float64) float64 { + return ((f1 - f2) / f1) * 100 +} + +func check(i1, i2 uint32) bool { + if i1 == i2 { + return false + } + if i1 == 0 { + return false + } + if i2 == 0 { + return false + } + f1 := float64(i1) + f2 := float64(i2) + if div(f1, f2) > 5.00 { + return true + } + return false +} + +func comp(p1, p2 image.Image) image.Image { + red := color.RGBA{255, 0, 0, 0} + n := image.NewRGBA(p1.Bounds()) + for i := 0; i < X; i++ { + for j := 0; j < Y; j++ { + r1, g1, b1, _ := p1.At(i, j).RGBA() + r2, g2, b2, _ := p2.At(i, j).RGBA() + if check(r1, r2) || check(g1, g2) || check(b1, b2) { + n.Set(i, j, red) + continue + } + n.Set(i, j, p1.At(i, j)) + } + } + return n +} + +var img image.Image +var test image.Image + +func main() { + f1, err := os.Open("pic1.jpeg") + fmt.Println(err) + f2, err := os.Open("pic2.jpeg") + fmt.Println(err) + p1, _, err := image.Decode(f1) + fmt.Println(err) + p2, _, err := image.Decode(f2) + fmt.Println(err) + img = comp(p1, p2) + test = p1 + http.HandleFunc("/", handler) + http.HandleFunc("/test", handler2) + panic(http.ListenAndServe(":8090", nil)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Println(jpeg.Encode(w, img, nil)) +} +func handler2(w http.ResponseWriter, r *http.Request) { + fmt.Println(jpeg.Encode(w, test, nil)) +}*/ diff --git a/webcam.go b/webcam.go new file mode 100644 index 0000000..913f170 --- /dev/null +++ b/webcam.go @@ -0,0 +1,442 @@ +package webcam + +import ( + "errors" + "fmt" + "os" + "sort" + "syscall" + "time" + "unsafe" + + "github.com/creack/goselect" +) + +type Webcam struct { + fd uintptr + file *os.File + path string + timeout time.Duration + buffers [][]byte +} + +func Open(path string, timeout time.Duration) (*Webcam, error) { + f, err := os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) + if err != nil { + return nil, err + } + + w := new(Webcam) + w.fd = f.Fd() + w.file = f + w.path = path + w.timeout = timeout + + v, s, err := w.checkCapabilities() + if err != nil { + return nil, err + } + if v { + return nil, errors.New("Not a video capture device") + } + if s { + return nil, errors.New("Device does not support the streaming I/O method") + } + return w, nil +} + +type requestbuffers struct { + count uint32 + t uint32 + memory uint32 + reserved [2]uint32 +} + +type buffer struct { + index uint32 + t uint32 + bytesused uint32 + flags uint32 + field uint32 + timestamp syscall.Timeval + timecode struct { + t uint32 + flags uint32 + frames uint8 + seconds uint8 + minutes uint8 + hours uint8 + userbits [4]uint8 + } + sequence uint32 + memory uint32 + /* + union { + __u32 offset; + unsigned long userptr; + struct v4l2_plane *planes; + __s32 fd; + } m; + */ + offset int64 // m + length uint32 + reserved2 uint32 + reserved uint32 +} + +type format_dummy struct { + t uint32 + fmt struct { + raw_data [201]byte + } +} + +type format struct { + t uint32 + pix pix + raw_data [155]byte // placeholder +} +type pix struct { + empty uint32 // placeholder + width uint32 + height uint32 + pixelformat uint32 + field uint32 + bytesperline uint32 + sizeimage uint32 + colorspace uint32 + priv uint32 + flags uint32 + ycbcr_enc uint32 + quantization uint32 + // commented out for 'empty' placeholder + //xfer_func uint32 +} + +type capability struct { + driver [16]byte + card [32]byte + bus_info [32]byte + version uint32 + capabilities uint32 + device_caps uint32 + reserved [3]uint32 +} + +type fmtdesc struct { + index uint32 + t uint32 + flags uint32 + description [32]byte + pixelformat uint32 + reserved [4]uint32 +} + +type frmsizeenum struct { + index uint32 + pixel_format uint32 + t uint32 + /* + struct v4l2_frmsize_stepwise { + __u32 min_width + __u32 max_width + __u32 step_width + __u32 min_height + __u32 max_height + __u32 step_height + }; + struct v4l2_frmsize_discrete { + __u32 width + __u32 height + }; + */ + frmsize [6]uint32 + reserved [2]uint32 +} + +const ( + _IOC_READ = 2 + _IOC_WRITE = 1 + + _IOC_NRBITS = 8 + _IOC_TYPEBITS = 8 + _IOC_SIZEBITS = 14 + _IOC_NRSHIFT = 0 + _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS + _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS + _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS + + _V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 + _V4L2_MEMORY_MMAP = 1 + _V4L2_FIELD_ANY = 0 + + _V4L2_CAP_STREAMING = 0x04000000 + _V4L2_CAP_VIDEO_CAPTURE = 0x00000001 + + _V4L2_FRMSIZE_TYPE_DISCRETE = 1 + _V4L2_FRMSIZE_TYPE_CONTINUOUS = 2 + _V4L2_FRMSIZE_TYPE_STEPWISE = 3 +) + +var ( + _VIDIOC_QUERYCAP = _IOR('V', 0, unsafe.Sizeof(capability{})) + _VIDIOC_ENUM_FMT = _IOWR('V', 2, unsafe.Sizeof(fmtdesc{})) + _VIDIOC_S_FMT = _IOWR('V', 5, unsafe.Sizeof(format{})) + _VIDIOC_REQBUFS = _IOWR('V', 8, unsafe.Sizeof(requestbuffers{})) + _VIDIOC_QUERYBUF = _IOWR('V', 9, unsafe.Sizeof(buffer{})) + _VIDIOC_QBUF = _IOWR('V', 15, unsafe.Sizeof(buffer{})) + _VIDIOC_DQBUF = _IOWR('V', 17, unsafe.Sizeof(buffer{})) + _VIDIOC_STREAMON = _IOW('V', 18, unsafe.Sizeof(int32(0))) + _VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, unsafe.Sizeof(frmsizeenum{})) +) + +func _IOC(dir, t, nr, size uintptr) uintptr { + return dir<<_IOC_DIRSHIFT | + t<<_IOC_TYPESHIFT | + nr<<_IOC_NRSHIFT | + size<<_IOC_SIZESHIFT +} + +func _IOWR(t rune, i, size uintptr) uintptr { + return _IOC(_IOC_READ|_IOC_WRITE, uintptr(t), i, size) +} + +func _IOW(t rune, i, size uintptr) uintptr { + return _IOC(_IOC_WRITE, uintptr(t), i, size) +} + +func _IOR(t rune, i, size uintptr) uintptr { + return _IOC(_IOC_READ, uintptr(t), i, size) +} + +func ioctl(fd, cmd, ptr uintptr) error { + _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) + if ep != 0 { + return syscall.Errno(ep) + } + return nil +} + +func (w *Webcam) checkCapabilities() (video, stream bool, err error) { + c := new(capability) + err = ioctl(w.fd, _VIDIOC_QUERYCAP, uintptr(unsafe.Pointer(c))) + video = (c.capabilities & _V4L2_CAP_VIDEO_CAPTURE) == 0 + stream = (c.capabilities & _V4L2_CAP_STREAMING) == 0 + return +} + +func (w *Webcam) GetPixelFormats() (m map[uint32]string) { + m = make(map[uint32]string) + for i := uint32(0); ; i++ { + vfd := fmtdesc{ + index: i, + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + } + err := ioctl(w.fd, _VIDIOC_ENUM_FMT, uintptr(unsafe.Pointer(&vfd))) + if err != nil { + return + } + var s string + for _, b := range vfd.description { + s = s + string(b) + } + m[vfd.pixelformat] = s + } + return +} + +type FrameSize struct { + MinWidth uint32 + MaxWidth uint32 + StepWidth uint32 + MinHeight uint32 + MaxHeight uint32 + StepHeight uint32 +} + +func (s FrameSize) String() string { + if s.StepWidth == 0 && s.StepHeight == 0 { + return fmt.Sprintf("%dx%d", s.MaxWidth, s.MaxHeight) + } else { + return fmt.Sprintf("[%d-%d;%d]x[%d-%d;%d]", s.MinWidth, s.MaxWidth, s.StepWidth, s.MinHeight, s.MaxHeight, s.StepHeight) + } +} + +type FrameSizes []FrameSize + +func (slice FrameSizes) Len() int { return len(slice) } +func (slice FrameSizes) Less(i, j int) bool { + ls := slice[i].MaxWidth * slice[i].MaxHeight + rs := slice[j].MaxWidth * slice[j].MaxHeight + return ls < rs +} +func (slice FrameSizes) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } + +func (w *Webcam) GetFrameSizes(code uint32) (fs FrameSizes) { + var s FrameSize + for i := uint32(0); ; i++ { + vfse := frmsizeenum{ + index: i, + pixel_format: code, + } + if err := ioctl(w.fd, _VIDIOC_ENUM_FRAMESIZES, uintptr(unsafe.Pointer(&vfse))); err != nil { + break + } + switch vfse.t { + case _V4L2_FRMSIZE_TYPE_DISCRETE: + s.MinWidth = vfse.frmsize[0] + s.MaxWidth = vfse.frmsize[0] + s.StepWidth = 0 + s.MinHeight = vfse.frmsize[1] + s.MaxHeight = vfse.frmsize[1] + s.StepHeight = 0 + case _V4L2_FRMSIZE_TYPE_CONTINUOUS, _V4L2_FRMSIZE_TYPE_STEPWISE: + s.MinWidth = vfse.frmsize[0] + s.MaxWidth = vfse.frmsize[1] + s.StepWidth = vfse.frmsize[2] + s.MinHeight = vfse.frmsize[3] + s.MaxHeight = vfse.frmsize[4] + s.StepHeight = vfse.frmsize[5] + } + fs = append(fs, s) + } + sort.Sort(fs) + return +} + +func (cam *Webcam) SetImageFormat(w, h, formatcode uint32) (uint32, uint32, uint32, error) { + f := format{ + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + pix: pix{ + width: w, + height: h, + pixelformat: formatcode, + field: _V4L2_FIELD_ANY, + }, + } + err := ioctl(cam.fd, _VIDIOC_S_FMT, uintptr(unsafe.Pointer(&f))) + return f.pix.pixelformat, f.pix.width, f.pix.height, err +} + +func (w *Webcam) mmapRequestBuffers(bufcount uint32) (int, error) { + req := requestbuffers{ + count: bufcount, + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: _V4L2_MEMORY_MMAP, + } + err := ioctl(w.fd, _VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&req))) + return int(req.count), err +} + +func (w *Webcam) mmapQueryBuffer(index uint32) ([]byte, error) { + buf := buffer{ + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: _V4L2_MEMORY_MMAP, + index: index, + } + err := ioctl(w.fd, _VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&buf))) + if err != nil { + return nil, err + } + return syscall.Mmap(int(w.fd), buf.offset, int(buf.length), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) +} + +func (w *Webcam) mmapDequeueBuffer() (uint32, uint32, error) { + buf := buffer{ + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: _V4L2_MEMORY_MMAP, + } + err := ioctl(w.fd, _VIDIOC_DQBUF, uintptr(unsafe.Pointer(&buf))) + return buf.index, buf.bytesused, err +} + +func (w *Webcam) mmapEnqueueBuffer(index uint32) error { + buf := buffer{ + t: _V4L2_BUF_TYPE_VIDEO_CAPTURE, + memory: _V4L2_MEMORY_MMAP, + index: index, + } + return ioctl(w.fd, _VIDIOC_QBUF, uintptr(unsafe.Pointer(&buf))) +} + +func (w *Webcam) mmapReleaseBuffers() error { + for _, buf := range w.buffers { + if err := syscall.Munmap(buf); err != nil { + return err + } + } + return nil +} + +func (w *Webcam) StartStreaming() error { + n, err := w.mmapRequestBuffers(uint32(256)) + if err != nil { + return err + } + + w.buffers = make([][]byte, n) + for i := range w.buffers { + buf, err := w.mmapQueryBuffer(uint32(i)) + if err != nil { + return err + } + w.buffers[i] = buf + err = w.mmapEnqueueBuffer(uint32(i)) + if err != nil { + return err + } + } + + t := _V4L2_BUF_TYPE_VIDEO_CAPTURE + return ioctl(w.fd, _VIDIOC_STREAMON, uintptr(unsafe.Pointer(&t))) +} + +type TimeoutErr string + +func (t TimeoutErr) Error() string { + return string(t) +} + +func (w *Webcam) WaitForFrame() (err error) { + fds := new(goselect.FDSet) + for { + fds.Zero() + fds.Set(w.fd) + err = goselect.Select(int(w.fd)+1, fds, nil, nil, w.timeout) + if err == nil { + return + } + e, ok := err.(syscall.Errno) + if ok { + if e.Timeout() { + return TimeoutErr("timeout") + } + if e.Temporary() { + continue + } + } + } +} + +func (w *Webcam) ReadFrame() ([]byte, error) { + i, l, err := w.mmapDequeueBuffer() + if err != nil { + return nil, err + } + + buf := make([]byte, l) + copy(buf, w.buffers[int(i)]) + + err = w.mmapEnqueueBuffer(i) + if err != nil { + return nil, err + } + return buf, nil +} + +func (w *Webcam) Close() error { + if err := w.mmapReleaseBuffers(); err != nil { + return err + } + return w.file.Close() +} diff --git a/webcam_test.go b/webcam_test.go new file mode 100644 index 0000000..9a2c6fb --- /dev/null +++ b/webcam_test.go @@ -0,0 +1,44 @@ +package webcam + +import "testing" + +func TestConstants(t *testing.T) { + const ( + __VIDIOC_QUERYCAP = 2154321408 + __VIDIOC_ENUM_FMT = 3225441794 + __VIDIOC_S_FMT = 3234878981 + __VIDIOC_REQBUFS = 3222558216 + __VIDIOC_QUERYBUF = 3227014665 + __VIDIOC_QBUF = 3227014671 + __VIDIOC_DQBUF = 3227014673 + __VIDIOC_STREAMON = 1074026002 + __VIDIOC_ENUM_FRAMESIZES = 3224131146 + ) + if __VIDIOC_QUERYCAP != _VIDIOC_QUERYCAP { + t.Error("expected _VIDIOC_QUERYCAP", __VIDIOC_QUERYCAP, "got", _VIDIOC_QUERYCAP) + } + if __VIDIOC_ENUM_FMT != _VIDIOC_ENUM_FMT { + t.Error("expected _VIDIOC_ENUM_FMT", __VIDIOC_ENUM_FMT, "got", _VIDIOC_ENUM_FMT) + } + if __VIDIOC_S_FMT != _VIDIOC_S_FMT { + t.Error("expected _VIDIOC_S_FMT", __VIDIOC_S_FMT, "got", _VIDIOC_S_FMT) + } + if __VIDIOC_REQBUFS != _VIDIOC_REQBUFS { + t.Error("expected _VIDIOC_REQBUFS", __VIDIOC_REQBUFS, "got", _VIDIOC_REQBUFS) + } + if __VIDIOC_QUERYBUF != _VIDIOC_QUERYBUF { + t.Error("expected _VIDIOC_QUERYBUF", __VIDIOC_QUERYBUF, "got", _VIDIOC_QUERYBUF) + } + if __VIDIOC_QBUF != _VIDIOC_QBUF { + t.Error("expected _VIDIOC_QBUF", __VIDIOC_QBUF, "got", _VIDIOC_QBUF) + } + if __VIDIOC_DQBUF != _VIDIOC_DQBUF { + t.Error("expected _VIDIOC_DQBUF", __VIDIOC_DQBUF, "got", _VIDIOC_DQBUF) + } + if __VIDIOC_STREAMON != _VIDIOC_STREAMON { + t.Error("expected _VIDIOC_STREAMON", __VIDIOC_STREAMON, "got", _VIDIOC_STREAMON) + } + if __VIDIOC_ENUM_FRAMESIZES != _VIDIOC_ENUM_FRAMESIZES { + t.Error("expected _VIDIOC_ENUM_FRAMESIZES", __VIDIOC_ENUM_FRAMESIZES, "got", _VIDIOC_ENUM_FRAMESIZES) + } +}