mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-19 15:04:52 +08:00
prop: support ranged/exact/oneof constraints
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,10 +67,10 @@ func main() {
|
|||||||
|
|
||||||
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
|
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = frame.FormatI420 // most of the encoder accepts I420
|
c.FrameFormat = prop.FrameFormatExact(frame.FormatI420) // most of the encoder accepts I420
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
c.Width = 640
|
c.Width = prop.Int(640)
|
||||||
c.Height = 480
|
c.Height = prop.Int(480)
|
||||||
c.VideoTransform = markFacesTransformer
|
c.VideoTransform = markFacesTransformer
|
||||||
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
||||||
},
|
},
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
|
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
|
||||||
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v2"
|
||||||
"github.com/pion/webrtc/v2/pkg/media"
|
"github.com/pion/webrtc/v2/pkg/media"
|
||||||
@@ -48,10 +49,10 @@ func main() {
|
|||||||
|
|
||||||
_, err = md.GetUserMedia(mediadevices.MediaStreamConstraints{
|
_, err = md.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = frame.FormatYUY2
|
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
c.Width = 640
|
c.Width = prop.Int(640)
|
||||||
c.Height = 480
|
c.Height = prop.Int(480)
|
||||||
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/examples/internal/signal"
|
"github.com/pion/mediadevices/examples/internal/signal"
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v2"
|
||||||
|
|
||||||
// This is required to use opus audio encoder
|
// This is required to use opus audio encoder
|
||||||
@@ -80,10 +81,10 @@ func main() {
|
|||||||
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&opusParams}
|
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&opusParams}
|
||||||
},
|
},
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = frame.FormatYUY2
|
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
c.Width = 640
|
c.Width = prop.Int(640)
|
||||||
c.Height = 480
|
c.Height = prop.Int(480)
|
||||||
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@@ -200,7 +200,11 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
|
|||||||
for d, props := range driverProperties {
|
for d, props := range driverProperties {
|
||||||
priority := float64(d.Info().Priority)
|
priority := float64(d.Info().Priority)
|
||||||
for _, p := range props {
|
for _, p := range props {
|
||||||
fitnessDist := constraints.Media.FitnessDistance(p) - priority
|
fitnessDist, ok := constraints.MediaConstraints.FitnessDistance(p)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fitnessDist -= priority
|
||||||
if fitnessDist < minFitnessDist {
|
if fitnessDist < minFitnessDist {
|
||||||
minFitnessDist = fitnessDist
|
minFitnessDist = fitnessDist
|
||||||
bestDriver = d
|
bestDriver = d
|
||||||
@@ -213,7 +217,8 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
|
|||||||
return nil, MediaTrackConstraints{}, errNotFound
|
return nil, MediaTrackConstraints{}, errNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints.Merge(bestProp)
|
constraints.selectedMedia = bestProp
|
||||||
|
constraints.selectedMedia.Merge(constraints.MediaConstraints)
|
||||||
return bestDriver, constraints, nil
|
return bestDriver, constraints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -50,8 +50,8 @@ func TestGetUserMedia(t *testing.T) {
|
|||||||
constraints := MediaStreamConstraints{
|
constraints := MediaStreamConstraints{
|
||||||
Video: func(c *MediaTrackConstraints) {
|
Video: func(c *MediaTrackConstraints) {
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
c.Width = 640
|
c.Width = prop.Int(640)
|
||||||
c.Height = 480
|
c.Height = prop.Int(480)
|
||||||
params := videoParams
|
params := videoParams
|
||||||
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
||||||
},
|
},
|
||||||
@@ -64,8 +64,8 @@ func TestGetUserMedia(t *testing.T) {
|
|||||||
constraintsWrong := MediaStreamConstraints{
|
constraintsWrong := MediaStreamConstraints{
|
||||||
Video: func(c *MediaTrackConstraints) {
|
Video: func(c *MediaTrackConstraints) {
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
c.Width = 640
|
c.Width = prop.Int(640)
|
||||||
c.Height = 480
|
c.Height = prop.Int(480)
|
||||||
params := videoParams
|
params := videoParams
|
||||||
params.BitRate = 0
|
params.BitRate = 0
|
||||||
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
||||||
|
@@ -14,7 +14,7 @@ type MediaStreamConstraints struct {
|
|||||||
|
|
||||||
// MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints
|
// MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints
|
||||||
type MediaTrackConstraints struct {
|
type MediaTrackConstraints struct {
|
||||||
prop.Media
|
prop.MediaConstraints
|
||||||
Enabled bool
|
Enabled bool
|
||||||
// VideoEncoderBuilders are codec builders that are used for encoding the video
|
// VideoEncoderBuilders are codec builders that are used for encoding the video
|
||||||
// and later being used for sending the appropriate RTP payload type.
|
// and later being used for sending the appropriate RTP payload type.
|
||||||
@@ -34,6 +34,8 @@ type MediaTrackConstraints struct {
|
|||||||
// AudioTransform will be used to transform the audio that's coming from the driver.
|
// AudioTransform will be used to transform the audio that's coming from the driver.
|
||||||
// So, basically it'll look like following: driver -> AudioTransform -> code
|
// So, basically it'll look like following: driver -> AudioTransform -> code
|
||||||
AudioTransform audio.TransformFunc
|
AudioTransform audio.TransformFunc
|
||||||
|
|
||||||
|
selectedMedia prop.Media
|
||||||
}
|
}
|
||||||
|
|
||||||
type MediaOption func(*MediaTrackConstraints)
|
type MediaOption func(*MediaTrackConstraints)
|
||||||
|
83
pkg/prop/duration.go
Normal file
83
pkg/prop/duration.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package prop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DurationConstraint interface {
|
||||||
|
Compare(time.Duration) (float64, bool)
|
||||||
|
Value() (time.Duration, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Duration time.Duration
|
||||||
|
|
||||||
|
func (d Duration) Compare(a time.Duration) (float64, bool) {
|
||||||
|
return math.Abs(float64(a-time.Duration(d))) / math.Max(math.Abs(float64(a)), math.Abs(float64(d))), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Duration) Value() (time.Duration, bool) { return time.Duration(d), true }
|
||||||
|
|
||||||
|
type DurationExact time.Duration
|
||||||
|
|
||||||
|
func (d DurationExact) Compare(a time.Duration) (float64, bool) {
|
||||||
|
if time.Duration(d) == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DurationExact) Value() (time.Duration, bool) { return time.Duration(d), true }
|
||||||
|
|
||||||
|
type DurationOneOf []time.Duration
|
||||||
|
|
||||||
|
func (d DurationOneOf) Compare(a time.Duration) (float64, bool) {
|
||||||
|
for _, ii := range d {
|
||||||
|
if ii == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DurationOneOf) Value() (time.Duration, bool) { return 0, false }
|
||||||
|
|
||||||
|
type DurationRanged struct {
|
||||||
|
Min time.Duration
|
||||||
|
Max time.Duration
|
||||||
|
Ideal time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DurationRanged) Compare(a time.Duration) (float64, bool) {
|
||||||
|
if d.Min != 0 && d.Min > a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if d.Max != 0 && d.Max < a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if d.Ideal == 0 {
|
||||||
|
// If the value is in the range and Ideal is not specified,
|
||||||
|
// any value is evenly acceptable.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case a == d.Ideal:
|
||||||
|
return 0.0, true
|
||||||
|
case a < d.Ideal:
|
||||||
|
if d.Min == 0 {
|
||||||
|
// If Min is not specified, smaller values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(d.Ideal-a) / float64(d.Ideal-d.Min), true
|
||||||
|
default:
|
||||||
|
if d.Max == 0 {
|
||||||
|
// If Max is not specified, larger values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(a-d.Ideal) / float64(d.Max-d.Ideal), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DurationRanged) Value() (time.Duration, bool) { return 0, false }
|
82
pkg/prop/float.go
Normal file
82
pkg/prop/float.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package prop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FloatConstraint interface {
|
||||||
|
Compare(float32) (float64, bool)
|
||||||
|
Value() (float32, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Float float32
|
||||||
|
|
||||||
|
func (f Float) Compare(a float32) (float64, bool) {
|
||||||
|
return math.Abs(float64(a-float32(f))) / math.Max(math.Abs(float64(a)), math.Abs(float64(f))), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Float) Value() (float32, bool) { return float32(f), true }
|
||||||
|
|
||||||
|
type FloatExact float32
|
||||||
|
|
||||||
|
func (f FloatExact) Compare(a float32) (float64, bool) {
|
||||||
|
if float32(f) == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FloatExact) Value() (float32, bool) { return float32(f), true }
|
||||||
|
|
||||||
|
type FloatOneOf []float32
|
||||||
|
|
||||||
|
func (f FloatOneOf) Compare(a float32) (float64, bool) {
|
||||||
|
for _, ff := range f {
|
||||||
|
if ff == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FloatOneOf) Value() (float32, bool) { return 0, false }
|
||||||
|
|
||||||
|
type FloatRanged struct {
|
||||||
|
Min float32
|
||||||
|
Max float32
|
||||||
|
Ideal float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FloatRanged) Compare(a float32) (float64, bool) {
|
||||||
|
if f.Min != 0 && f.Min > a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if f.Max != 0 && f.Max < a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if f.Ideal == 0 {
|
||||||
|
// If the value is in the range and Ideal is not specified,
|
||||||
|
// any value is evenly acceptable.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case a == f.Ideal:
|
||||||
|
return 0.0, true
|
||||||
|
case a < f.Ideal:
|
||||||
|
if f.Min == 0 {
|
||||||
|
// If Min is not specified, smaller values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(f.Ideal-a) / float64(f.Ideal-f.Min), true
|
||||||
|
default:
|
||||||
|
if f.Max == 0 {
|
||||||
|
// If Max is not specified, larger values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(a-f.Ideal) / float64(f.Max-f.Ideal), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FloatRanged) Value() (float32, bool) { return 0, false }
|
45
pkg/prop/format.go
Normal file
45
pkg/prop/format.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package prop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FrameFormatConstraint interface {
|
||||||
|
Compare(frame.Format) (float64, bool)
|
||||||
|
Value() (frame.Format, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FrameFormat frame.Format
|
||||||
|
|
||||||
|
func (f FrameFormat) Compare(a frame.Format) (float64, bool) {
|
||||||
|
if frame.Format(f) == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return 1.0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FrameFormat) Value() (frame.Format, bool) { return frame.Format(f), true }
|
||||||
|
|
||||||
|
type FrameFormatExact frame.Format
|
||||||
|
|
||||||
|
func (f FrameFormatExact) Compare(a frame.Format) (float64, bool) {
|
||||||
|
if frame.Format(f) == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FrameFormatExact) Value() (frame.Format, bool) { return frame.Format(f), true }
|
||||||
|
|
||||||
|
type FrameFormatOneOf []frame.Format
|
||||||
|
|
||||||
|
func (f FrameFormatOneOf) Compare(a frame.Format) (float64, bool) {
|
||||||
|
for _, ff := range f {
|
||||||
|
if ff == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FrameFormatOneOf) Value() (frame.Format, bool) { return "", false }
|
82
pkg/prop/int.go
Normal file
82
pkg/prop/int.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package prop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntConstraint interface {
|
||||||
|
Compare(int) (float64, bool)
|
||||||
|
Value() (int, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Int int
|
||||||
|
|
||||||
|
func (i Int) Compare(a int) (float64, bool) {
|
||||||
|
return math.Abs(float64(a-int(i))) / math.Max(math.Abs(float64(a)), math.Abs(float64(i))), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Int) Value() (int, bool) { return int(i), true }
|
||||||
|
|
||||||
|
type IntExact int
|
||||||
|
|
||||||
|
func (i IntExact) Compare(a int) (float64, bool) {
|
||||||
|
if int(i) == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i IntExact) Value() (int, bool) { return int(i), true }
|
||||||
|
|
||||||
|
type IntOneOf []int
|
||||||
|
|
||||||
|
func (i IntOneOf) Compare(a int) (float64, bool) {
|
||||||
|
for _, ii := range i {
|
||||||
|
if ii == a {
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IntOneOf) Value() (int, bool) { return 0, false }
|
||||||
|
|
||||||
|
type IntRanged struct {
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
Ideal int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i IntRanged) Compare(a int) (float64, bool) {
|
||||||
|
if i.Min != 0 && i.Min > a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if i.Max != 0 && i.Max < a {
|
||||||
|
// Out of range
|
||||||
|
return 1.0, false
|
||||||
|
}
|
||||||
|
if i.Ideal == 0 {
|
||||||
|
// If the value is in the range and Ideal is not specified,
|
||||||
|
// any value is evenly acceptable.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case a == i.Ideal:
|
||||||
|
return 0.0, true
|
||||||
|
case a < i.Ideal:
|
||||||
|
if i.Min == 0 {
|
||||||
|
// If Min is not specified, smaller values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(i.Ideal-a) / float64(i.Ideal-i.Min), true
|
||||||
|
default:
|
||||||
|
if i.Max == 0 {
|
||||||
|
// If Max is not specified, larger values than Ideal are even.
|
||||||
|
return 0.0, true
|
||||||
|
}
|
||||||
|
return float64(a-i.Ideal) / float64(i.Max-i.Ideal), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IntRanged) Value() (int, bool) { return 0, false }
|
128
pkg/prop/prop.go
128
pkg/prop/prop.go
@@ -1,15 +1,18 @@
|
|||||||
package prop
|
package prop
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MediaConstraints struct {
|
||||||
|
DeviceID string
|
||||||
|
VideoConstraints
|
||||||
|
AudioConstraints
|
||||||
|
}
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
DeviceID string
|
DeviceID string
|
||||||
Video
|
Video
|
||||||
@@ -17,7 +20,7 @@ type Media struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges all the field values from o to p, except zero values.
|
// Merge merges all the field values from o to p, except zero values.
|
||||||
func (p *Media) Merge(o Media) {
|
func (p *Media) Merge(o MediaConstraints) {
|
||||||
rp := reflect.ValueOf(p).Elem()
|
rp := reflect.ValueOf(p).Elem()
|
||||||
ro := reflect.ValueOf(o)
|
ro := reflect.ValueOf(o)
|
||||||
|
|
||||||
@@ -29,9 +32,9 @@ func (p *Media) Merge(o Media) {
|
|||||||
fieldA := a.Field(i)
|
fieldA := a.Field(i)
|
||||||
fieldB := b.Field(i)
|
fieldB := b.Field(i)
|
||||||
|
|
||||||
// if a is a struct, b is also a struct. Then,
|
// if b is a struct, a is also a struct. Then,
|
||||||
// we recursively merge them
|
// we recursively merge them
|
||||||
if fieldA.Kind() == reflect.Struct {
|
if fieldB.Kind() == reflect.Struct {
|
||||||
merge(fieldA, fieldB)
|
merge(fieldA, fieldB)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -43,67 +46,122 @@ func (p *Media) Merge(o Media) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldA.Set(fieldB)
|
switch c := fieldB.Interface().(type) {
|
||||||
|
case IntConstraint:
|
||||||
|
if v, ok := c.Value(); ok {
|
||||||
|
fieldA.Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
case FloatConstraint:
|
||||||
|
if v, ok := c.Value(); ok {
|
||||||
|
fieldA.Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
case DurationConstraint:
|
||||||
|
if v, ok := c.Value(); ok {
|
||||||
|
fieldA.Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
case FrameFormatConstraint:
|
||||||
|
if v, ok := c.Value(); ok {
|
||||||
|
fieldA.Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unsupported property type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
merge(rp, ro)
|
merge(rp, ro)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Media) FitnessDistance(o Media) float64 {
|
func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
|
||||||
cmps := comparisons{}
|
cmps := comparisons{}
|
||||||
cmps.add(p.Width, o.Width)
|
cmps.add(p.Width, o.Width)
|
||||||
cmps.add(p.Height, o.Height)
|
cmps.add(p.Height, o.Height)
|
||||||
cmps.add(p.FrameFormat, o.FrameFormat)
|
cmps.add(p.FrameFormat, o.FrameFormat)
|
||||||
cmps.add(p.SampleRate, o.SampleRate)
|
cmps.add(p.SampleRate, o.SampleRate)
|
||||||
cmps.add(p.Latency, o.Latency)
|
cmps.add(p.Latency, o.Latency)
|
||||||
|
|
||||||
return cmps.fitnessDistance()
|
return cmps.fitnessDistance()
|
||||||
}
|
}
|
||||||
|
|
||||||
type comparisons map[string]string
|
type comparisons []struct {
|
||||||
|
desired, actual interface{}
|
||||||
|
}
|
||||||
|
|
||||||
func (c comparisons) add(actual, ideal interface{}) {
|
func (c *comparisons) add(desired, actual interface{}) {
|
||||||
c[fmt.Sprint(actual)] = fmt.Sprint(ideal)
|
if desired != nil {
|
||||||
|
*c = append(*c,
|
||||||
|
struct{ desired, actual interface{} }{
|
||||||
|
desired, actual,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fitnessDistance is an implementation for https://w3c.github.io/mediacapture-main/#dfn-fitness-distance
|
// fitnessDistance is an implementation for https://w3c.github.io/mediacapture-main/#dfn-fitness-distance
|
||||||
func (c comparisons) fitnessDistance() float64 {
|
func (c *comparisons) fitnessDistance() (float64, bool) {
|
||||||
var dist float64
|
var dist float64
|
||||||
|
for _, field := range *c {
|
||||||
for actual, ideal := range c {
|
var d float64
|
||||||
if actual == ideal {
|
var ok bool
|
||||||
continue
|
switch c := field.desired.(type) {
|
||||||
}
|
case IntConstraint:
|
||||||
|
if actual, typeOK := field.actual.(int); typeOK {
|
||||||
actualF, err1 := strconv.ParseFloat(actual, 64)
|
d, ok = c.Compare(actual)
|
||||||
idealF, err2 := strconv.ParseFloat(ideal, 64)
|
} else {
|
||||||
|
panic("wrong type of actual value")
|
||||||
switch {
|
}
|
||||||
// If both of the values are numeric, we need to normalize the values to get the distance
|
case FloatConstraint:
|
||||||
case err1 == nil && err2 == nil:
|
if actual, typeOK := field.actual.(float32); typeOK {
|
||||||
dist += math.Abs(actualF-idealF) / math.Max(math.Abs(actualF), math.Abs(idealF))
|
d, ok = c.Compare(actual)
|
||||||
// If both of the values are not numeric, the only comparison value is either 0 (matched) or 1 (not matched)
|
} else {
|
||||||
case err1 != nil && err2 != nil:
|
panic("wrong type of actual value")
|
||||||
if actual != ideal {
|
}
|
||||||
dist++
|
case DurationConstraint:
|
||||||
|
if actual, typeOK := field.actual.(time.Duration); typeOK {
|
||||||
|
d, ok = c.Compare(actual)
|
||||||
|
} else {
|
||||||
|
panic("wrong type of actual value")
|
||||||
|
}
|
||||||
|
case FrameFormatConstraint:
|
||||||
|
if actual, typeOK := field.actual.(frame.Format); typeOK {
|
||||||
|
d, ok = c.Compare(actual)
|
||||||
|
} else {
|
||||||
|
panic("wrong type of actual value")
|
||||||
}
|
}
|
||||||
// Comparing a numeric value with a non-numeric value is a an internal error, so panic.
|
|
||||||
default:
|
default:
|
||||||
panic("fitnessDistance can't mix comparisons.")
|
panic("unsupported constraint type")
|
||||||
|
}
|
||||||
|
dist += d
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return dist, true
|
||||||
return dist
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video represents a video's properties
|
// VideoConstraints represents a video's constraints
|
||||||
|
type VideoConstraints struct {
|
||||||
|
Width, Height IntConstraint
|
||||||
|
FrameRate FloatConstraint
|
||||||
|
FrameFormat FrameFormatConstraint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video represents a video's constraints
|
||||||
type Video struct {
|
type Video struct {
|
||||||
Width, Height int
|
Width, Height int
|
||||||
FrameRate float32
|
FrameRate float32
|
||||||
FrameFormat frame.Format
|
FrameFormat frame.Format
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio represents an audio's properties
|
// AudioConstraints represents an audio's constraints
|
||||||
|
type AudioConstraints struct {
|
||||||
|
ChannelCount IntConstraint
|
||||||
|
Latency DurationConstraint
|
||||||
|
SampleRate IntConstraint
|
||||||
|
SampleSize IntConstraint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio represents an audio's constraints
|
||||||
type Audio struct {
|
type Audio struct {
|
||||||
ChannelCount int
|
ChannelCount int
|
||||||
Latency time.Duration
|
Latency time.Duration
|
||||||
|
@@ -2,8 +2,138 @@ package prop
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestCompareMatch(t *testing.T) {
|
||||||
|
testDataSet := map[string]struct {
|
||||||
|
a MediaConstraints
|
||||||
|
b Media
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
"IntIdealUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: Int(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 50,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"IntIdealMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: Int(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 30,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"IntExactUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: IntExact(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 50,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"IntExactMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: IntExact(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 30,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"IntRangeUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: IntRanged{Min: 30, Max: 40},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 50,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"IntRangeMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
Width: IntRanged{Min: 30, Max: 40},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
Width: 35,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FrameFormatOneOfUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameFormat: frame.FormatYUYV,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FrameFormatOneOfMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameFormat: frame.FormatMJPEG,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"DurationExactUnmatch": {
|
||||||
|
MediaConstraints{AudioConstraints: AudioConstraints{
|
||||||
|
Latency: DurationExact(time.Second),
|
||||||
|
}},
|
||||||
|
Media{Audio: Audio{
|
||||||
|
Latency: time.Second + time.Millisecond,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"DurationExactMatch": {
|
||||||
|
MediaConstraints{AudioConstraints: AudioConstraints{
|
||||||
|
Latency: DurationExact(time.Second),
|
||||||
|
}},
|
||||||
|
Media{Audio: Audio{
|
||||||
|
Latency: time.Second,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"DurationRangedUnmatch": {
|
||||||
|
MediaConstraints{AudioConstraints: AudioConstraints{
|
||||||
|
Latency: DurationRanged{Max: time.Second},
|
||||||
|
}},
|
||||||
|
Media{Audio: Audio{
|
||||||
|
Latency: time.Second + time.Millisecond,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"DurationRangedMatch": {
|
||||||
|
MediaConstraints{AudioConstraints: AudioConstraints{
|
||||||
|
Latency: DurationRanged{Max: time.Second},
|
||||||
|
}},
|
||||||
|
Media{Audio: Audio{
|
||||||
|
Latency: time.Millisecond,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testData := range testDataSet {
|
||||||
|
testData := testData
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
_, match := testData.a.FitnessDistance(testData.b)
|
||||||
|
if match != testData.match {
|
||||||
|
t.Errorf("matching flag differs, expected: %v, got: %v", testData.match, match)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMergeWithZero(t *testing.T) {
|
func TestMergeWithZero(t *testing.T) {
|
||||||
a := Media{
|
a := Media{
|
||||||
Video: Video{
|
Video: Video{
|
||||||
@@ -11,9 +141,9 @@ func TestMergeWithZero(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b := Media{
|
b := MediaConstraints{
|
||||||
Video: Video{
|
VideoConstraints: VideoConstraints{
|
||||||
Height: 100,
|
Height: Int(100),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +165,9 @@ func TestMergeWithSameField(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b := Media{
|
b := MediaConstraints{
|
||||||
Video: Video{
|
VideoConstraints: VideoConstraints{
|
||||||
Width: 100,
|
Width: Int(100),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,9 +191,9 @@ func TestMergeNested(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b := Media{
|
b := MediaConstraints{
|
||||||
Video: Video{
|
VideoConstraints: VideoConstraints{
|
||||||
Width: 100,
|
Width: Int(100),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
track.go
16
track.go
@@ -1,7 +1,7 @@
|
|||||||
package mediadevices
|
package mediadevices
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -62,11 +62,11 @@ func newTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrack
|
|||||||
case driver.AudioRecorder:
|
case driver.AudioRecorder:
|
||||||
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeAudio]
|
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeAudio]
|
||||||
buildSampler = func(t LocalTrack) samplerFunc {
|
buildSampler = func(t LocalTrack) samplerFunc {
|
||||||
return newAudioSampler(t, constraints.Latency)
|
return newAudioSampler(t, constraints.selectedMedia.Latency)
|
||||||
}
|
}
|
||||||
encoderBuilders, err = newAudioEncoderBuilders(r, constraints)
|
encoderBuilders, err = newAudioEncoderBuilders(r, constraints)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("newTrack: invalid driver type")
|
err = errors.New("newTrack: invalid driver type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,7 +114,7 @@ func newTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrack
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.Close()
|
d.Close()
|
||||||
return nil, fmt.Errorf("newTrack: failed to find a matching codec")
|
return nil, errors.New("newTrack: failed to find a matching codec")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnEnded sets an error handler. When a track has been created and started, if an
|
// OnEnded sets an error handler. When a track has been created and started, if an
|
||||||
@@ -196,7 +196,7 @@ type encoderBuilder struct {
|
|||||||
// newVideoEncoderBuilders transforms video given by VideoRecorder with the video transformer that is passed through
|
// newVideoEncoderBuilders transforms video given by VideoRecorder with the video transformer that is passed through
|
||||||
// constraints and create a list of generic encoder builders
|
// constraints and create a list of generic encoder builders
|
||||||
func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
||||||
r, err := vr.VideoRecord(constraints.Media)
|
r, err := vr.VideoRecord(constraints.selectedMedia)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackCons
|
|||||||
for i, b := range constraints.VideoEncoderBuilders {
|
for i, b := range constraints.VideoEncoderBuilders {
|
||||||
encoderBuilders[i].name = b.Name()
|
encoderBuilders[i].name = b.Name()
|
||||||
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
||||||
return b.BuildVideoEncoder(r, constraints.Media)
|
return b.BuildVideoEncoder(r, constraints.selectedMedia)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encoderBuilders, nil
|
return encoderBuilders, nil
|
||||||
@@ -218,7 +218,7 @@ func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackCons
|
|||||||
// newAudioEncoderBuilders transforms audio given by AudioRecorder with the audio transformer that is passed through
|
// newAudioEncoderBuilders transforms audio given by AudioRecorder with the audio transformer that is passed through
|
||||||
// constraints and create a list of generic encoder builders
|
// constraints and create a list of generic encoder builders
|
||||||
func newAudioEncoderBuilders(ar driver.AudioRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
func newAudioEncoderBuilders(ar driver.AudioRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
||||||
r, err := ar.AudioRecord(constraints.Media)
|
r, err := ar.AudioRecord(constraints.selectedMedia)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ func newAudioEncoderBuilders(ar driver.AudioRecorder, constraints MediaTrackCons
|
|||||||
for i, b := range constraints.AudioEncoderBuilders {
|
for i, b := range constraints.AudioEncoderBuilders {
|
||||||
encoderBuilders[i].name = b.Name()
|
encoderBuilders[i].name = b.Name()
|
||||||
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
||||||
return b.BuildAudioEncoder(r, constraints.Media)
|
return b.BuildAudioEncoder(r, constraints.selectedMedia)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encoderBuilders, nil
|
return encoderBuilders, nil
|
||||||
|
Reference in New Issue
Block a user