Repository: pion/mediadevices
Pull request #386 (2022-03-21 02:01:49)

Update module github.com/pion/rtp to v1.7.9
[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/pion/rtp](https://togithub.com/pion/rtp) | require | patch | `v1.7.4` -> `v1.7.9` |

---

### Release Notes

<details>
<summary>pion/rtp</summary>

### [`v1.7.9`](https://togithub.com/pion/rtp/compare/v1.7.8...v1.7.9)

[Compare Source](https://togithub.com/pion/rtp/compare/v1.7.8...v1.7.9)

### [`v1.7.8`](https://togithub.com/pion/rtp/compare/v1.7.7...v1.7.8)

[Compare Source](https://togithub.com/pion/rtp/compare/v1.7.7...v1.7.8)

### [`v1.7.7`](https://togithub.com/pion/rtp/compare/v1.7.6...v1.7.7)

[Compare Source](https://togithub.com/pion/rtp/compare/v1.7.6...v1.7.7)

### [`v1.7.6`](https://togithub.com/pion/rtp/compare/v1.7.5...v1.7.6)

[Compare Source](https://togithub.com/pion/rtp/compare/v1.7.5...v1.7.6)

### [`v1.7.5`](https://togithub.com/pion/rtp/compare/v1.7.4...v1.7.5)

[Compare Source](https://togithub.com/pion/rtp/compare/v1.7.4...v1.7.5)

</details>

---

### Configuration

📅 **Schedule**: "before 3am on Monday" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/pion/mediadevices).

codecov[bot]: # [Codecov](https://codecov.io/gh/pion/mediadevices/pull/386?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) Report
> Merging [#386](https://codecov.io/gh/pion/mediadevices/pull/386?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (a2399f8) into [master](https://codecov.io/gh/pion/mediadevices/commit/548cdac66865c70d22347eef7a8e342a08739825?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (548cdac) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master     #386   +/-   ##
=======================================
  Coverage   47.16%   47.16%           
=======================================
  Files          67       67           
  Lines        4380     4380           
=======================================
  Hits         2066     2066           
  Misses       2189     2189           
  Partials      125      125           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/mediadevices/pull/386?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/mediadevices/pull/386?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Last update [548cdac...a2399f8](https://codecov.io/gh/pion/mediadevices/pull/386?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).


Patch:
diff --git a/go.mod b/go.mod
index c25d391..36e97a5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,13 @@ module github.com/pion/mediadevices
 go 1.13
 
 require (
-	github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
+	github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
 	github.com/gen2brain/malgo v0.10.35
 	github.com/google/uuid v1.3.0
 	github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
 	github.com/pion/logging v0.2.2
-	github.com/pion/rtp v1.7.4
+	github.com/pion/rtp v1.7.9
 	github.com/pion/webrtc/v3 v3.1.10
-	golang.org/x/image v0.0.0-20220302094943-723b81ca9867
+	golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 )
diff --git a/go.sum b/go.sum
index d943dd9..3b70ebc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
-github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
+github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
+github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -65,8 +65,9 @@ github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
 github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
 github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
 github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
-github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
 github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
+github.com/pion/rtp v1.7.9 h1:17W5Mt2IM3MVfOh7yRfzXbbKXYzBZxV8eG4KKAy+0bg=
+github.com/pion/rtp v1.7.9/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
 github.com/pion/sctp v1.8.0 h1:6erMF2qmQwXr+0iB1lm0AUSmDr9LdmpaBzgSVAEgehw=
 github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
 github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
@@ -100,8 +101,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4=
-golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg=
+golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
diff --git a/pkg/avfoundation/avfoundation_darwin.go b/pkg/avfoundation/avfoundation_darwin.go
index b42e6db..ea6caa9 100644
--- a/pkg/avfoundation/avfoundation_darwin.go
+++ b/pkg/avfoundation/avfoundation_darwin.go
@@ -11,6 +11,7 @@ package avfoundation
 // }
 import "C"
 import (
+	"context"
 	"fmt"
 	"io"
 	"unsafe"
@@ -93,9 +94,11 @@ func Devices(mediaType MediaType) ([]Device, error) {
 // ReadCloser is a wrapper around the data callback from AVFoundation. The data received from the
 // the underlying callback can be retrieved by calling Read.
 type ReadCloser struct {
-	dataChan chan []byte
-	id       handleID
-	onClose  func()
+	dataChan   chan []byte
+	id         handleID
+	onClose    func()
+	cancelCtx  context.Context
+	cancelFunc func()
 }
 
 func newReadCloser(onClose func()) *ReadCloser {
@@ -103,12 +106,22 @@ func newReadCloser(onClose func()) *ReadCloser {
 	rc.dataChan = make(chan []byte, 1)
 	rc.onClose = onClose
 	rc.id = register(rc.dataCb)
+	cancelCtx, cancelFunc := context.WithCancel(context.Background())
+	rc.cancelCtx = cancelCtx
+	rc.cancelFunc = cancelFunc
 	return &rc
 }
 
 func (rc *ReadCloser) dataCb(data []byte) {
 	// TODO: add a policy for slow reader
-	rc.dataChan <- data
+	if rc.cancelCtx.Err() != nil {
+		return
+	}
+	select {
+	case <-rc.cancelCtx.Done():
+		close(rc.dataChan)
+	case rc.dataChan <- data:
+	}
 }
 
 // Read reads raw data, the format is determined by the media type and property:
@@ -127,7 +140,7 @@ func (rc *ReadCloser) Close() {
 	if rc.onClose != nil {
 		rc.onClose()
 	}
-	close(rc.dataChan)
+	rc.cancelFunc()
 	unregister(rc.id)
 }
 
diff --git a/pkg/driver/camera/camera_darwin.go b/pkg/driver/camera/camera_darwin.go
index 07d0962..3db1537 100644
--- a/pkg/driver/camera/camera_darwin.go
+++ b/pkg/driver/camera/camera_darwin.go
@@ -13,6 +13,7 @@ import (
 type camera struct {
 	device  avfoundation.Device
 	session *avfoundation.Session
+	rcClose func()
 }
 
 func init() {
@@ -43,6 +44,9 @@ func (cam *camera) Open() error {
 }
 
 func (cam *camera) Close() error {
+	if cam.rcClose != nil {
+		cam.rcClose()
+	}
 	return cam.session.Close()
 }
 
@@ -56,6 +60,7 @@ func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) {
 	if err != nil {
 		return nil, err
 	}
+	cam.rcClose = rc.Close
 	r := video.ReaderFunc(func() (image.Image, func(), error) {
 		frame, _, err := rc.Read()
 		if err != nil {
diff --git a/pkg/driver/camera/camera_linux.go b/pkg/driver/camera/camera_linux.go
index 9e0f375..1b04f04 100644
--- a/pkg/driver/camera/camera_linux.go
+++ b/pkg/driver/camera/camera_linux.go
@@ -179,6 +179,13 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
 		return nil, err
 	}
 
+	if p.FrameRate > 0 {
+		err = c.cam.SetFramerate(float32(p.FrameRate))
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	if err := c.cam.StartStreaming(); err != nil {
 		return nil, err
 	}
diff --git a/pkg/prop/prop.go b/pkg/prop/prop.go
index 26e4a3f..90edde7 100644
--- a/pkg/prop/prop.go
+++ b/pkg/prop/prop.go
@@ -145,6 +145,7 @@ func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
 	cmps.add(p.Width, o.Width)
 	cmps.add(p.Height, o.Height)
 	cmps.add(p.FrameFormat, o.FrameFormat)
+	cmps.add(p.FrameRate, o.FrameRate)
 	cmps.add(p.SampleRate, o.SampleRate)
 	cmps.add(p.Latency, o.Latency)
 	cmps.add(p.ChannelCount, o.ChannelCount)


Test patch:
diff --git a/mediadevices_test.go b/mediadevices_test.go
index 28a8682..f5024a8 100644
--- a/mediadevices_test.go
+++ b/mediadevices_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/pion/mediadevices/pkg/driver"
 	_ "github.com/pion/mediadevices/pkg/driver/audiotest"
 	_ "github.com/pion/mediadevices/pkg/driver/videotest"
+	"github.com/pion/mediadevices/pkg/frame"
 	"github.com/pion/mediadevices/pkg/prop"
 )
 
@@ -97,37 +98,163 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
 		t.Fatal("expect to get at least 1 property")
 	}
 	expectedProp := driver.Properties()[0]
-	// Since this is a continuous value, bestConstraints should be set with the value that user specified
-	expectedProp.FrameRate = 30.0
-
-	wantConstraints := MediaTrackConstraints{
-		MediaConstraints: prop.MediaConstraints{
-			VideoConstraints: prop.VideoConstraints{
-				// By reducing the width from the driver by a tiny amount, this property should be chosen.
-				// At the same time, we'll be able to find out if the return constraints will be properly set
-				// to the best constraints.
-				Width:       prop.Int(expectedProp.Width - 1),
-				Height:      prop.Int(expectedProp.Width),
-				FrameFormat: prop.FrameFormat(expectedProp.FrameFormat),
-				FrameRate:   prop.Float(30.0),
-			},
+
+	// By reducing the value from the driver by a tiny amount, this property should be chosen.
+	// At the same time, we'll be able to find out if the return constraints will be properly set
+	// to the best constraints.
+	cases := map[string]struct {
+		width, height int
+		frameFormat   frame.Format
+		frameRate     float32
+	}{
+		"DifferentWidth": {
+			width:       expectedProp.Width - 1,
+			height:      expectedProp.Height,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate,
+		},
+		"DifferentHeight": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height - 1,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate,
+		},
+		"DifferentFrameFormat": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height,
+			frameFormat: frame.FormatI420,
+			frameRate:   expectedProp.FrameRate,
 		},
+		"DifferentFrameRate": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate - 1,
+		},
+		"NoFrameRateConstraints": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   -1,
+		},
+	}
+
+	for name, c := range cases {
+		c := c
+		t.Run(name, func(t *testing.T) {
+			var vc prop.VideoConstraints
+
+			if c.frameRate >= 0 {
+				vc = prop.VideoConstraints{
+					Width:       prop.Int(c.width),
+					Height:      prop.Int(c.height),
+					FrameFormat: prop.FrameFormat(c.frameFormat),
+					FrameRate:   prop.Float(c.frameRate),
+				}
+			} else {
+				// do not specify the framerate
+				vc = prop.VideoConstraints{
+					Width:       prop.Int(c.width),
+					Height:      prop.Int(c.height),
+					FrameFormat: prop.FrameFormat(c.frameFormat),
+				}
+			}
+			wantConstraints := MediaTrackConstraints{
+				MediaConstraints: prop.MediaConstraints{
+					VideoConstraints: vc,
+				},
+			}
+
+			bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if driver != bestDriver {
+				t.Fatal("best driver is not expected")
+			}
+
+			s := bestConstraints.selectedMedia
+			if s.Width != expectedProp.Width ||
+				s.Height != expectedProp.Height ||
+				s.FrameFormat != expectedProp.FrameFormat ||
+				s.FrameRate != expectedProp.FrameRate {
+				t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
+			}
+
+		})
 	}
 
-	bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
+}
+
+func TestSelectBestDriverConstraintsNoFit(t *testing.T) {
+	filterFn := driver.FilterVideoRecorder()
+	drivers := driver.GetManager().Query(filterFn)
+	if len(drivers) == 0 {
+		t.Fatal("expect to get at least 1 driver")
+	}
+
+	driver := drivers[0]
+	err := driver.Open()
 	if err != nil {
-		t.Fatal(err)
+		t.Fatal("expect to open driver successfully")
 	}
+	defer driver.Close()
 
-	if driver != bestDriver {
-		t.Fatal("best driver is not expected")
+	if len(driver.Properties()) == 0 {
+		t.Fatal("expect to get at least 1 property")
 	}
+	expectedProp := driver.Properties()[0]
 
-	s := bestConstraints.selectedMedia
-	if s.Width != expectedProp.Width ||
-		s.Height != expectedProp.Height ||
-		s.FrameFormat != expectedProp.FrameFormat ||
-		s.FrameRate != expectedProp.FrameRate {
-		t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
+	cases := map[string]struct {
+		width, height int
+		frameFormat   frame.Format
+		frameRate     float32
+	}{
+		"DifferentWidth": {
+			width:       expectedProp.Width - 1,
+			height:      expectedProp.Height,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate,
+		},
+		"DifferentHeight": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height - 1,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate,
+		},
+		"DifferentFrameFormat": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height,
+			frameFormat: frame.FormatI420,
+			frameRate:   expectedProp.FrameRate,
+		},
+		"DifferentFrameRate": {
+			width:       expectedProp.Width,
+			height:      expectedProp.Height,
+			frameFormat: expectedProp.FrameFormat,
+			frameRate:   expectedProp.FrameRate - 1,
+		},
+	}
+
+	for name, c := range cases {
+		c := c
+		t.Run(name, func(t *testing.T) {
+			wantConstraints := MediaTrackConstraints{
+				MediaConstraints: prop.MediaConstraints{
+					VideoConstraints: prop.VideoConstraints{
+						Width:       prop.IntExact(c.width),
+						Height:      prop.IntExact(c.height),
+						FrameFormat: prop.FrameFormatExact(c.frameFormat),
+						FrameRate:   prop.FloatExact(c.frameRate),
+					},
+				},
+			}
+
+			_, _, err := selectBestDriver(filterFn, wantConstraints)
+			if err == nil {
+				t.Fatal("expect to not find a driver that fits the constraints")
+			}
+		})
 	}
 }
diff --git a/pkg/driver/videotest/dummy.go b/pkg/driver/videotest/dummy.go
index 8619cee..0728b12 100644
--- a/pkg/driver/videotest/dummy.go
+++ b/pkg/driver/videotest/dummy.go
@@ -47,10 +47,6 @@ func (d *dummy) Close() error {
 }
 
 func (d *dummy) VideoRecord(p prop.Media) (video.Reader, error) {
-	if p.FrameRate == 0 {
-		p.FrameRate = 30
-	}
-
 	colors := [][3]byte{
 		{235, 128, 128},
 		{210, 16, 146},
@@ -143,6 +139,7 @@ func (d dummy) Properties() []prop.Media {
 				Width:       640,
 				Height:      480,
 				FrameFormat: frame.FormatYUYV,
+				FrameRate:   30,
 			},
 		},
 	}