Skip to content

Commit 827a907

Browse files
hexbabenicksanforddgottliebethanlookpottsabe-winter
authored
[RSDK-9864] Remove gostream middle-man dependency from webcam (viamrobotics#4604)
Co-authored-by: nicksanford <[email protected]> Co-authored-by: Dan Gottlieb <[email protected]> Co-authored-by: Ethan Look-Potts <[email protected]> Co-authored-by: abe-winter <[email protected]> Co-authored-by: Eliot Horowitz <[email protected]> Co-authored-by: Naomi Pentrel <[email protected]> Co-authored-by: Maxim Pertsov <[email protected]> Co-authored-by: nicksanford <[email protected]> Co-authored-by: Devin Hilly <[email protected]> Co-authored-by: martha-johnston <[email protected]> Co-authored-by: Kurt S <[email protected]> Co-authored-by: randhid <[email protected]> Co-authored-by: randhid <[email protected]>
1 parent d65bbc4 commit 827a907

File tree

4 files changed

+328
-412
lines changed

4 files changed

+328
-412
lines changed
+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package videosource
2+
3+
import (
4+
"math"
5+
"strings"
6+
"time"
7+
8+
"github.com/pion/mediadevices"
9+
"github.com/pion/mediadevices/pkg/driver"
10+
"github.com/pion/mediadevices/pkg/driver/availability"
11+
"github.com/pion/mediadevices/pkg/driver/camera"
12+
"github.com/pion/mediadevices/pkg/io/video"
13+
"github.com/pion/mediadevices/pkg/prop"
14+
"github.com/pkg/errors"
15+
16+
"go.viam.com/rdk/logging"
17+
)
18+
19+
// Below is adapted from github.com/pion/mediadevices.
20+
// It is further adapted from gostream's query.go
21+
// However, this is the minimum code needed for webcam to work, placed in this directory.
22+
// This vastly improves the debugging and feature development experience, by not over-DRY-ing.
23+
24+
// GetNamedVideoSource attempts to find a device (not a screen) by the given name.
25+
// If name is empty, it finds any device.
26+
func getReaderAndDriver(
27+
name string,
28+
constraints mediadevices.MediaStreamConstraints,
29+
logger logging.Logger,
30+
) (video.Reader, driver.Driver, error) {
31+
var ptr *string
32+
if name == "" {
33+
ptr = nil
34+
} else {
35+
ptr = &name
36+
}
37+
d, selectedMedia, err := getUserVideoDriver(constraints, ptr, logger)
38+
if err != nil {
39+
return nil, nil, err
40+
}
41+
reader, err := newReaderFromDriver(d, selectedMedia)
42+
if err != nil {
43+
return nil, nil, err
44+
}
45+
return reader, d, nil
46+
}
47+
48+
func getUserVideoDriver(
49+
constraints mediadevices.MediaStreamConstraints,
50+
label *string,
51+
logger logging.Logger,
52+
) (driver.Driver, prop.Media, error) {
53+
var videoConstraints mediadevices.MediaTrackConstraints
54+
if constraints.Video != nil {
55+
constraints.Video(&videoConstraints)
56+
}
57+
return selectVideo(videoConstraints, label, logger)
58+
}
59+
60+
func newReaderFromDriver(
61+
videoDriver driver.Driver,
62+
mediaProp prop.Media,
63+
) (video.Reader, error) {
64+
recorder, ok := videoDriver.(driver.VideoRecorder)
65+
if !ok {
66+
return nil, errors.New("driver not a driver.VideoRecorder")
67+
}
68+
69+
if ok, err := driver.IsAvailable(videoDriver); !errors.Is(err, availability.ErrUnimplemented) && !ok {
70+
return nil, errors.Wrap(err, "video driver not available")
71+
} else if driverStatus := videoDriver.Status(); driverStatus != driver.StateClosed {
72+
return nil, errors.New("video driver in use")
73+
} else if err := videoDriver.Open(); err != nil {
74+
return nil, errors.Wrap(err, "cannot open video driver")
75+
}
76+
77+
mediaProp.DiscardFramesOlderThan = time.Second
78+
reader, err := recorder.VideoRecord(mediaProp)
79+
if err != nil {
80+
return nil, err
81+
}
82+
return reader, nil
83+
}
84+
85+
func labelFilter(target string, useSep bool) driver.FilterFn {
86+
return driver.FilterFn(func(d driver.Driver) bool {
87+
if !useSep {
88+
return d.Info().Label == target
89+
}
90+
labels := strings.Split(d.Info().Label, camera.LabelSeparator)
91+
for _, label := range labels {
92+
if label == target {
93+
return true
94+
}
95+
}
96+
return false
97+
})
98+
}
99+
100+
func selectVideo(
101+
constraints mediadevices.MediaTrackConstraints,
102+
label *string,
103+
logger logging.Logger,
104+
) (driver.Driver, prop.Media, error) {
105+
return selectBestDriver(getVideoFilterBase(), getVideoFilter(label), constraints, logger)
106+
}
107+
108+
func getVideoFilterBase() driver.FilterFn {
109+
typeFilter := driver.FilterVideoRecorder()
110+
notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen))
111+
return driver.FilterAnd(typeFilter, notScreenFilter)
112+
}
113+
114+
func getVideoFilter(label *string) driver.FilterFn {
115+
filter := getVideoFilterBase()
116+
if label != nil {
117+
filter = driver.FilterAnd(filter, labelFilter(*label, true))
118+
}
119+
return filter
120+
}
121+
122+
// select implements SelectSettings algorithm.
123+
// Reference: https://w3c.github.io/mediacapture-main/#dfn-selectsettings
124+
func selectBestDriver(
125+
baseFilter driver.FilterFn,
126+
filter driver.FilterFn,
127+
constraints mediadevices.MediaTrackConstraints,
128+
logger logging.Logger,
129+
) (driver.Driver, prop.Media, error) {
130+
var bestDriver driver.Driver
131+
var bestProp prop.Media
132+
minFitnessDist := math.Inf(1)
133+
134+
baseDrivers := driver.GetManager().Query(baseFilter)
135+
logger.Debugw("before specific filter, we found the following drivers", "count", len(baseDrivers))
136+
for _, d := range baseDrivers {
137+
logger.Debugw(d.Info().Label, "priority", float32(d.Info().Priority), "type", d.Info().DeviceType)
138+
}
139+
140+
driverProperties := queryDriverProperties(filter, logger)
141+
if len(driverProperties) == 0 {
142+
logger.Debugw("found no drivers matching filter")
143+
} else {
144+
logger.Debugw("found drivers matching specific filter", "count", len(driverProperties))
145+
}
146+
for d, props := range driverProperties {
147+
priority := float64(d.Info().Priority)
148+
logger.Debugw(
149+
"considering driver",
150+
"label", d.Info().Label,
151+
"priority", priority)
152+
for _, p := range props {
153+
fitnessDist, ok := constraints.MediaConstraints.FitnessDistance(p)
154+
if !ok {
155+
logger.Debugw("driver does not satisfy any constraints", "label", d.Info().Label)
156+
continue
157+
}
158+
fitnessDistWithPriority := fitnessDist - priority
159+
logger.Debugw(
160+
"driver properties satisfy some constraints",
161+
"label", d.Info().Label,
162+
"props", p,
163+
"distance", fitnessDist,
164+
"distance_with_priority", fitnessDistWithPriority)
165+
if fitnessDistWithPriority < minFitnessDist {
166+
minFitnessDist = fitnessDistWithPriority
167+
bestDriver = d
168+
bestProp = p
169+
}
170+
}
171+
}
172+
173+
if bestDriver == nil {
174+
return nil, prop.Media{}, errors.New("failed to find the best driver that fits the constraints")
175+
}
176+
177+
logger.Debugw("winning driver", "label", bestDriver.Info().Label, "props", bestProp)
178+
selectedMedia := prop.Media{}
179+
selectedMedia.MergeConstraints(constraints.MediaConstraints)
180+
selectedMedia.Merge(bestProp)
181+
return bestDriver, selectedMedia, nil
182+
}
183+
184+
func queryDriverProperties(
185+
filter driver.FilterFn,
186+
logger logging.Logger,
187+
) map[driver.Driver][]prop.Media {
188+
var needToClose []driver.Driver
189+
drivers := driver.GetManager().Query(filter)
190+
m := make(map[driver.Driver][]prop.Media)
191+
192+
for _, d := range drivers {
193+
var status string
194+
isAvailable, err := driver.IsAvailable(d)
195+
if errors.Is(err, availability.ErrUnimplemented) {
196+
s := d.Status()
197+
status = string(s)
198+
isAvailable = s == driver.StateClosed
199+
} else if err != nil {
200+
status = err.Error()
201+
}
202+
203+
if isAvailable {
204+
err := d.Open()
205+
if err != nil {
206+
logger.Debugw("error opening driver for querying", "error", err)
207+
// Skip this driver if we failed to open because we can't get the properties
208+
continue
209+
}
210+
needToClose = append(needToClose, d)
211+
m[d] = d.Properties()
212+
} else {
213+
logger.Debugw("driver not available", "name", d.Info().Name, "label", d.Info().Label, "status", status)
214+
}
215+
}
216+
217+
for _, d := range needToClose {
218+
// Since it was closed, we should close it to avoid a leak
219+
if err := d.Close(); err != nil {
220+
logger.Errorw("error closing driver", "error", err)
221+
}
222+
}
223+
224+
return m
225+
}

0 commit comments

Comments
 (0)