Browse Source

Doc updates for examples

Vladimir Vivien 3 years ago
parent
commit
5395a936ba

+ 6 - 0
examples/README.md

@@ -0,0 +1,6 @@
+# Examples
+
+* [capture0](./capture0) - simple capture example with hardcoded pixel format
+* [capture1](./capture1) - capture example with preferred format search
+* [device_info](./device_info) - uses go4vl to retrieve device and format info
+* [webcam](./webcam) - use go4vl to build a working webcam example

+ 58 - 0
examples/capture0/README.md

@@ -0,0 +1,58 @@
+# Capture example
+
+This example shows how to use the `go4vl` API to create a simple program that captures a video frames from an attached input (camera) device.
+
+Firstly, the source code opens a device, `devName`, with a hard-coded pixel format (MPEG) and size. If the device does not support 
+the specified format, the open operation will fail, returning an error.
+
+```go
+func main() {
+    devName := "/dev/video0"
+	// open device
+	device, err := device.Open(
+		devName,
+		device.WithPixFormat(v4l2.PixFormat{PixelFormat: v4l2.PixelFmtMPEG, Width: 640, Height: 480}),
+	)
+...
+}
+```
+
+Next, the source code calls the `device.Start` method to start the input (capture) process.
+
+```go
+func main() {
+...
+	// start stream
+	ctx, stop := context.WithCancel(context.TODO())
+	if err := device.Start(ctx); err != nil {
+		log.Fatalf("failed to start stream: %s", err)
+	}
+...
+
+}
+```
+
+Once the device starts, the code sets up a loop capture incoming video frame buffer from the input device and save each 
+frame to a local file.
+
+```go
+func main() {
+...
+	for frame := range device.GetOutput() {
+		fileName := fmt.Sprintf("capture_%d.jpg", count)
+		file, err := os.Create(fileName)
+		...
+		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)
+		}
+	}
+
+}
+```
+
+> See the full source code [here](./capture0.go).

+ 1 - 1
examples/capture0/capture0.go

@@ -58,7 +58,7 @@ func main() {
 		}
 	}
 
-	stop() // stop capture	
+	stop() // stop capture
 	fmt.Println("Done.")
 
 }

+ 119 - 0
examples/capture1/README.md

@@ -0,0 +1,119 @@
+# Capture example
+
+In this capture example, the source code uses a more advanced approach (compared to [capture0/capture0.go](../capture0/capture0.go)) where it 
+leverages the go4vl device description API to ensure that the device supports the selected preferred format and size.
+
+First, the source code opens the device with `device.Open` function call. Unlike in the [previous example](../capture0/capture0.go), the call to
+`Open` omits the pixel format option.
+
+```go
+func main() {
+	devName := "/dev/video0"
+	device, err := device.Open(devName)
+	if err != nil {
+		log.Fatalf("failed to open device: %s", err)
+	}
+	defer device.Close()
+}
+```
+
+Next, the source code defines a function that is used to search formats supported by the device.
+
+```go
+func main() {
+...
+	findPreferredFmt := func(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCType) *v4l2.FormatDescription {
+		for _, desc := range fmts {
+			if desc.PixelFormat == pixEncoding{
+				return &desc
+			}
+		}
+		return nil
+	}
+}
+```
+
+Next, the code enumerates the formats supported by the device, `device.GetFormatDescriptions`, and used the search function
+to test whether the device support one of several preferred formats.
+
+```go
+func main() {
+...
+	fmtDescs, err := device.GetFormatDescriptions()
+	if err != nil{
+		log.Fatal("failed to get format desc:", err)
+	}
+
+	// search for preferred formats
+	preferredFmts := []v4l2.FourCCType{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV}
+	var fmtDesc *v4l2.FormatDescription
+	for _, preferredFmt := range preferredFmts{
+		fmtDesc = findPreferredFmt(fmtDescs, preferredFmt)
+		if fmtDesc != nil {
+			break
+		}
+	}
+}
+```
+
+Next, if one of the preferred formats is found, then it is assigned to `fmtDesc`. The next step is to search the device 
+for an appropriate supported dimension (640x480) for the selected format which is stored in `frmSize`.
+
+```go
+func main() {
+...
+    frameSizes, err := v4l2.GetFormatFrameSizes(device.Fd(), fmtDesc.PixelFormat)
+
+	// select size 640x480 for format
+	var frmSize v4l2.FrameSizeEnum
+	for _, size := range frameSizes {
+		if size.Size.MinWidth == 640 && size.Size.MinHeight == 480 {
+			frmSize = size
+			break
+		}
+	}
+}
+```
+
+At this point, the device can be assigned the selected pixel format and its associated size.
+
+```go
+func main() {
+...
+	if err := device.SetPixFormat(v4l2.PixFormat{
+		Width:       frmSize.Size.MinWidth,
+		Height:      frmSize.Size.MinHeight,
+		PixelFormat: fmtDesc.PixelFormat,
+		Field:       v4l2.FieldNone,
+	}); err != nil {
+		log.Fatalf("failed to set format: %s", err)
+	}
+}
+```
+
+Finally, the device can be started and the streaming buffers can be captured:
+
+```go
+fun main() {
+...
+	if err := device.Start(ctx); err != nil {
+		log.Fatalf("failed to stream: %s", err)
+	}
+
+	for frame := range device.GetOutput() {
+		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
+		}
+        ...
+	}
+}
+```
+
+> See source code [here](./capture1.go).

