initial commit

This commit is contained in:
ston1th 2016-04-10 11:06:26 +02:00
commit c67729332c
26 changed files with 1612 additions and 0 deletions

10
Godeps/Godeps.json generated Normal file
View 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
View 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
View file

@ -0,0 +1,2 @@
/pkg
/bin

View 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

View 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

View 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.

View 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"
```

View 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
}

View 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[:])
}

View 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
)

View 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
)

View 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)
*/

View 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[:])
}

View 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() {}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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)
}
}