Skip to content

Commit

Permalink
Code and example updates (new examples, new features, ect)
Browse files Browse the repository at this point in the history
This patch adds many updtes to the code and examples, including

- New updates to the webcam example GUI
- Streaming loop code update to copy frames to corruption by device during capture
- Update webcam to include face detection feature
- New examples including snapshot and simplecam
- Updates to the example documentation
- And much more
  • Loading branch information
vladimirvivien committed Oct 2, 2022
1 parent b1aac42 commit a93d5b2
Show file tree
Hide file tree
Showing 17 changed files with 553 additions and 92 deletions.
21 changes: 14 additions & 7 deletions device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,7 @@ func (d *Device) Start(ctx context.Context) error {
if err != nil {
return fmt.Errorf("device: requested buffer type not be supported: %w", err)
}
if bufReq.Count < 2 {
return fmt.Errorf("device: %s: issuficient buffer memory", d.path)
}

d.config.bufSize = bufReq.Count
d.requestedBuf = bufReq

Expand Down Expand Up @@ -395,10 +393,10 @@ func (d *Device) startStreamLoop(ctx context.Context) error {
defer close(d.output)

fd := d.Fd()
var frame []byte
ioMemType := d.MemIOType()
bufType := d.BufferType()
waitForRead := v4l2.WaitForRead(d)

for {
select {
// handle stream capture (read from driver)
Expand All @@ -411,12 +409,21 @@ func (d *Device) startStreamLoop(ctx context.Context) error {
panic(fmt.Sprintf("device: stream loop dequeue: %s", err))
}

// copy mapped buffer (copying avoids polluted data from subsequent dequeue ops)
if buff.Flags&v4l2.BufFlagMapped != 0 && buff.Flags&v4l2.BufFlagError == 0 {
frame = make([]byte, buff.BytesUsed)
if n := copy(frame, d.buffers[buff.Index][:buff.BytesUsed]); n == 0 {
d.output <- []byte{}
}
d.output <- frame
frame = nil
} else {
d.output <- []byte{}
}

if _, err := v4l2.QueueBuffer(fd, ioMemType, bufType, buff.Index); err != nil {
panic(fmt.Sprintf("device: stream loop queue: %s: buff: %#v", err, buff))
}

d.output <- d.Buffers()[buff.Index][:buff.BytesUsed]

case <-ctx.Done():
d.Stop()
return
Expand Down
4 changes: 3 additions & 1 deletion examples/capture0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ func main() {
// open device
device, err := device.Open(
devName,
device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMPEG, Width: 640, Height: 480}),
device.WithPixFormat(
v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMPEG, Width: 640, Height: 480},
),
)
...
}
Expand Down
4 changes: 3 additions & 1 deletion examples/capture0/capture0.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"os"
"time"

"github.com/vladimirvivien/go4vl/device"
"github.com/vladimirvivien/go4vl/v4l2"
Expand All @@ -19,7 +20,7 @@ func main() {
// open device
device, err := device.Open(
devName,
device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMJPEG, Width: 640, Height: 480, Field: v4l2.FieldInterlaced}),
device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMJPEG, Width: 640, Height: 480}),
)
if err != nil {
log.Fatalf("failed to open device: %s", err)
Expand Down Expand Up @@ -56,6 +57,7 @@ func main() {
if count >= totalFrames {
break
}
time.Sleep(1 * time.Second)
}

stop() // stop capture
Expand Down
49 changes: 41 additions & 8 deletions examples/capture1/capture1.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package main

import (
"bytes"
"context"
"flag"
"fmt"
"image"
"image/jpeg"
"log"
"os"

Expand All @@ -13,7 +16,13 @@ import (

func main() {
devName := "/dev/video0"
totalFrames := 3
width := 640
height := 480
flag.StringVar(&devName, "d", devName, "device name (path)")
flag.IntVar(&totalFrames, "c", totalFrames, "number of frames to caputure")
flag.IntVar(&width, "w", width, "picture width")
flag.IntVar(&height, "h", height, "picture height")
flag.Parse()

// open device
Expand Down Expand Up @@ -59,6 +68,7 @@ func main() {
log.Fatalf("device does not support any of %#v", preferredFmts)
}
log.Printf("Found preferred fmt: %s", fmtDesc)

frameSizes, err := v4l2.GetFormatFrameSizes(device.Fd(), fmtDesc.PixelFormat)
if err != nil {
log.Fatalf("failed to get framesize info: %s", err)
Expand All @@ -67,16 +77,18 @@ func main() {
// select size 640x480 for format
var frmSize v4l2.FrameSizeEnum
for _, size := range frameSizes {
if size.Size.MinWidth == 640 && size.Size.MinHeight == 480 {
if size.Size.MinWidth == uint32(width) && size.Size.MinHeight == uint32(height) {
frmSize = size
break
}
}

if frmSize.Size.MinWidth == 0 {
log.Fatalf("Size 640x480 not supported for fmt: %s", fmtDesc)
log.Fatalf("Size %dx%d not supported for fmt: %s", width, height, fmtDesc)
}

log.Printf("Found preferred size: %#v", frmSize)

// configure device with preferred fmt

if err := device.SetPixFormat(v4l2.PixFormat{
Expand All @@ -101,28 +113,49 @@ func main() {
}

// process frames from capture channel
totalFrames := 10
count := 0
log.Printf("Capturing %d frames at %d fps...", totalFrames, fps)
log.Printf("Capturing %d frames (buffers: %d, %d fps)...", totalFrames, device.BufferCount(), fps)
for frame := range device.GetOutput() {
if count >= totalFrames {
break
}
count++

if len(frame) == 0 {
log.Println("received frame size 0")
continue
}

log.Printf("captured %d bytes", len(frame))
img, fmtName, err := image.Decode(bytes.NewReader(frame))
if err != nil {
log.Printf("failed to decode jpeg: %s", err)
continue
}
log.Printf("decoded image format: %s", fmtName)

var imgBuf bytes.Buffer
if err := jpeg.Encode(&imgBuf, img, nil); err != nil {
log.Printf("failed to encode jpeg: %s", err)
continue
}

fileName := fmt.Sprintf("capture_%d.jpg", count)
file, err := os.Create(fileName)
if err != nil {
log.Printf("failed to create file %s: %s", fileName, err)
continue
}

if _, err := file.Write(frame); err != nil {
log.Printf("failed to write file %s: %s", fileName, err)
file.Close()
continue
}
log.Printf("Saved file: %s", fileName)
if err := file.Close(); err != nil {
log.Printf("failed to close file %s: %s", fileName, err)
}
count++
if count >= totalFrames {
break
}
}

cancel() // stop capture
Expand Down
2 changes: 1 addition & 1 deletion examples/ccapture/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# V4L2 video capture example in C

This an example in C showing a minimally required steps to capture video using V4L2. This is can be used to run tests on devices and compare results with the Go4VL code.
This an example in C showing the minimally required steps to capture video using the V4L2 framework. This can be used as a test tool to compare results between C and the Go4VL Go code.

## Build and run
On a Linux machine, run the following:
Expand Down
9 changes: 9 additions & 0 deletions examples/fileserv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# fileserv

A simple file server that can be used to view generated images in a remote environment.

## Run

```
go run fileserv.go ":port"
```
24 changes: 24 additions & 0 deletions examples/fileserv/fileserv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"log"
"net/http"
"os"
)

var (
port = ":5050"
)

func main() {
if len(os.Args) > 2 {
port = os.Args[1]
}

// serve examples dir
log.Printf("serving files on port %s", port)
http.Handle("/", http.FileServer(http.Dir("../")))
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}
4 changes: 4 additions & 0 deletions examples/simplecam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# camserv

This is a simple example shows how easy it is to use go4vl to
create a simple web application to stream camera images.
64 changes: 64 additions & 0 deletions examples/simplecam/simplecam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"mime/multipart"
"net/http"
"net/textproto"

"github.com/vladimirvivien/go4vl/device"
"github.com/vladimirvivien/go4vl/v4l2"
)

var (
frames <-chan []byte
)

func imageServ(w http.ResponseWriter, req *http.Request) {
mimeWriter := multipart.NewWriter(w)
w.Header().Set("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=%s", mimeWriter.Boundary()))
partHeader := make(textproto.MIMEHeader)
partHeader.Add("Content-Type", "image/jpeg")

var frame []byte
for frame = range frames {
partWriter, err := mimeWriter.CreatePart(partHeader)
if err != nil {
log.Printf("failed to create multi-part writer: %s", err)
return
}

if _, err := partWriter.Write(frame); err != nil {
log.Printf("failed to write image: %s", err)
}
}
}

func main() {
port := ":9090"
devName := "/dev/video0"
flag.StringVar(&devName, "d", devName, "device name (path)")
flag.StringVar(&port, "p", port, "webcam service port")

camera, err := device.Open(
devName,
device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMJPEG, Width: 640, Height: 480}),
)
if err != nil {
log.Fatalf("failed to open device: %s", err)
}
defer camera.Close()

if err := camera.Start(context.TODO()); err != nil {
log.Fatalf("camera start: %s", err)
}

frames = camera.GetOutput()

log.Printf("Serving images: [%s/stream]", port)
http.HandleFunc("/stream", imageServ)
log.Fatal(http.ListenAndServe(port, nil))
}
33 changes: 33 additions & 0 deletions examples/snapshot/snap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"context"
"log"
"os"

"github.com/vladimirvivien/go4vl/device"
)

func main() {
dev, err := device.Open("/dev/video0", device.WithBufferSize(1))
if err != nil {
log.Fatal(err)
}
defer dev.Close()

if err := dev.Start(context.TODO()); err != nil {
log.Fatal(err)
}

frame := <-dev.GetOutput()

file, err := os.Create("pic.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()

if _, err := file.Write(frame); err != nil {
log.Fatal(err)
}
}
9 changes: 9 additions & 0 deletions examples/webcam/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/bash
# Run the following once to pull correct dependencies
go get github.com/vladimirvivien/go4vl@latest
go get github.com/esimov/pigo/core@latest
go get github.com/fogleman/gg@8febc0f526adecda6f8ae80f3869b7cd77e52984

go mod tidy

go build .
Binary file added examples/webcam/facefinder.model
Binary file not shown.
14 changes: 14 additions & 0 deletions examples/webcam/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/vladimirvivien/go4vl/exampels/webcam

go 1.19

require (
github.com/esimov/pigo v1.4.5
github.com/fogleman/gg v1.3.1-0.20210928143535-8febc0f526ad
)

require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)
Loading

0 comments on commit a93d5b2

Please sign in to comment.