+ 0 - 0
examples/capture1/capture.go → examples/capture1/capture1.go


+ 40 - 0
examples/device_info/README.md

@@ -0,0 +1,40 @@
+# Device info example
+
+The example in this directory showcases `go4vl` support for device information. For instance, the following function 
+prints driver information
+
+```go
+func main() {
+    devName := '/dev/video0'
+    device, err := device2.Open(devName)
+    if err := printDeviceDriverInfo(device); err != nil {
+        log.Fatal(err)
+    }
+}
+
+func printDeviceDriverInfo(dev *device.Device) error {
+	caps := dev.Capability()
+
+	// print driver info
+	fmt.Println("v4l2Device Info:")
+	fmt.Printf(template, "Driver name", caps.Driver)
+	fmt.Printf(template, "Card name", caps.Card)
+	fmt.Printf(template, "Bus info", caps.BusInfo)
+
+	fmt.Printf(template, "Driver version", caps.GetVersionInfo())
+
+	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", "v4l2Device capabilities", caps.Capabilities)
+	for _, desc := range caps.GetDeviceCapDescriptions() {
+		fmt.Printf("\t\t%s\n", desc.Desc)
+	}
+
+	return nil
+}
+```
+
+> See the [complete example](./devinfo.go) and all available device information from go4vl.

+ 162 - 0
examples/webcam/README.md

