initial commit
This commit is contained in:
commit
c67729332c
26 changed files with 1612 additions and 0 deletions
10
Godeps/Godeps.json
generated
Normal file
10
Godeps/Godeps.json
generated
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"ImportPath": "git.giftfish.de/ston1th/webcam",
|
||||
"GoVersion": "go1.6",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/creack/goselect",
|
||||
"Rev": "2b9baf3927bb887c7a904663aa1a1c2dcf02ca61"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
|
|
@ -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.
|
||||
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/pkg
|
||||
/bin
|
||||
29
Godeps/_workspace/src/github.com/creack/goselect/.gitignore
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/creack/goselect/.gitignore
generated
vendored
Normal file
|
|
@ -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
|
||||
5
Godeps/_workspace/src/github.com/creack/goselect/Dockerfile
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/creack/goselect/Dockerfile
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM google/golang:stable
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@charmes.net>
|
||||
CMD /tmp/a.out
|
||||
ADD . /src
|
||||
RUN cd /src && go build -o /tmp/a.out
|
||||
22
Godeps/_workspace/src/github.com/creack/goselect/LICENSE
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/creack/goselect/LICENSE
generated
vendored
Normal file
|
|
@ -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.
|
||||
|
||||
30
Godeps/_workspace/src/github.com/creack/goselect/README.md
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/creack/goselect/README.md
generated
vendored
Normal file
|
|
@ -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"
|
||||
```
|
||||
369
Godeps/_workspace/src/github.com/creack/goselect/example/main.go
generated
vendored
Normal file
369
Godeps/_workspace/src/github.com/creack/goselect/example/main.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
33
Godeps/_workspace/src/github.com/creack/goselect/fdset.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/creack/goselect/fdset.go
generated
vendored
Normal file
|
|
@ -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[:])
|
||||
}
|
||||
10
Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/creack/goselect/fdset_32.go
generated
vendored
Normal file
|
|
@ -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
|
||||
)
|
||||
10
Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/creack/goselect/fdset_64.go
generated
vendored
Normal file
|
|
@ -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
|
||||
)
|
||||
93
Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go
generated
vendored
Normal file
93
Godeps/_workspace/src/github.com/creack/goselect/fdset_doc.go
generated
vendored
Normal file
|
|
@ -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)
|
||||
*/
|
||||
33
Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/creack/goselect/fdset_freebsd.go
generated
vendored
Normal file
|
|
@ -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[:])
|
||||
}
|
||||
20
Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/creack/goselect/fdset_unsupported.go
generated
vendored
Normal file
|
|
@ -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() {}
|
||||
57
Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/creack/goselect/fdset_windows.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
28
Godeps/_workspace/src/github.com/creack/goselect/select.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/creack/goselect/select.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
10
Godeps/_workspace/src/github.com/creack/goselect/select_linux.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/creack/goselect/select_linux.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
9
Godeps/_workspace/src/github.com/creack/goselect/select_other.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/creack/goselect/select_other.go
generated
vendored
Normal file
|
|
@ -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)
|
||||
}
|
||||
16
Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/creack/goselect/select_unsupported.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
13
Godeps/_workspace/src/github.com/creack/goselect/select_windows.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/creack/goselect/select_windows.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
41
Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go
generated
vendored
Normal file
41
Godeps/_workspace/src/github.com/creack/goselect/zselect_windows.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
24
LICENSE
Normal file
24
LICENSE
Normal file
|
|
@ -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.
|
||||
5
README.md
Normal file
5
README.md
Normal file
|
|
@ -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
|
||||
252
examples/main.go
Normal file
252
examples/main.go
Normal file
|
|
@ -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))
|
||||
}*/
|
||||
442
webcam.go
Normal file
442
webcam.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
44
webcam_test.go
Normal file
44
webcam_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue