Skip to content

Commit

Permalink
Merge pull request #10 from vladimirvivien/cgo-refactor
Browse files Browse the repository at this point in the history
Refactor project to use cgo-generated types and values
  • Loading branch information
vladimirvivien committed Nov 14, 2021
2 parents eab8daf + 647b814 commit 1a3878d
Show file tree
Hide file tree
Showing 21 changed files with 651 additions and 734 deletions.
118 changes: 113 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,133 @@
# go4vl
A Go library for the Video for Linux user API (V4L2).
A Go library for the `Video for Linux 2` (v4l2) user API.

----

`go4vl` hides all the complexities of working with V4L2 and
provides idiomatic Go types, like channels, to consume and process captured video frames.
The `go4vl` project is for working with the Video for Linux 2 API for real-time video.
It hides all the complexities of working with V4L2 and provides idiomatic Go types, like channels, to consume and process captured video frames.

> This project is designed to work with Linux and the Linux Video API.
> It is *NOT* meant to be a portable/cross-platform capable package for real-time video processing.
## Features
* Capture and control video data from your Go programs
* Idiomatic Go API for device access and video capture
* Use cgo-generated types for correct data representation in Go
* Use familiar types such as channels to stream video data
* Exposes device enumeration and information
* Provides device capture control
* Access to video format information
* Streaming support using memory map (other methods coming soon)
* Streaming support using memory map (other methods coming later)

### Not working/supported yet
* Inherent support for video output
* Only support MMap memory stream (user pointers, DMA not working)
* Device control not implemented yet

## Prerequisites
* Go compiler/tools
* Linux OS (32- or 64-bit)
* Kernel minimum v5.10.x
* A locally configured C compiler (i.e. gcc)
* Header files for V4L2 (i.e. /usr/include/linux/videodev2.h)

All examples have been tested using a Rasperry PI 3, running 32-bit Raspberry PI OS.
The package should work with no problem on your 64-bit Linux OS.

## Getting started
To avoid issues with old header files on your machine, upgrade your system to pull down the latest OS packages
with something similar to the following (follow directions for your system to properly upgrade):

```shell
sudo apt update
sudo apt full-upgrade
```

To include `go4vl` in your own code, pull the package

```bash
go get github.com/vladimirvivien/go4vl/v4l2
```

## Example
## Examples
The following is a simple example that captures video data from an attached camera device to
and saves them as JPEG files. The example assumes the attached device supports JPEG (MJPEG) output format inherently.

```go
package main

import (
...
"github.com/vladimirvivien/go4vl/v4l2"
)

func main() {
// open device
device, err := v4l2.Open("/dev/video0")
if err != nil {
log.Fatalf("failed to open device: %s", err)
}
defer device.Close()

// configure device with preferred fmt
if err := device.SetPixFormat(v4l2.PixFormat{
Width: 640,
Height: 480,
PixelFormat: v4l2.PixelFmtMJPEG,
Field: v4l2.FieldNone,
}); err != nil {
log.Fatalf("failed to set format: %s", err)
}

// start a device stream with 3 video buffers
if err := device.StartStream(3); err != nil {
log.Fatalf("failed to start stream: %s", err)
}

ctx, cancel := context.WithCancel(context.TODO())
// capture video data at 15 fps
frameChan, err := device.Capture(ctx, 15)
if err != nil {
log.Fatal(err)
}

// grab 10 frames from frame channel and save them as files
totalFrames := 10
count := 0
for frame := range frameChan {
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)
continue
}
if err := file.Close(); err != nil {
log.Printf("failed to close file %s: %s", fileName, err)
}
count++
if count >= totalFrames {
break
}
}

cancel() // stop capture
if err := device.StopStream(); err != nil {
log.Fatal(err)
}
fmt.Println("Done.")
}
```

### Other examples
The [./examples](./examples) directory contains additional examples including:

* [device_info](./examples/device_info) - queries and prints devince information
* [webcam](./examples/webcam) - uses the v4l2 package to create a simple webcam that streams images from an attached camera accessible via a web page.

## Roadmap
There is no defined roadmap. The main goal is to port as much functionlities as possible so that
adopters can use Go to create cool video-based tools on platforms such as the Raspberry Pi.
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ A general list (of no implied order) of high level tasks for the project.
* [ ] Support for YUYV conversion
* [ ] Set and query device controls
* [ ] Support for User pointer and other stream mode
* [x] Use cgo-generated Go types to avoid alignment bugs
41 changes: 32 additions & 9 deletions examples/capture/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ func main() {
defer device.Close()

// helper function to search for format descriptions
findPreferredFmt := func(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCEncoding) *v4l2.FormatDescription {
findPreferredFmt := func(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCType) *v4l2.FormatDescription {
for _, desc := range fmts {
if desc.GetPixelFormat() == pixEncoding{
if desc.PixelFormat == pixEncoding{
return &desc
}
}
Expand All @@ -39,7 +39,7 @@ func main() {
}

// search for preferred formats
preferredFmts := []v4l2.FourCCEncoding{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV}
preferredFmts := []v4l2.FourCCType{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV}
var fmtDesc *v4l2.FormatDescription
for _, preferredFmt := range preferredFmts{
fmtDesc = findPreferredFmt(fmtDescs, preferredFmt)
Expand All @@ -52,24 +52,45 @@ func main() {
if fmtDesc == nil {
log.Fatalf("device does not support any of %#v", preferredFmts)
}

frameSize, err := fmtDesc.GetFrameSize()
log.Printf("Found preferred fmt: %s", fmtDesc)
frameSizes, err := v4l2.GetFormatFrameSizes(device.GetFileDescriptor(), fmtDesc.PixelFormat)
if err!=nil{
log.Fatalf("failed to get framesize info: %s", err)
}

// select size 640x480 for format
var frmSize v4l2.FrameSize
for _, size := range frameSizes {
if size.Width == 640 && size.Height == 480 {
frmSize = size
break
}
}

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

// configure device with preferred fmt

if err := device.SetPixFormat(v4l2.PixFormat{
Width: frameSize.Width,
Height: frameSize.Height,
PixelFormat: fmtDesc.GetPixelFormat(),
Width: frmSize.Width,
Height: frmSize.Height,
PixelFormat: fmtDesc.PixelFormat,
Field: v4l2.FieldNone,
}); err != nil {
log.Fatalf("failed to set format: %s", err)
}

pixFmt, err := device.GetPixFormat()
if err != nil {
log.Fatalf("failed to get format: %s", err)
}
log.Printf("Pixel format set to [%s]", pixFmt)

// start stream
if err := device.StartStream(10); err != nil {
log.Println("Start capturing...")
if err := device.StartStream(3); err != nil {
log.Fatalf("failed to start stream: %s", err)
}

Expand All @@ -82,6 +103,7 @@ func main() {
// process frames from capture channel
totalFrames := 10
count := 0
log.Println("Streaming frames from device...")
for frame := range frameChan {
fileName := fmt.Sprintf("capture_%d.jpg", count)
file, err := os.Create(fileName)
Expand All @@ -93,6 +115,7 @@ func main() {
log.Printf("failed to write file %s: %s", fileName, err)
continue
}
log.Printf("Saved file: %s", fileName)
if err := file.Close(); err != nil {
log.Printf("failed to close file %s: %s", fileName, err)
}
Expand Down
17 changes: 17 additions & 0 deletions examples/cgo_types/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Example: capture with cgo-generated types
:warning:

This example illustrates how to use ioctl directly
to communicate to device using cgo-generated types.

## Do not use it ##

Use package `v4l2` to do realtime image capture, as shown in example
[examples/capture](../capture).

:warning:

The example in this directory shows most of the moving pieces that make
the V4L2 API works using Go. It illustrates the steps, in detail, that
are required to communicate with a device driver to configure, initiate,
and capture images without using the Go v4l2 device type provided.
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,6 @@ import (
sys "golang.org/x/sys/unix"
)

// ========================= V4L2 command encoding =====================
// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/asm-generic/ioctl.h

const (
//ioctl command layout
iocNone = 0 // no op
iocWrite = 1 // userland app is writing, kernel reading
iocRead = 2 // userland app is reading, kernel writing

iocTypeBits = 8
iocNumberBits = 8
iocSizeBits = 14
iocOpBits = 2

numberPos = 0
typePos = numberPos + iocNumberBits
sizePos = typePos + iocTypeBits
opPos = sizePos + iocSizeBits
)

// ioctl command encoding funcs
func ioEnc(iocMode, iocType, number, size uintptr) uintptr {
return (iocMode << opPos) |
(iocType << typePos) |
(number << numberPos) |
(size << sizePos)
}

func ioEncR(iocType, number, size uintptr) uintptr {
return ioEnc(iocRead, iocType, number, size)
}

func ioEncW(iocType, number, size uintptr) uintptr {
return ioEnc(iocWrite, iocType, number, size)
}

func ioEncRW(iocType, number, size uintptr) uintptr {
return ioEnc(iocRead|iocWrite, iocType, number, size)
}

// four character pixel format encoding
func fourcc(a, b, c, d uint32) uint32 {
return (a | b<<8) | c<<16 | d<<24
}

// wrapper for ioctl system call
func ioctl(fd, req, arg uintptr) (err error) {
if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 {
Expand Down Expand Up @@ -115,9 +70,6 @@ func setFormat(fd uintptr, pixFmt PixFormat) error {
v4l2Fmt._type = C.uint(BufTypeVideoCapture)
*(*C.struct_v4l2_pix_format)(unsafe.Pointer(&v4l2Fmt.fmt[0])) = *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&pixFmt))

// encode command to send
// vidiocSetFormat := ioEncRW('V', 5, uintptr(unsafe.Sizeof(v4l2Fmt)))

// send command
if err := ioctl(fd, C.VIDIOC_S_FMT, uintptr(unsafe.Pointer(&v4l2Fmt))); err != nil {
return err
Expand Down
19 changes: 8 additions & 11 deletions examples/device_info/devinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,18 @@ func printDeviceDriverInfo(dev *v4l2.Device) error {

// print driver info
fmt.Println("Device Info:")
fmt.Printf(template, "Driver name", caps.DriverName())
fmt.Printf(template, "Card name", caps.CardName())
fmt.Printf(template, "Bus info", caps.BusInfo())
fmt.Printf(template, "Driver name", caps.Driver)
fmt.Printf(template, "Card name", caps.Card)
fmt.Printf(template, "Bus info", caps.BusInfo)

verVal := caps.GetVersion()
version := fmt.Sprintf("%d.%d.%d", verVal>>16, (verVal>>8)&0xff, verVal&0xff)
fmt.Printf(template, "Driver version", version)
fmt.Printf(template, "Driver version", caps.GetVersionInfo())

fmt.Printf("\t%-16s : %0x\n", "Driver capabilities", caps.GetCapabilities())
fmt.Printf("\t%-16s : %0x\n", "Driver capabilities", caps.Capabilities)
for _, desc := range caps.GetDriverCapDescriptions() {
fmt.Printf("\t\t%s\n", desc.Desc)
}

fmt.Printf("\t%-16s : %0x\n", "Device capabilities", caps.GetCapabilities())
fmt.Printf("\t%-16s : %0x\n", "Device capabilities", caps.Capabilities)
for _, desc := range caps.GetDeviceCapDescriptions() {
fmt.Printf("\t\t%s\n", desc.Desc)
}
Expand Down Expand Up @@ -141,7 +139,7 @@ func printFormatDesc(dev *v4l2.Device) error {
}
fmt.Println("Supported formats:")
for i, desc := range descs{
frmSizes, err := desc.GetFrameSizes()
frmSizes, err := v4l2.GetFormatFrameSizes(dev.GetFileDescriptor(), desc.PixelFormat)
if err != nil {
return fmt.Errorf("format desc: %w", err)
}
Expand All @@ -150,8 +148,7 @@ func printFormatDesc(dev *v4l2.Device) error {
for _, size := range frmSizes{
sizeStr.WriteString(fmt.Sprintf("[%dx%d] ", size.Width, size.Height))
}
fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.GetDescription()), sizeStr.String())

fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.Description), sizeStr.String())
}
return nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Example: capture with V4L2 directly
# Example: using handcrafted types (Deprecated)
:warning:

This example is here to illustrate the complexity of v4l2.
This example is here to illustrate the complexity of v4l2 when using hand-crafted
Go types to communicate with the driver. This approach was abandoned in favor of
cgo-generated types (see v4l2 package) for stability.

Do not use it.

If you want to play around with image capture, use the
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion examples/webcam/webcam.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
var (
frames <-chan []byte
fps uint32 = 30
pixfmt v4l2.FourCCEncoding
pixfmt v4l2.FourCCType
)

// servePage reads templated HTML
Expand Down
Loading

0 comments on commit 1a3878d

Please sign in to comment.