@@ -0,0 +1,162 @@
+# Webcam example
+
+The webcam examples shows how the `go4vl` API can be used to create a webcam that streams incoming video frames from an attached camera to a web page. The code sets up a web server that returns a web page with an image element that continuously stream the captured video from the camera.
+
+## Running the example
+Keep in mind that this code can only run on systems with the Linux operating system.
+Before you can build and run the code, you must satisfy the following prerequisites.
+
+### Pre-requisites
+
+* Go compiler/tools
+* Linux OS (32- or 64-bit)
+* Kernel minimum v5.10.x or higher
+* A locally configured C compiler (i.e. gcc)
+* Header files for V4L2 (i.e. /usr/include/linux/videodev2.h)
+* A video camera (with support for Video for Linux API)
+
+If you are running a system that has not been upgraded in a while, ensure to issue the following commands:
+
+```
+sudo apt update
+sudo apt full-upgrade
+```
+
+This example has been tested using a Raspberry Pi 3 running 32-bit Linux, with kernel version 5.14, with cheap USB video camera attached.
+
+### Build and run
+
+From within this directory, build with the following command:
+
+```
+go build -o webcam webcam.go
+```
+
+Once built, you can start the webcam with the following command (and output as shown):
+
+```
+ ./webcam
+ 
+2022/05/21 09:04:31 device [/dev/video0] opened
+2022/05/21 09:04:31 device info: driver: uvcvideo; card: HDM Webcam USB: HDM Webcam USB; bus info: usb-3f980000.usb-1.5
+2022/05/21 09:04:31 Current format: Motion-JPEG [1920x1080]; field=any; bytes per line=0; size image=0; colorspace=Default; YCbCr=Default; Quant=Default; XferFunc=Default
+2022/05/21 09:04:31 device capture started, frames available
+2022/05/21 09:04:31 starting server on port :9090
+2022/05/21 09:04:31 use url path /webcam
+```
+
+Next, point your browser to your machine's address and shown port (i.e. `http://198.162.100.20:9090`). You should see a webpage with the streaming image in the middle (see below.)
+
+The webcam program offers several CLI arguments that you can use to configure the webcam:
+
+```
+./webcam --help
+Usage of ./webcam:
+  -d string
+    	device name (path) (default "/dev/video0")
+  -f string
+    	pixel format (default "mjpeg")
+  -h int
+    	capture height (default 1080)
+  -p string
+    	webcam service port (default ":9090")
+  -r int
+    	frames per second (fps) (default 30)
+  -w int
+    	capture width (default 1920)
+```
+
+## The source code
+The following code walkthrough illustrates how simple it is to create programs that can stream video using the `go4vl` project.
+
+Firstly, the `main` function opens the video device with a set of specified configurations (from CLI flags):
+
+```go
+var frames <-chan []byte
+
+func main() {
+    port := ":9090"
+    devName := "/dev/video0"
+    frameRate := 30
+    
+    // create device
+	device, err := device.Open(devName,
+	    device.WithIOType(v4l2.IOTypeMMAP),
+	    device.WithPixFormat(v4l2.PixFormat{PixelFormat: getFormatType(format), Width: uint32(width), Height: uint32(height)}),
+	    device.WithFPS(uint32(frameRate)), 
+	)
+}
+```
+
+Next, start the device and make the device stream available via package variable `frames`:
+
+```go
+var frames <-chan []byte
+
+func main() {
+...
+	ctx, cancel := context.WithCancel(context.TODO())
+	if err := device.Start(ctx); err != nil {
+		log.Fatalf("stream capture: %s", err)
+	}
+	defer func() {
+		cancel()
+		device.Close()
+	}()
+
+	frames = device.GetOutput()
+
+}
+```
+
+The last major step is to start an HTTP server to serve the video buffers, as images, and the page for the webcam:
+
+```go
+var frames <-chan []byte
+
+func main() {
+...
+
+	// setup http service
+	http.HandleFunc("/webcam", servePage)        // returns an html page
+	http.HandleFunc("/stream", serveVideoStream) // returns video feed
+	if err := http.ListenAndServe(port, nil); err != nil {
+		log.Fatal(err)
+	}
+}
+```
+
+The video captured from the camera is served at endpoint `/stream` (see source above) which is serviced by HTTP handler
+function `serveVideoStream`. The function uses a content type of `multipart/x-mixed-replace`, with a separate boundary for
+each image buffer, that is rendered on the browser as a video stream.
+
+```go
+func serveVideoStream(w http.ResponseWriter, req *http.Request) {
+	// Start HTTP Response
+	const boundaryName = "Yt08gcU534c0p4Jqj0p0"
+
+	// send multi-part header
+	w.Header().Set("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=%s", boundaryName))
+	w.WriteHeader(http.StatusOK)
+
+	for frame := range frames {
+		// start boundary
+		io.WriteString(w, fmt.Sprintf("--%s\n", boundaryName))
+		io.WriteString(w, "Content-Type: image/jpeg\n")
+		io.WriteString(w, fmt.Sprintf("Content-Length: %d\n\n", len(frame)))
+
+		if _, err := w.Write(frame); err != nil {
+			log.Printf("failed to write mjpeg image: %s", err)
+			return
+		}
+		
+		// close boundary
+		if _, err := io.WriteString(w, "\n"); err != nil {
+			log.Printf("failed to write boundary: %s", err)
+			return
+		}
+	}
+}
+```
+
+> See the full source code [here](./webcam.go)

+ 7 - 27
examples/webcam/webcam.go

@@ -12,12 +12,11 @@ import (
 	"time"
 
 	"github.com/vladimirvivien/go4vl/device"
-	"github.com/vladimirvivien/go4vl/imgsupport"
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
 var (
-	frames chan []byte
+	frames <-chan []byte
 	fps    uint32 = 30
 	pixfmt v4l2.FourCCType
 )
@@ -71,20 +70,13 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 				log.Printf("failed to write mjpeg image: %s", err)
 				return
 			}
-		case v4l2.PixelFmtYUYV:
-			data, err := imgsupport.Yuyv2Jpeg(640, 480, frame)
-			if err != nil {
-				log.Printf("failed to convert yuyv to jpeg: %s", err)
-				continue
-			}
-			if _, err := w.Write(data); err != nil {
-				log.Printf("failed to write yuyv image: %s", err)
-				return
-			}
+		default:
+			log.Printf("selected pixel format is not supported")
 		}
+
 		// close boundary
 		if _, err := io.WriteString(w, "\n"); err != nil {
-			log.Printf("failed to write bounday: %s", err)
+			log.Printf("failed to write boundary: %s", err)
 			return
 		}
 	}
@@ -165,22 +157,10 @@ func main() {
 		device.Close()
 	}()
 
-	// buffer captured video stream into a local channel of bytes
-	frames = make(chan []byte, 1024)
-	go func() {
-		defer close(frames)
-		for {
-			select {
-			case frame := <-device.GetOutput():
-				frames <- frame
-			case <-ctx.Done():
-				return
-			}
-		}
-	}()
+	// video stream
+	frames = device.GetOutput()
 
 	log.Println("device capture started, frames available")
-
 	log.Printf("starting server on port %s", port)
 	log.Println("use url path /webcam")