Skip to content

Commit b664d29

Browse files
AGanguly13hexbabe
authored andcommitted
Rdk webcam lag fix (#5053)
1 parent 3a9d6cf commit b664d29

File tree

1 file changed

+102
-14
lines changed

1 file changed

+102
-14
lines changed

components/camera/videosource/webcam.go

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package videosource
44
import (
55
"context"
66
"fmt"
7+
"image"
78
"path/filepath"
89
"strings"
910
"sync"
@@ -43,7 +44,10 @@ var (
4344
// as equally acceptable. See https://github.com/pion/mediadevices/blob/c10fb000dbbb28597e068468f3175dc68a281bfd/pkg/prop/int.go#L104
4445
// Setting it to 1 could theoretically allow 1x1 resolutions. 2 is small enough and even,
4546
// allowing all real camera resolutions while ensuring proper distance calculations.
46-
const minResolutionDimension = 2
47+
const (
48+
minResolutionDimension = 2
49+
defaultFrameRate = float32(30.0)
50+
)
4751

4852
func init() {
4953
resource.RegisterComponent(
@@ -54,6 +58,15 @@ func init() {
5458
})
5559
}
5660

61+
// WebcamBuffer is a buffer for webcam frames.
62+
// WARNING: This struct is NOT thread safe. It must be protected by the mutex in the webcam struct.
63+
type WebcamBuffer struct {
64+
frame image.Image // Holds the frames and their release functions in the buffer
65+
release func()
66+
err error
67+
worker *goutils.StoppableWorkers // A separate worker for the webcam buffer that allows stronger concurrency control.
68+
}
69+
5770
// WebcamConfig is the native config attribute struct for webcams.
5871
type WebcamConfig struct {
5972
CameraParameters *transform.PinholeCameraIntrinsics `json:"intrinsic_parameters,omitempty"`
@@ -199,6 +212,8 @@ type webcam struct {
199212
disconnected bool
200213
logger logging.Logger
201214
workers *goutils.StoppableWorkers
215+
216+
buffer *WebcamBuffer
202217
}
203218

204219
// NewWebcam returns the webcam discovered based on the given config as the Camera interface type.
@@ -213,6 +228,8 @@ func NewWebcam(
213228
logger: logger.WithFields("camera_name", conf.ResourceName().ShortName()),
214229
workers: goutils.NewBackgroundStoppableWorkers(),
215230
}
231+
cam.buffer = NewWebcamBuffer(cam.workers.Context())
232+
216233
if err := cam.Reconfigure(ctx, deps, conf); err != nil {
217234
return nil, err
218235
}
@@ -230,12 +247,12 @@ func (c *webcam) Reconfigure(
230247
if err != nil {
231248
return err
232249
}
233-
250+
c.buffer.worker.Stop() // Calling this before locking shuts down the goroutines, and allows stopBuffer() to handle rest of the shutdown.
234251
c.mu.Lock()
235252
defer c.mu.Unlock()
253+
c.buffer.stopBuffer()
236254

237255
c.cameraModel = camera.NewPinholeModelWithBrownConradyDistortion(newConf.CameraParameters, newConf.DistortionParameters)
238-
239256
driverReinitNotNeeded := c.conf.Format == newConf.Format &&
240257
c.conf.Path == newConf.Path &&
241258
c.conf.Width == newConf.Width &&
@@ -256,6 +273,13 @@ func (c *webcam) Reconfigure(
256273

257274
// only set once we're good
258275
c.conf = *newConf
276+
277+
if c.conf.FrameRate == 0.0 {
278+
c.conf.FrameRate = defaultFrameRate
279+
}
280+
c.buffer = NewWebcamBuffer(c.workers.Context())
281+
c.startBuffer()
282+
259283
return nil
260284
}
261285

@@ -371,15 +395,11 @@ func (c *webcam) Images(ctx context.Context) ([]camera.NamedImage, resource.Resp
371395
return nil, resource.ResponseMetadata{}, err
372396
}
373397

374-
img, release, err := c.reader.Read()
398+
img, err := c.getLatestFrame()
375399
if err != nil {
376400
return nil, resource.ResponseMetadata{}, errors.Wrap(err, "monitoredWebcam: call to get Images failed")
377401
}
378-
defer func() {
379-
if release != nil {
380-
release()
381-
}
382-
}()
402+
383403
namedImg, err := camera.NamedImageFromImage(img, c.Name().Name, utils.MimeTypeJPEG)
384404
if err != nil {
385405
return nil, resource.ResponseMetadata{}, err
@@ -408,11 +428,10 @@ func (c *webcam) Image(ctx context.Context, mimeType string, extra map[string]in
408428
if c.reader == nil {
409429
return nil, camera.ImageMetadata{}, errors.New("underlying reader is nil")
410430
}
411-
img, release, err := c.reader.Read()
431+
img, err := c.getLatestFrame()
412432
if err != nil {
413433
return nil, camera.ImageMetadata{}, err
414434
}
415-
defer release()
416435

417436
if mimeType == "" {
418437
mimeType = utils.MimeTypeJPEG
@@ -476,15 +495,84 @@ func (c *webcam) Geometries(ctx context.Context, extra map[string]interface{}) (
476495
return make([]spatialmath.Geometry, 0), nil
477496
}
478497

498+
// NewWebcamBuffer creates a new WebcamBuffer struct.
499+
func NewWebcamBuffer(ctx context.Context) *WebcamBuffer {
500+
return &WebcamBuffer{
501+
worker: goutils.NewStoppableWorkers(ctx),
502+
}
503+
}
504+
505+
// Must lock the mutex before calling this function.
506+
func (c *webcam) getLatestFrame() (image.Image, error) {
507+
if c.buffer.frame == nil {
508+
if c.buffer.err != nil {
509+
return nil, c.buffer.err
510+
}
511+
return nil, errors.New("no frames available to read")
512+
}
513+
514+
return c.buffer.frame, nil
515+
}
516+
517+
func (c *webcam) startBuffer() {
518+
if c.buffer.frame != nil {
519+
return // webcam buffer already started
520+
}
521+
522+
interFrameDuration := time.Duration(float32(time.Second) / c.conf.FrameRate)
523+
ticker := time.NewTicker(interFrameDuration)
524+
c.buffer.worker.Add(func(closedCtx context.Context) {
525+
defer ticker.Stop()
526+
for {
527+
select {
528+
case <-closedCtx.Done():
529+
return
530+
case <-ticker.C:
531+
// We must unlock the mutex even if the release() or read() functions panic.
532+
func() {
533+
c.mu.Lock()
534+
defer c.mu.Unlock()
535+
if c.buffer.release != nil {
536+
c.buffer.release()
537+
c.buffer.release = nil
538+
c.buffer.frame = nil
539+
}
540+
img, release, err := c.reader.Read()
541+
c.buffer.err = err
542+
if err != nil {
543+
c.logger.Errorf("error reading frame: %v", err)
544+
return // next iteration of for loop
545+
}
546+
c.buffer.frame = img
547+
c.buffer.release = release
548+
}()
549+
}
550+
}
551+
})
552+
}
553+
554+
// Must lock the mutex before using this function.
555+
func (buffer *WebcamBuffer) stopBuffer() {
556+
if buffer == nil {
557+
return
558+
}
559+
560+
// Release the remaining frame.
561+
if buffer.release != nil {
562+
buffer.release()
563+
buffer.release = nil
564+
}
565+
}
566+
479567
func (c *webcam) Close(ctx context.Context) error {
568+
c.workers.Stop()
569+
c.buffer.worker.Stop()
480570
c.mu.Lock()
571+
defer c.mu.Unlock()
481572
if c.closed {
482-
c.mu.Unlock()
483573
return errors.New("webcam already closed")
484574
}
485575
c.closed = true
486-
c.mu.Unlock()
487-
c.workers.Stop()
488576

489577
return c.driver.Close()
490578
}

0 commit comments

Comments
 (0)