github.com/aamcrae/webcam@v0.0.0-20210915060337-934acc13bdc3/snapshot/snapshot.go (about)

     1  // Copyright 2019 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // snapshot extracts image frames from a video stream.
    16  package snapshot
    17  
    18  import (
    19  	"fmt"
    20  
    21  	"github.com/aamcrae/webcam"
    22  	"github.com/aamcrae/webcam/frame"
    23  )
    24  
    25  const (
    26  	defaultTimeout = 5
    27  	defaultBuffers = 16
    28  )
    29  
    30  type snap struct {
    31  	frm   []byte
    32  	index uint32
    33  }
    34  
    35  type Snapper struct {
    36  	cam     *webcam.Webcam
    37  	Timeout uint32
    38  	Buffers uint32
    39  	framer  func([]byte, func()) (frame.Frame, error)
    40  	stop    chan struct{}
    41  	stream  chan snap
    42  }
    43  
    44  // NewSnapper creates a new Snapper.
    45  func NewSnapper() *Snapper {
    46  	return &Snapper{Timeout: defaultTimeout, Buffers: defaultBuffers}
    47  }
    48  
    49  // Close releases all current frames and shuts down the webcam.
    50  func (c *Snapper) Close() {
    51  	if c.cam != nil {
    52  		c.stop <- struct{}{}
    53  		// Flush any remaining frames.
    54  		for f := range c.stream {
    55  			c.cam.ReleaseFrame(f.index)
    56  		}
    57  		c.cam.StopStreaming()
    58  		c.cam.Close()
    59  		c.cam = nil
    60  	}
    61  }
    62  
    63  // Open initialises the webcam ready for use, and begins streaming.
    64  func (c *Snapper) Open(device string, format frame.FourCC, w, h int) (ret error) {
    65  	pf, err := frame.FourCCToPixelFormat(format)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if c.cam != nil {
    70  		c.Close()
    71  	}
    72  	cam, err := webcam.Open(device)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	// Add a defer function that closes the camera in the event of an error.
    77  	defer func() {
    78  		if ret != nil {
    79  			close(c.stream)
    80  			c.Close()
    81  		}
    82  	}()
    83  	c.cam = cam
    84  	c.stop = make(chan struct{}, 1)
    85  	c.stream = make(chan snap, 0)
    86  	// Get the supported formats and their descriptions.
    87  	_, ok := c.cam.GetSupportedFormats()[pf]
    88  	if !ok {
    89  		return fmt.Errorf("%s: unsupported format: %s", device, format)
    90  	}
    91  	var found bool
    92  	for _, value := range c.cam.GetSupportedFrameSizes(pf) {
    93  		if Match(value, w, h) {
    94  			found = true
    95  			break
    96  		}
    97  	}
    98  	if !found {
    99  		return fmt.Errorf("%s: unsupported resolution: %dx%d", device, w, h)
   100  	}
   101  	npf, nw, nh, stride, size, err := c.cam.SetImageFormat(pf, uint32(w), uint32(h))
   102  
   103  	if err != nil {
   104  		return err
   105  	}
   106  	if npf != pf || w != int(nw) || h != int(nh) {
   107  		fmt.Printf("Asked for %08x %dx%d, got %08x %dx%d\n", pf, w, h, npf, nw, nh)
   108  	}
   109  	if c.framer, err = frame.GetFramer(format, w, h, int(stride), int(size)); err != nil {
   110  		return err
   111  	}
   112  
   113  	c.cam.SetBufferCount(c.Buffers)
   114  	c.cam.SetAutoWhiteBalance(true)
   115  	if err := c.cam.StartStreaming(); err != nil {
   116  		return err
   117  	}
   118  	go c.capture()
   119  	return nil
   120  }
   121  
   122  // Snap returns one frame from the camera.
   123  func (c *Snapper) Snap() (frame.Frame, error) {
   124  	snap, ok := <-c.stream
   125  	if !ok {
   126  		return nil, fmt.Errorf("No frame received")
   127  	}
   128  	return c.framer(snap.frm, func() {
   129  		c.cam.ReleaseFrame(snap.index)
   130  	})
   131  }
   132  
   133  // capture continually reads frames and either discards the frames or
   134  // sends them to a channel that is ready.
   135  func (c *Snapper) capture() {
   136  	for {
   137  		err := c.cam.WaitForFrame(c.Timeout)
   138  
   139  		switch err.(type) {
   140  		case nil:
   141  		case *webcam.Timeout:
   142  			continue
   143  		default:
   144  			panic(err)
   145  		}
   146  
   147  		frame, index, err := c.cam.GetFrame()
   148  		if err != nil {
   149  			panic(err)
   150  		}
   151  		select {
   152  		// Only executed if stream is ready to receive.
   153  		case c.stream <- snap{frame, index}:
   154  		// Signal to stop streaming.
   155  		case <-c.stop:
   156  			// Finish up.
   157  			c.cam.ReleaseFrame(index)
   158  			close(c.stream)
   159  			return
   160  		default:
   161  			c.cam.ReleaseFrame(index)
   162  		}
   163  	}
   164  }
   165  
   166  // GetControl returns the current value of a camera control.
   167  func (c *Snapper) GetControl(id webcam.ControlID) (int32, error) {
   168  	return c.cam.GetControl(id)
   169  }
   170  
   171  // SetControl sets the selected camera control.
   172  func (c *Snapper) SetControl(id webcam.ControlID, value int32) error {
   173  	return c.cam.SetControl(id, value)
   174  }
   175  
   176  // Return true if frame size can accomodate request.
   177  func Match(fs webcam.FrameSize, w, h int) bool {
   178  	return canFit(fs.MinWidth, fs.MaxWidth, fs.StepWidth, uint32(w)) &&
   179  		canFit(fs.MinHeight, fs.MaxHeight, fs.StepHeight, uint32(h))
   180  }
   181  
   182  func canFit(min, max, step, val uint32) bool {
   183  	// Fixed size exact match.
   184  	if min == max && step == 0 && val == min {
   185  		return true
   186  	}
   187  	return step != 0 && val >= min && val <= max && ((val-min)%step) == 0
   188  }