Compare commits

...

149 Commits

Author SHA1 Message Date
Sean Pollock
8fb8d65764 Add webcam close to linux camera discover (#568)
Add webcam close to linux discover
2024-04-30 10:14:26 -04:00
qiulin
ae63fa65bf Remove Windows camera cpp inline function (#569)
Co-authored-by: Qiulin Li <liqiulin@outlook.com>
2024-04-29 09:54:45 -04:00
Eric Daniels
23d77e2bb2 update to blackjack/webcam to v0.6.0 (#567) 2024-04-26 15:37:33 -04:00
renovate[bot]
02d4e0e896 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.29 (#559)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-21 19:19:46 -04:00
renovate[bot]
38a3b829f6 fix(deps): update module github.com/pion/rtcp to v1.2.14 (#557)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 12:19:08 +09:00
renovate[bot]
c4bb9eb649 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.28 (#555)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 12:01:54 +09:00
renovate[bot]
bc3bdc1855 fix(deps): update module github.com/stretchr/testify to v1.9.0 (#558)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 11:48:51 +09:00
renovate[bot]
a6ffeb31ab fix(deps): update module github.com/google/uuid to v1.6.0 (#553)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-04 11:47:33 +09:00
sean yu
a9046e6ac1 Add fix that strips unexpected padding for NV12 frames (#556) 2024-03-01 12:23:05 -05:00
Eric Daniels
2bc011f39f Update malgo to v0.11.21 for a macOS microphone fix (#551) 2024-01-24 09:48:40 -05:00
renovate[bot]
68df5b3eb5 chore(deps): update actions/cache action to v4 (#549)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-22 09:37:20 +09:00
renovate[bot]
bf52b1af58 fix(deps): update module golang.org/x/image to v0.15.0 (#548)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 12:03:19 +09:00
renovate[bot]
ccee17d04c fix(deps): update module github.com/pion/webrtc/v3 to v3.2.24 (#546)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 11:29:22 +09:00
renovate[bot]
f4f5a24ce4 fix(deps): update module github.com/pion/rtcp to v1.2.13 (#540)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 11:28:59 +09:00
renovate[bot]
dec2300a95 fix(deps): update module github.com/google/uuid to v1.5.0 (#547)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 11:28:35 +09:00
Sean DuBois
0c8f3cfc7a Remove 'AUTHORS.txt' from README.md
Relates to pion/.goassets#185
2024-01-02 12:39:28 -05:00
renovate[bot]
6829d71e58 chore(deps): update actions/setup-go action to v5 (#544)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-11 10:28:42 +09:00
renovate[bot]
1b36c0360d fix(deps): update module github.com/pion/webrtc/v3 to v3.2.23 (#542)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 10:50:35 +09:00
renovate[bot]
5aa157e8fc fix(deps): update module golang.org/x/image to v0.14.0 (#543)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 10:49:56 +09:00
renovate[bot]
cd8e34f3ed fix(deps): update module github.com/pion/interceptor to v0.1.25 (#534)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 10:23:25 +09:00
renovate[bot]
d03383a6fd fix(deps): update module github.com/google/uuid to v1.4.0 (#536)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 10:20:40 +09:00
Eric Daniels
c4fd1de9a0 Upgrade blackjack/webcam to v0.5.0 (#538)
See https://github.com/blackjack/webcam/issues/65
2023-11-05 20:11:56 -05:00
renovate[bot]
47638e3290 fix(deps): update module golang.org/x/image to v0.13.0 (#537)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 09:52:25 +09:00
hexbabe
09b497727d Removed 'unreachable' else initialization and replaced with error indicating a wtf 2023-10-31 14:48:49 -04:00
hexbabe
7ae82fbda7 Remove old unnecessary macro 2023-10-31 14:48:49 -04:00
hexbabe
4477898296 Use new macro and also use @ available 2023-10-31 14:48:49 -04:00
hexbabe
d5d41e9ca5 Switch to what I think is the correct preprocessor method 2023-10-31 14:48:49 -04:00
hexbabe
9fb24fb036 Use identifier conditional compilation (hackier) but might work and is still decently readable 2023-10-31 14:48:49 -04:00
hexbabe
1cd1b136cc This @available compile time directive really should work... confused 2023-10-31 14:48:49 -04:00
hexbabe
08fb3e8a48 Change code conditional to conditional compilation directive to avoid CI failure 2023-10-31 14:48:49 -04:00
hexbabe
4aae1bc842 Add conditional to choose types based on pre vs. post sonoma update 2023-10-31 14:48:49 -04:00
seanavery
3c9fee958e Add manager clearing mechanism 2023-10-17 16:41:33 -04:00
Clyde Bazile
68092afe36 Go mod tidy (#533) 2023-10-17 09:49:06 +09:00
Atsushi Watanabe
cd92becd1c Bump minimum supported Go version to 1.19 (#530)
atomic.Bool is used since 2882fd4
2023-10-10 23:52:39 +09:00
renovate[bot]
2bad953124 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.21 (#524)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-10 11:21:32 +09:00
renovate[bot]
634addc04d fix(deps): update module github.com/pion/interceptor to v0.1.22 (#531)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-10 10:59:31 +09:00
Atsushi Watanabe
d0c1677cfb Update CI Go versions to 20 and 21 (#529) 2023-10-03 13:02:33 +09:00
renovate[bot]
b4770e5fbf fix(deps): update module github.com/pion/interceptor to v0.1.20 (#528)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-02 12:44:13 +09:00
renovate[bot]
855441dad1 fix(deps): update module golang.org/x/image to v0.12.0 (#527)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-02 12:27:51 +09:00
Valentin Cocaud
2882fd42d5 use thread safe atomic bool 2023-09-20 15:46:33 -04:00
Valentin Cocaud
694b4abd83 fix ForceKeyFrame signature for VP8 2023-09-20 15:46:33 -04:00
Valentin Cocaud
afb2f78e3c force key frame implementation for vaapi 2023-09-20 15:46:33 -04:00
renovate[bot]
b14ce7987c fix(deps): update module github.com/pion/interceptor to v0.1.19 (#523)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 09:34:49 +09:00
renovate[bot]
728207526d chore(deps): update actions/checkout action to v4 (#525)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 09:34:13 +09:00
renovate[bot]
3582e5d017 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.17 (#516)
* fix(deps): update module github.com/pion/webrtc/v3 to v3.2.17
* Update test to address API change

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2023-09-01 11:00:12 +09:00
renovate[bot]
774b7de8d2 fix(deps): update github.com/kbinani/screenshot digest to b87d318 (#520)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:38:09 +09:00
renovate[bot]
96efc0932e fix(deps): update module github.com/google/uuid to v1.3.1 (#521)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:14:46 +09:00
renovate[bot]
a1b4c0f69a fix(deps): update module golang.org/x/image to v0.11.0 (#522)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:14:26 +09:00
renovate[bot]
6bba5f1663 fix(deps): update module golang.org/x/image to v0.10.0 (#519)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-02 10:26:38 +09:00
renovate[bot]
79aef00e07 fix(deps): update module github.com/pion/rtp to v1.8.1 (#518)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-02 10:26:07 +09:00
seanavery
03c44ee803 Remove extra comment 2023-07-24 12:07:38 -04:00
seanavery
dad145ef11 Add props even if framerate is not supported 2023-07-24 12:07:38 -04:00
Atsushi Watanabe
72c1d7bb89 Fix invalid memory access in ScalerFastBoxSampling (#513)
[src-px]->[dst-px] of 4x1px to 2x1px scaling was
  0->0, 1->1, 2->1, 3->2(out of bounds)
Fix it to be
  0->0, 1->0, 2->1, 3->1
2023-07-05 12:01:40 +09:00
Atsushi Watanabe
fc301a8a92 Cache local brew taps on self-hosted darwin runner (#514) 2023-07-05 12:01:26 +09:00
renovate[bot]
aca3ee9126 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.11
Generated by Renovate Bot
2023-07-05 11:19:55 +09:00
renovate[bot]
4924411e88 fix(deps): update github.com/blackjack/webcam digest to 87693b3
Generated by Renovate Bot
2023-07-05 10:19:20 +09:00
renovate[bot]
b88f541c4f fix(deps): update module golang.org/x/image to v0.8.0
Generated by Renovate Bot
2023-07-05 10:18:08 +09:00
aljanabim
09f6bdeac4 Added Name in driver.Info to Microphone driver 2023-06-12 11:41:34 +09:00
Ryan Boring
e64f0d8697 Replace YUY2 with YUYV for Mac 2023-06-05 12:37:57 -04:00
Ryan Boring
36f908c6e2 Fix stale links 2023-06-05 12:37:57 -04:00
Ryan Boring
9987e01d3f Add YUYV to supported video formats 2023-06-05 12:37:57 -04:00
renovate[bot]
ae173b1b61 fix(deps): update module github.com/stretchr/testify to v1.8.4
Generated by Renovate Bot
2023-06-05 12:04:03 +09:00
Alex Harvey
4eea55285e Update README.md to include mention of GetDisplayMedia
Added to the FAQ to mention GetDisplayMedia if using driver/screen in the section explaining solutions to the error that gets throne if you don't make that swap in example code.
2023-06-05 12:01:48 +09:00
renovate[bot]
18cf1fe38a fix(deps): update module github.com/pion/webrtc/v3 to v3.2.6
Generated by Renovate Bot
2023-05-22 18:15:33 +09:00
seanavery
138499b52d Support fps props for linux cams 2023-05-16 14:25:02 -04:00
renovate[bot]
87486146d5 fix(deps): update module github.com/pion/interceptor to v0.1.16
Generated by Renovate Bot
2023-05-05 18:46:45 +02:00
Clyde Bazile
cadb155755 update test 2023-04-24 11:14:58 -04:00
Clyde Bazile
2372f55064 comments: var block + interface-less error 2023-04-24 11:14:58 -04:00
Clyde Bazile
8568b1b20d do not change other classes 2023-04-24 11:14:58 -04:00
Steffen Vogel
f0f6be7350 Fix README section levels 2023-04-18 12:12:53 +02:00
Steffen Vogel
8146e84f2f Harmonize sections in README 2023-04-18 12:12:53 +02:00
Steffen Vogel
4ff24bd656 Harmonize section levels 2023-04-18 12:12:53 +02:00
Steffen Vogel
64dbe507f0 Rephrase relation to webrtc repo 2023-04-18 12:12:53 +02:00
Steffen Vogel
dffaf0fcb4 Harmonize RFC section and links 2023-04-18 12:12:53 +02:00
Steffen Vogel
d3adaeea1a Cleanup common sections in README 2023-04-18 12:12:53 +02:00
Steffen Vogel
7a414948c6 Cleanup badges in README 2023-04-18 12:12:53 +02:00
renovate[bot]
e1616b8cc2 Update module golang.org/x/image to v0.7.0 (#489)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-18 13:10:51 +09:00
Kim Mishra
52a080b55a add link to comment 2023-04-17 23:51:01 -04:00
Kim Mishra
c2fe66c579 t.Errorf -> t.Fatalf 2023-04-17 23:51:01 -04:00
Kim Mishra
ac50077e77 addressing PR reviews 2023-04-17 23:51:01 -04:00
kim-mishra
2f5c61e1f3 Update compressed.go 2023-04-17 23:51:01 -04:00
Kim Mishra
0715258726 check if error is format error 2023-04-17 23:51:01 -04:00
Kim Mishra
bccff100e5 addressing PR reviews 2023-04-17 23:51:01 -04:00
Kim Mishra
0507093a59 check if huffman table error occurred, and if so add in necessary data 2023-04-17 23:51:01 -04:00
Abdarrakhman Akhmetgali
bf290b026c fix i422ToI420 calculation, add mock FastBoxSampling 2023-04-15 08:19:04 -04:00
Abdarrakhman Akhmetgali
0dc4f43c94 fix cLen in nocgo implementation of i444Toi420 2023-04-15 08:19:04 -04:00
Clyde Bazile
14bfaa5dbd Update pkg/driver/camera/camera_linux.go
Co-authored-by: Eric Daniels <eric@erdaniels.com>
2023-04-12 14:07:14 -04:00
Clyde Bazile
09c31a264c expose device name and bus info 2023-04-12 14:07:14 -04:00
renovate[bot]
30badd819d Update module github.com/pion/webrtc/v3 to v3.1.59
Generated by Renovate Bot
2023-04-06 22:50:50 -04:00
martha-johnston
dc8aeea11f remove name from linux driver info 2023-03-31 16:54:09 -04:00
martha-johnston
57c9ba0fc5 reveal Name var for driver.info 2023-03-31 16:54:09 -04:00
Clyde Bazile
b9ce5bb861 change buffer size to 2 (#482) 2023-03-29 17:42:58 -04:00
Clyde Bazile
e4ac96ea6b Handle race conditions + add test for Manager.Register (#485) 2023-03-27 10:13:23 +09:00
renovate[bot]
11bf55f80c Update actions/setup-go action to v4 (#483)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 09:32:50 +09:00
martha-johnston
55881ddd41 When finding cameras on linux systems, look at camera ID before path or video* (#476) 2023-03-07 15:58:22 -05:00
Clyde Bazile
62009a882b discard buffered frames for < 1fps (#470) 2023-03-06 16:52:59 -05:00
Abdarrakhman Akhmetgali
dbd37689e4 Optimize ToI420 conversion by using sync.Pool (#473)
Added sync.Pool to the i420 conversion to minimize overhead of creating new byte slices
2023-03-06 15:53:44 -05:00
renovate[bot]
d561715bf9 Update module golang.org/x/image to v0.5.0 [SECURITY] (#472)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-03 10:51:29 -05:00
renovate[bot]
76ba048312 Update module golang.org/x/image to v0.3.0 (#469)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-01 13:51:26 +09:00
Kyle
5da0ebf443 Fix deadlock in track.Bind() (#466)
Occurs when read errors happen from a
driver source during a call to track.Bind()
2023-01-23 10:00:20 +09:00
renovate[bot]
f8f8511d94 Update module github.com/pion/webrtc/v3 to v3.1.50 (#456)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 09:03:47 +09:00
Clyde Bazile
85597da5bb Expose device list update func (#463) 2023-01-14 09:02:46 +09:00
renovate[bot]
6ac1424488 Update module golang.org/x/image to v0.2.0 (#462)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-04 12:31:41 +09:00
Denys Malykhin
73a158d097 Fix for preventing close of stopRead nil channel (#458)
* Fix for preventing close of stopRead nil channel

* Prevent use of stopRead before assignment
2022-12-19 14:05:19 -05:00
Eric Daniels
cb23f1fa82 upgrade malgo to v0.11.10 (#455) 2022-12-05 16:19:47 -05:00
renovate[bot]
a0f090dced Update module github.com/pion/webrtc/v3 to v3.1.48 (#444)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-19 00:13:24 +09:00
Atsushi Watanabe
4f7542b614 Add CI for M1 Mac (#454) 2022-11-19 00:00:03 +09:00
renovate[bot]
860f4a1490 Update module golang.org/x/image to v0.1.0 (#451)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-18 11:50:42 +09:00
renovate[bot]
09f3bcc013 Update module github.com/esimov/pigo to v1.4.6 (#446)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-12 23:19:56 +09:00
renovate[bot]
f0ad407da8 Update golang.org/x/image digest to e7cb969 (#445)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-12 23:18:55 +09:00
Atsushi Watanabe
8d7ccada1c Run CI on Go 1.18 and 1.19 (#449) 2022-10-12 23:11:22 +09:00
Atsushi Watanabe
2d9208de5b Disable updating minimum Go version in go.mod (#448) 2022-10-12 22:51:12 +09:00
Atsushi Watanabe
52cf6e72b1 Skip vaapi test on CI (#447)
DRI device appears on GitHub Actions runner but unusable.
Skip pkg/codec/vaapi test on CI.
2022-10-12 22:41:36 +09:00
代码人生
7335797301 Fix for having multiple cameras in Windows (#443) 2022-09-21 18:43:59 +08:00
Eric Daniels
3bec69bbf8 Utilize audio property latency in microphone AudioRecord (#439) 2022-08-24 07:56:55 -04:00
Atsushi Watanabe
58dc90d03a Fix opus codec panic on read after close (#440)
Also add test to other codecs
2022-08-24 07:23:57 -04:00
Valentin Cocaud
8ad810e61e fix PLI and FIR handling, wrongly triggering track.OnEnded (#420) 2022-08-16 11:13:33 +02:00
Eric Daniels
6f204fa3d1 Properly close microphone before malgo chunk channel (#436) 2022-08-09 09:17:47 -04:00
Eric Daniels
5215057409 Update libopus static library to correct arch for darwin arm64 (#428) 2022-08-07 09:01:29 -04:00
renovate[bot]
3bcbed0286 Update module github.com/esimov/pigo to v1.4.5 (#430)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-07 14:46:54 +09:00
renovate[bot]
907e0d68e2 Update module github.com/pion/webrtc/v3 to v3.1.43 (#432)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-07 14:40:22 +09:00
Atsushi Watanabe
9fd2d01dbe Ignore self dependency from examples (#433) 2022-08-07 14:39:43 +09:00
Atsushi Watanabe
285f8cd23c Do not ignore dependencies under examples (#429)
Update go.mod under examples by Renovate.
2022-08-07 14:30:49 +09:00
Sean DuBois
b309c30ca0 Update pion/webrtc version
v3.0.20 -> v3.1.34
2022-08-04 22:14:44 -04:00
Sean DuBois
601f27c014 Fix crash when capturing AudioTracks
Resolves #426
2022-08-04 22:14:44 -04:00
Valentin Cocaud
2a04a14225 rename opus C function to be compatible with other opus modules (#425) 2022-08-03 17:14:36 +02:00
renovate[bot]
416bbc33f3 Update module github.com/pion/interceptor to v0.1.12 (#423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-02 00:24:45 +02:00
renovate[bot]
4a682a48c1 Update golang.org/x/image digest to 062f8c9 (#422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-02 00:23:15 +02:00
Valentin Cocaud
14db2b8130 allow to close the avfoundation device multiple time (#419)
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2022-08-01 09:50:47 +02:00
Valentin Cocaud
a3c15d1fb0 fix race condition in i420 video converter leading to video artefacts (#418)
* fix race condition in i420 video converter leading to video artefacts

* avoid copy when not necessary

* store conversion result in new slice instead of copy slice
2022-08-01 09:40:23 +02:00
renovate[bot]
43272ea965 Update module github.com/pion/webrtc/v3 to v3.1.43 (#403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 09:33:10 +02:00
Valentin Cocaud
e32fc1bdb8 fix nil reference when a read error occur with copyFrame enabled (#417)
* fix nil reference when a read error occur with copyFrame enabled

* add test

* Update pkg/io/video/broadcast_test.go

Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>

Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2022-07-28 07:36:25 +02:00
Valentin Cocaud
2af325d1a5 Fix closing a camera not closing the associated track (#414) 2022-07-27 14:15:17 +02:00
Valentin Cocaud
5e0df5e5cf Upgrade CI go versions for tests (#416)
* Upgrade CI go versions for tests

* Upgrade CI go versions for tests

* fix buffer tests with wrong error log format
2022-07-27 10:21:59 +02:00
Valentin Cocaud
d038133783 Allow to enable frame copy on a video track (#405) 2022-07-25 16:56:03 +02:00
Valentin Cocaud
cd6aaa1393 Force key frame on PLI
Also make the ReadCloser an Controllable allows to uncouple
the controller implementation from the encoder.
This is not needed for the 2 codec controller already implemented (openh264 and vpx)
but is more future proof in case it required for other codecs.
2022-07-25 10:51:35 -04:00
adamroach
82cc32308b Enable configuration of OpenH264 Encoder (#409)
There's been a long-standing TODO comment in the bridge for OpenH264 to
remove hardcoding for the set of configurable parameters exposed by that
library. That patch does exactly that.
2022-07-06 23:58:56 -05:00
renovate[bot]
8fce8a2bb5 Update golang.org/x/image digest to 41969df (#410)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-01 11:43:59 +09:00
f-fl0
5b99500290 Camera timeout duration as parameter (#408)
Set PION_MEDIADEVICES_CAMERA_READ_TIMEOUT environment variable
to set the timeout duration.
2022-06-23 10:33:23 +09:00
代码人生
e371c0d955 fix: Fix compilation error on arm64 Mac (#404) 2022-06-02 18:41:39 +08:00
renovate[bot]
69f9cbe008 Update module github.com/pion/webrtc/v3 to v3.1.34 (#400)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 13:52:14 +09:00
renovate[bot]
b5acc5d7f6 Update golang.org/x/image digest to 70e8d0d (#402)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 11:50:24 +09:00
renovate[bot]
55e65027f9 Update codecov/codecov-action action to v3 (#398)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 11:49:12 +09:00
Atsushi Watanabe
f0ff9261b4 Add darwin arm64 libs (#368)
Add opus and openh264 for arm64

Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
Co-authored-by: Earther <qn.khuat@gmail.com>
2022-05-02 11:44:46 +09:00
代码人生
08a396571f fix : Turn on the microphone with the current device id (#395)
* fix : Turn on the microphone with the current device id

* modify comments
2022-04-13 13:01:30 +08:00
f-fl0
0d09f7f458 Remove video framerate as explicit constraint (#394)
* Do not consider video framerate in the constraints fitness function
* Remove test about video framerate
2022-04-06 10:07:15 +09:00
代码人生
e780bdc6f9 Implement forced keyframes for x264 (#388)
* Implement forced keyframes for x264

* format code
2022-04-05 13:12:24 +08:00
代码人生
ff18b21629 Fix OpenH264 keyframe generation method (#387)
* Fix OpenH264 keyframe generation method

* Fix : fix variable reference error
2022-04-05 13:12:07 +08:00
代码人生
eaf9ff42a8 Implement the CursorEncoding protocol of VNC (#389)
* Implement the CursorEncoding protocol of VNC

* Fix color depth transform
2022-04-05 13:11:28 +08:00
82 changed files with 2503 additions and 502 deletions

View File

@@ -13,13 +13,15 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go: [ '1.16', '1.15' ] go:
- '1.20'
- '1.21'
name: Linux Go ${{ matrix.go }} name: Linux Go ${{ matrix.go }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Install dependencies - name: Install dependencies
@@ -32,24 +34,67 @@ jobs:
libx264-dev libx264-dev
- name: Run Test Suite - name: Run Test Suite
run: make test run: make test
- uses: codecov/codecov-action@v2 - uses: codecov/codecov-action@v3
if: matrix.go == '1.16' if: matrix.go == '1.21'
build-darwin: build-darwin:
runs-on: macos-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go: [ '1.16', '1.15' ] runs-on:
name: Darwin Go ${{ matrix.go }} - macos-latest
- ['self-hosted', 'macOS', 'ARM64']
go:
- '1.20'
- '1.21'
runs-on: ${{ matrix.runs-on }}
name: Darwin Go ${{ matrix.go }} ${{ join(matrix.runs-on, ' ') }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
# Set up local brew only on self-hosted darwin
- name: Checkout Homebrew
if: matrix.runs-on != 'macos-latest'
uses: actions/checkout@v4
with:
repository: Homebrew/brew
path: homebrew
- name: Local brew cache key
if: matrix.runs-on != 'macos-latest'
id: brew-cache-key
run: echo "key=$(date +'%Y-%U')" | tee ${GITHUB_OUTPUT} # weekly update cache
- name: Cache local brew taps
if: matrix.runs-on != 'macos-latest'
uses: actions/cache@v4
with:
path: homebrew/Library/Taps
key: ${{ runner.os }}-brew-taps-${{ steps.brew-cache-key.outputs.key }}
restore-keys: ${{ runner.os }}-brew-taps-
- name: Set up brew to install deps under temporary dir
if: matrix.runs-on != 'macos-latest'
run: |
dir="${GITHUB_WORKSPACE}/homebrew"
cd "${dir}"
echo "Set up shellenv" >&2
env="$(./bin/brew shellenv)"
echo "${env}" | tee -a ${GITHUB_ENV}
eval "${env}"
echo "Set up paths" >&2
echo "${dir}/bin" | tee -a ${GITHUB_PATH}
echo "Brew update" >&2
brew update --force --quiet
chmod -R go-w "$(brew --prefix)/share/zsh"
- name: Install dependencies - name: Install dependencies
run: | run: |
which brew
brew install \ brew install \
pkg-config \ pkg-config \
opus \ opus \
@@ -62,12 +107,12 @@ jobs:
name: Check Licenses name: Check Licenses
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: '1.16' go-version: '1.21'
- name: Installing go-licenses - name: Installing go-licenses
run: go get github.com/google/go-licenses run: go install github.com/google/go-licenses@latest
- name: Checking licenses - name: Checking licenses
run: go-licenses check ./... run: go-licenses check ./...

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- name: fix - name: fix

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@
scripts/cross scripts/cross
coverage.txt coverage.txt
.idea

View File

@@ -16,7 +16,8 @@ supported_platforms := \
linux-arm64 \ linux-arm64 \
linux-x64 \ linux-x64 \
windows-x64 \ windows-x64 \
darwin-x64 darwin-x64 \
darwin-arm64
cmd_build := build cmd_build := build
cmd_test := test cmd_test := test
examples_dir := examples examples_dir := examples
@@ -24,7 +25,7 @@ codec_dir := pkg/codec
codec_list := $(shell ls $(codec_dir)/*/Makefile) codec_list := $(shell ls $(codec_dir)/*/Makefile)
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%) codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms))) targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms)))
pkgs_without_mmal := $(shell go list ./... | grep -v mmal) pkgs_without_ext_device := $(shell go list ./... | grep -v mmal | grep -v vaapi)
pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation) pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
define BUILD_TEMPLATE define BUILD_TEMPLATE
@@ -72,11 +73,11 @@ $(foreach codec, $(codec_list), \
# Description: # Description:
# Run a series of tests # Run a series of tests
$(cmd_test): $(cmd_test):
go vet $(pkgs_without_mmal) go vet $(pkgs_without_ext_device)
go build $(pkgs_without_mmal) go build $(pkgs_without_ext_device)
# go build without CGO # go build without CGO
CGO_ENABLED=0 go build $(pkgs_without_cgo) CGO_ENABLED=0 go build $(pkgs_without_cgo)
# go build with CGO # go build with CGO
CGO_ENABLED=1 go build $(pkgs_without_mmal) CGO_ENABLED=1 go build $(pkgs_without_ext_device)
$(MAKE) --directory=$(examples_dir) $(MAKE) --directory=$(examples_dir)
go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_mmal) go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_ext_device)

View File

@@ -6,8 +6,8 @@
<h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4> <h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4>
<p align="center"> <p align="center">
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
<a href="https://github.com/pion/mediadevices/actions"><img src="https://github.com/pion/mediadevices/workflows/CI/badge.svg?branch=master" alt="Build status"></a> <img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/mediadevices/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://godoc.org/github.com/pion/mediadevices?status.svg" alt="GoDoc"></a> <a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://pkg.go.dev/badge/github.com/pion/mediadevices.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a> <a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p> </p>
@@ -15,11 +15,13 @@
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API! `mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
## Install ### Install
`go get -u github.com/pion/mediadevices` ```bash
go get -u github.com/pion/mediadevices
```
## Usage ### Usage
The following snippet shows how to capture a camera stream and store a frame as a jpeg image: The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
@@ -67,25 +69,21 @@ func main() {
output, _ := os.Create("frame.jpg") output, _ := os.Create("frame.jpg")
jpeg.Encode(output, frame, nil) jpeg.Encode(output, frame, nil)
} }
``` ```
## More Examples ### More Examples
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call * [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream * [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server * [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG * [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera * [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
## Available Media Inputs ### Available Media Inputs
| Input | Linux | Mac | Windows |
| Input | Linux | Mac | Windows |
| :--------: | :---: | :-: | :-----: | | :--------: | :---: | :-: | :-----: |
| Camera | ✔️ | ✔️ | ✔️ | | Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ | | Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | ✔️ | ✔️ | | Screen | ✔️ | ✔️ | ✔️ |
By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect: By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
@@ -96,8 +94,7 @@ import (
) )
``` ```
## Available Codecs ### Available Codecs
In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`: In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`:
```go ```go
@@ -130,9 +127,9 @@ Since `mediadevices` doesn't implement the video/audio codecs, it needs to call
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective. Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
### Video Codecs #### Video Codecs
#### x264 ##### x264
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format. A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264) * Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
@@ -140,19 +137,19 @@ A free software library and application for encoding video streams into the H.26
* Mac: `brew install x264` * Mac: `brew install x264`
* Ubuntu: `apt install libx264-dev` * Ubuntu: `apt install libx264-dev`
#### mmal ##### mmal
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs. A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal) * Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
* Installation: no installation needed, mmal should come built in Raspberry Pi devices * Installation: no installation needed, mmal should come built in Raspberry Pi devices
#### openh264 ##### openh264
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications. A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264) * Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
* Installation: no installation needed, included as a static binary * Installation: no installation needed, included as a static binary
#### vpx ##### vpx
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats. A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx) * Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
@@ -160,7 +157,7 @@ A free software video codec library from Google and the Alliance for Open Media
* Mac: `brew install libvpx` * Mac: `brew install libvpx`
* Ubuntu: `apt install libvpx-dev` * Ubuntu: `apt install libvpx-dev`
#### vaapi ##### vaapi
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9). An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi) * Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
@@ -168,9 +165,9 @@ An open source API that allows applications such as VLC media player or GStreame
* Ubuntu: `apt install libva-dev` * Ubuntu: `apt install libva-dev`
### Audio Codecs #### Audio Codecs
#### opus ##### opus
A totally open, royalty-free, highly versatile audio codec. A totally open, royalty-free, highly versatile audio codec.
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus) * Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
@@ -178,16 +175,14 @@ A totally open, royalty-free, highly versatile audio codec.
* Mac: `brew install opus` * Mac: `brew install opus`
* Ubuntu: `apt install libopus-dev` * Ubuntu: `apt install libopus-dev`
## Benchmark ### Benchmark
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**. Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc. The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
## FAQ ### FAQ
### Failed to find the best driver that fits the constraints
#### Failed to find the best driver that fits the constraints
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like: `mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
1. Open all registered drivers 1. Open all registered drivers
@@ -198,13 +193,14 @@ So, when `mediadevices` returns `failed to find the best driver that fits the co
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera` * Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that. * Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter` * Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
* If trying to use `import _ github.com/pion/mediadevices/pkg/driver/screen` note that you will need to use `GetDisplayMedia` instead of `GetUserMedia`
### Failed to find vpx/x264/mmal/opus codecs #### Failed to find vpx/x264/mmal/opus codecs
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery. Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
If you see the following error message at compile time: If you see the following error message at compile time:
```
```bash
# pkg-config --cflags -- vpx # pkg-config --cflags -- vpx
Package vpx was not found in the pkg-config search path. Package vpx was not found in the pkg-config search path.
Perhaps you should add the directory containing `vpx.pc' Perhaps you should add the directory containing `vpx.pc'
@@ -218,8 +214,10 @@ There are 2 common problems:
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs). * The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`. * Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
### Roadmap
The library can be used with our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
## Community ### Community
Pion has an active community on the [Slack](https://pion.ly/slack). Pion has an active community on the [Slack](https://pion.ly/slack).
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
@@ -227,11 +225,8 @@ Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and imp
We are always looking to support **your projects**. Please reach out if you have something to build! We are always looking to support **your projects**. Please reach out if you have something to build!
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
## Contributing ### Contributing
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
* [Lukas Herman](https://github.com/lherman-cs) - _Original Author_ ### License
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
## License
MIT License - see [LICENSE](LICENSE) for full text MIT License - see [LICENSE](LICENSE) for full text

View File

@@ -0,0 +1,47 @@
FROM dockercore/golang-cross as m1cross
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq && apt-get install -y -q --no-install-recommends \
cmake \
git \
libssl-dev \
libxml2-dev \
libz-dev \
&& rm -rf /var/lib/apt/lists/*
ENV SDK_VERSION=11.3 \
TARGET_DIR=/osxcross/target \
UNATTENDED=1
WORKDIR /work
RUN git clone --depth=1 https://github.com/tpoechtrager/osxcross.git /work \
&& cd /work/tarballs \
&& wget -q https://github.com/phracker/MacOSX-SDKs/releases/download/${SDK_VERSION}/MacOSX${SDK_VERSION}.sdk.tar.xz
# Build cross compile toolchain for Apple silicon
RUN ./build.sh
FROM dockcross/base
ENV OSX_CROSS_PATH=/osxcross
COPY --from=m1cross "${OSX_CROSS_PATH}/." "${OSX_CROSS_PATH}/"
ENV PATH=${OSX_CROSS_PATH}/target/bin:$PATH
COPY init.sh /tmp/init.sh
RUN bash /tmp/init.sh
ENV CC=arm64-apple-darwin20.4-clang \
CXX=arm64-apple-darwin20.4-clang++ \
CPP=arm64-apple-darwin20.4-clang++ \
AR=arm64-apple-darwin20.4-ar \
AS=arm64-apple-darwin20.4-as \
LD=arm64-apple-darwin20.4-ld
COPY darwin-arm64.cmake ${OSX_CROSS_PATH}/
ENV CMAKE_TOOLCHAIN_FILE ${OSX_CROSS_PATH}/darwin-arm64.cmake
ARG IMAGE=lherman/cross-darwin-arm64
ARG VERSION=latest
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}

View File

@@ -0,0 +1,8 @@
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR arm64)
set(CMAKE_C_COMPILER $ENV{CC})
set(CMAKE_CXX_COMPILER $ENV{CXX})
set(CMAKE_AR $ENV{AR})
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})

View File

@@ -3,9 +3,9 @@ module github.com/pion/mediadevices/examples
go 1.14 go 1.14
require ( require (
github.com/esimov/pigo v1.4.3 github.com/esimov/pigo v1.4.6
github.com/pion/mediadevices v0.0.0 github.com/pion/mediadevices v0.0.0
github.com/pion/webrtc/v3 v3.0.20 github.com/pion/webrtc/v3 v3.2.29
) )
replace github.com/pion/mediadevices v0.0.0 => ../ replace github.com/pion/mediadevices v0.0.0 => ../

View File

@@ -1,18 +1,18 @@
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/blackjack/webcam v0.6.0 h1:ovvUNsIQZVfJZl6V2FcvFLFH62/Xx5zLwybKwEy3ZLw=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs= github.com/blackjack/webcam v0.6.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/esimov/pigo v1.4.3 h1:xl098Z9CHmouywvyRZepuKx8aSWHBs/0lZtp7Yt5g28= github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.3/go.mod h1:aOTYpOWsqniACzXKdSOGkqI6CnWQpP8tFjgtUOARoEs= github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
github.com/fogleman/gg v1.0.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM= github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs= github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -21,13 +21,18 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0/go.mod h1:ZceVWGtzUZmxyN+/1I+oG31oOm1dOA2QUNbua9TLVdE= github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -36,78 +41,110 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/ice/v2 v2.0.16 h1:K6bzD8ef9vMKbGMTHaUweHXEyuNGnvr2zdqKoLKZPn0= github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
github.com/pion/ice/v2 v2.0.16/go.mod h1:SJNJzC27gDZoOW0UoxIoC8Hf2PDxG28hQyNdSexDu38= github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE= github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4= github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U= github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY= github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU= github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0= github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/webrtc/v3 v3.0.20 h1:Jj0sk45MqQdkR24E1wbFRmOzb1Lv258ot9zd2fYB/Pw= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/webrtc/v3 v3.0.20/go.mod h1:0eJnCpQrUMpRnvyonw4ZiWClToerpixrZ2KcoTxvX9M= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -116,21 +153,54 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -138,6 +208,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -147,5 +219,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1
examples/vnc/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
vnc

44
go.mod
View File

@@ -1,15 +1,41 @@
module github.com/pion/mediadevices module github.com/pion/mediadevices
go 1.13 go 1.19
require ( require (
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 github.com/blackjack/webcam v0.6.0
github.com/gen2brain/malgo v0.10.35 github.com/gen2brain/malgo v0.11.21
github.com/google/uuid v1.3.0 github.com/google/uuid v1.6.0
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
github.com/pion/interceptor v0.1.25
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2
github.com/pion/rtp v1.7.9 github.com/pion/rtcp v1.2.14
github.com/pion/webrtc/v3 v3.1.27 github.com/pion/rtp v1.8.3
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a github.com/pion/webrtc/v3 v3.2.29
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect github.com/stretchr/testify v1.9.0
golang.org/x/image v0.15.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/pion/datachannel v1.5.5 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/ice/v2 v2.3.13 // indirect
github.com/pion/mdns v0.0.12 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/sctp v1.8.12 // indirect
github.com/pion/sdp/v3 v3.0.8 // indirect
github.com/pion/srtp/v2 v2.0.18 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/transport/v2 v2.2.3 // indirect
github.com/pion/turn/v2 v2.1.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

178
go.sum
View File

@@ -1,14 +1,14 @@
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw= github.com/blackjack/webcam v0.6.0 h1:ovvUNsIQZVfJZl6V2FcvFLFH62/Xx5zLwybKwEy3ZLw=
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4= github.com/blackjack/webcam v0.6.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gen2brain/malgo v0.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo= github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs= github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY= github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -23,13 +23,14 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o= github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4= github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -47,80 +48,100 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/ice/v2 v2.2.2 h1:UfmAslxZ0u0itVjA4x7aw7WeQIv22FdF8VjW9cM+74g= github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
github.com/pion/ice/v2 v2.2.2/go.mod h1:vLI7dFqxw8zMSb9J+ca74XU7JjLhddgfQB9+BbTydCo= github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/pion/interceptor v0.1.10 h1:DJ2GjMGm4XGIQgMJxuEpdaExdY/6RdngT7Uh4oVmquU= github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
github.com/pion/interceptor v0.1.10/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U= github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.7.9 h1:17W5Mt2IM3MVfOh7yRfzXbbKXYzBZxV8eG4KKAy+0bg= github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
github.com/pion/rtp v1.7.9/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ= github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A= github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/webrtc/v3 v3.1.27 h1:yQ6TuHKJR/vro3nLZMfPv+WGf7T1/4ItaQeuzIZLXs4= github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
github.com/pion/webrtc/v3 v3.1.27/go.mod h1:hdduI+Rx0cpGvva18j0gKy/Iak611WPyhUIXs5W/FuI= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -134,21 +155,45 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -168,5 +213,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,5 +1,7 @@
package mediadevices package mediadevices
import "github.com/pion/mediadevices/pkg/codec"
type EncodedBuffer struct { type EncodedBuffer struct {
Data []byte Data []byte
Samples uint32 Samples uint32
@@ -8,11 +10,13 @@ type EncodedBuffer struct {
type EncodedReadCloser interface { type EncodedReadCloser interface {
Read() (EncodedBuffer, func(), error) Read() (EncodedBuffer, func(), error)
Close() error Close() error
codec.Controllable
} }
type encodedReadCloserImpl struct { type encodedReadCloserImpl struct {
readFn func() (EncodedBuffer, func(), error) readFn func() (EncodedBuffer, func(), error)
closeFn func() error closeFn func() error
controllerFn func() codec.EncoderController
} }
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) { func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
@@ -23,9 +27,14 @@ func (r *encodedReadCloserImpl) Close() error {
return r.closeFn() return r.closeFn()
} }
func (r *encodedReadCloserImpl) Controller() codec.EncoderController {
return r.controllerFn()
}
type encodedIOReadCloserImpl struct { type encodedIOReadCloserImpl struct {
readFn func([]byte) (int, error) readFn func([]byte) (int, error)
closeFn func() error closeFn func() error
controller func() codec.EncoderController
} }
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl { func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
@@ -48,7 +57,8 @@ func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserIm
encoded.Data = encoded.Data[n:] encoded.Data = encoded.Data[n:]
return n, nil return n, nil
}, },
closeFn: reader.Close, closeFn: reader.Close,
controller: reader.Controller,
} }
} }
@@ -59,3 +69,7 @@ func (r *encodedIOReadCloserImpl) Read(b []byte) (int, error) {
func (r *encodedIOReadCloserImpl) Close() error { func (r *encodedIOReadCloserImpl) Close() error {
return r.closeFn() return r.closeFn()
} }
func (r *encodedIOReadCloserImpl) Controller() codec.EncoderController {
return r.controller()
}

View File

@@ -125,18 +125,6 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
frameFormat: frame.FormatI420, frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate, 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 { for name, c := range cases {
@@ -229,12 +217,6 @@ func TestSelectBestDriverConstraintsNoFit(t *testing.T) {
frameFormat: frame.FormatI420, frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate, frameRate: expectedProp.FrameRate,
}, },
"DifferentFrameRate": {
width: expectedProp.Width,
height: expectedProp.Height,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate - 1,
},
} }
for name, c := range cases { for name, c := range cases {

View File

@@ -27,6 +27,7 @@
#define MAX_DEVICES 8 #define MAX_DEVICES 8
#define MAX_PROPERTIES 64 #define MAX_PROPERTIES 64
#define MAX_DEVICE_UID_CHARS 64 #define MAX_DEVICE_UID_CHARS 64
#define MAX_DEVICE_NAME_CHARS 64
typedef const char* STATUS; typedef const char* STATUS;
static STATUS STATUS_OK = (STATUS) NULL; static STATUS STATUS_OK = (STATUS) NULL;
@@ -46,7 +47,7 @@ typedef enum AVBindFrameFormat {
AVBindFrameFormatI420, AVBindFrameFormatI420,
AVBindFrameFormatNV21, AVBindFrameFormatNV21,
AVBindFrameFormatNV12, AVBindFrameFormatNV12,
AVBindFrameFormatYUY2, AVBindFrameFormatYUYV,
AVBindFrameFormatUYVY, AVBindFrameFormatUYVY,
} AVBindFrameFormat; } AVBindFrameFormat;
@@ -65,6 +66,7 @@ typedef struct AVBindSession AVBindSession, *PAVBindSession;
typedef struct { typedef struct {
char uid[MAX_DEVICE_UID_CHARS + 1]; char uid[MAX_DEVICE_UID_CHARS + 1];
char name[MAX_DEVICE_NAME_CHARS + 1];
} AVBindDevice, *PAVBindDevice; } AVBindDevice, *PAVBindDevice;
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static // AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static

View File

@@ -46,6 +46,8 @@
} \ } \
} while(0) } while(0)
static NSString *const UnrecognizedMacOSVersionException = @"UnrecognizedMacOSVersionException";
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> @interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property (readonly) AVBindDataCallback mCallback; @property (readonly) AVBindDataCallback mCallback;
@@ -74,26 +76,61 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
!CMSampleBufferIsValid(sampleBuffer) || !CMSampleBufferIsValid(sampleBuffer) ||
!CMSampleBufferDataIsReady(sampleBuffer)) { !CMSampleBufferDataIsReady(sampleBuffer)) {
return; return;
} }
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer == NULL) { if (imageBuffer == NULL) {
return; return;
} }
imageBuffer = CVBufferRetain(imageBuffer); CVBufferRetain(imageBuffer);
CVReturn ret = CVReturn ret =
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
if (ret != kCVReturnSuccess) { if (ret != kCVReturnSuccess) {
return; CVBufferRelease(imageBuffer);
return;
}
// Handle NV12 special case
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
// Get actual dimensions of image (without padding)
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t totalSize = /*Y plane*/ width * height + /*UV plane*/ width * height / 2;
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
void *mergedBuffer = malloc(totalSize);
if (!mergedBuffer) {
NSLog(@"Failed to allocate memory for merged buffer");
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
CVBufferRelease(imageBuffer);
return;
}
// Truncate data where we know it should end to strip padding
void *yPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
for (size_t row = 0; row < height; ++row) {
memcpy(mergedBuffer + row * width, yPlaneBuf + row * bytesPerRowY, width);
}
void *uvPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
for (size_t row = 0; row < height / 2; ++row) {
memcpy(mergedBuffer + width * height + row * width, uvPlaneBuf + row * bytesPerRowUV, width);
}
_mCallback(_mPUserData, mergedBuffer, (int)totalSize);
free(mergedBuffer);
} else {
void *buf = CVPixelBufferGetBaseAddress(imageBuffer);
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
_mCallback(_mPUserData, buf, (int)dataSize);
} }
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
_mCallback(_mPUserData, buf, (int)dataSize);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
CVBufferRelease(imageBuffer); CVBufferRelease(imageBuffer);
} }
@@ -139,7 +176,7 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
case AVBindFrameFormatUYVY: case AVBindFrameFormatUYVY:
*pFourCC = kCVPixelFormatType_422YpCbCr8; *pFourCC = kCVPixelFormatType_422YpCbCr8;
break; break;
case AVBindFrameFormatYUY2: case AVBindFrameFormatYUYV:
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs; *pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
break; break;
// TODO: Add the rest of frame formats // TODO: Add the rest of frame formats
@@ -163,7 +200,7 @@ STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
*pFormat = AVBindFrameFormatUYVY; *pFormat = AVBindFrameFormatUYVY;
break; break;
case kCVPixelFormatType_422YpCbCr8_yuvs: case kCVPixelFormatType_422YpCbCr8_yuvs:
*pFormat = AVBindFrameFormatYUY2; *pFormat = AVBindFrameFormatYUYV;
break; break;
// TODO: Add the rest of frame formats // TODO: Add the rest of frame formats
default: default:
@@ -182,15 +219,32 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
PAVBindDevice pDevice; PAVBindDevice pDevice;
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio; AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
NSArray *refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera, NSArray *refAllTypes;
AVCaptureDeviceTypeBuiltInMicrophone, #if defined(MAC_OS_VERSION_14_0)
AVCaptureDeviceTypeExternalUnknown if (@available(macOS 14.0, *)) {
]; refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeMicrophone,
AVCaptureDeviceTypeExternal,
];
} else {
@throw [NSException exceptionWithName:UnrecognizedMacOSVersionException
reason:@"Unrecognized or unsupported macOS version detected."
userInfo:nil];
}
#else
refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeExternalUnknown,
];
#endif
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes: refAllTypes discoverySessionWithDeviceTypes: refAllTypes
mediaType: _mediaType mediaType: _mediaType
position: AVCaptureDevicePositionUnspecified]; position: AVCaptureDevicePositionUnspecified];
int i = 0; int i = 0;
for (AVCaptureDevice *refDevice in refSession.devices) { for (AVCaptureDevice *refDevice in refSession.devices) {
@@ -201,6 +255,8 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
pDevice = devices + i; pDevice = devices + i;
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS); strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0'; pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
strncpy(pDevice->name, refDevice.localizedName.UTF8String, MAX_DEVICE_NAME_CHARS);
pDevice->name[MAX_DEVICE_NAME_CHARS] = '\0';
i++; i++;
} }

View File

@@ -14,6 +14,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"sync"
"unsafe" "unsafe"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
@@ -33,6 +34,7 @@ type Device struct {
// UID is a unique identifier for a device // UID is a unique identifier for a device
UID string UID string
cDevice C.AVBindDevice cDevice C.AVBindDevice
Name string
} }
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) { func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
@@ -43,8 +45,8 @@ func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
return C.AVBindFrameFormatNV21, true return C.AVBindFrameFormatNV21, true
case frame.FormatNV12: case frame.FormatNV12:
return C.AVBindFrameFormatNV12, true return C.AVBindFrameFormatNV12, true
case frame.FormatYUY2: case frame.FormatYUYV:
return C.AVBindFrameFormatYUY2, true return C.AVBindFrameFormatYUYV, true
case frame.FormatUYVY: case frame.FormatUYVY:
return C.AVBindFrameFormatUYVY, true return C.AVBindFrameFormatUYVY, true
default: default:
@@ -60,8 +62,8 @@ func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
return frame.FormatNV21, true return frame.FormatNV21, true
case C.AVBindFrameFormatNV12: case C.AVBindFrameFormatNV12:
return frame.FormatNV12, true return frame.FormatNV12, true
case C.AVBindFrameFormatYUY2: case C.AVBindFrameFormatYUYV:
return frame.FormatYUY2, true return frame.FormatYUYV, true
case C.AVBindFrameFormatUYVY: case C.AVBindFrameFormatUYVY:
return frame.FormatUYVY, true return frame.FormatUYVY, true
default: default:
@@ -86,6 +88,7 @@ func Devices(mediaType MediaType) ([]Device, error) {
for i := range devices { for i := range devices {
devices[i].UID = C.GoString(&cDevices[i].uid[0]) devices[i].UID = C.GoString(&cDevices[i].uid[0])
devices[i].cDevice = cDevices[i] devices[i].cDevice = cDevices[i]
devices[i].Name = C.GoString(&cDevices[i].name[0])
} }
return devices, nil return devices, nil
@@ -99,6 +102,8 @@ type ReadCloser struct {
onClose func() onClose func()
cancelCtx context.Context cancelCtx context.Context
cancelFunc func() cancelFunc func()
closeWG sync.WaitGroup
lock sync.Mutex
} }
func newReadCloser(onClose func()) *ReadCloser { func newReadCloser(onClose func()) *ReadCloser {
@@ -113,13 +118,16 @@ func newReadCloser(onClose func()) *ReadCloser {
} }
func (rc *ReadCloser) dataCb(data []byte) { func (rc *ReadCloser) dataCb(data []byte) {
rc.closeWG.Add(1)
defer rc.closeWG.Done()
// TODO: add a policy for slow reader // TODO: add a policy for slow reader
if rc.cancelCtx.Err() != nil { if rc.cancelCtx.Err() != nil {
return return
} }
select { select {
// Use the Done channel to avoid waiting for new data from closed camera
case <-rc.cancelCtx.Done(): case <-rc.cancelCtx.Done():
close(rc.dataChan)
case rc.dataChan <- data: case rc.dataChan <- data:
} }
} }
@@ -137,17 +145,28 @@ func (rc *ReadCloser) Read() ([]byte, func(), error) {
// Close closes the capturing session, and no data will flow anymore // Close closes the capturing session, and no data will flow anymore
func (rc *ReadCloser) Close() { func (rc *ReadCloser) Close() {
rc.lock.Lock()
defer rc.lock.Unlock()
if rc.cancelCtx.Err() != nil {
return // already closed
}
if rc.onClose != nil { if rc.onClose != nil {
rc.onClose() rc.onClose()
} }
rc.cancelFunc() rc.cancelFunc()
unregister(rc.id) unregister(rc.id)
rc.closeWG.Wait()
close(rc.dataChan)
} }
// Session represents a capturing session. // Session represents a capturing session.
type Session struct { type Session struct {
device Device device Device
cSession C.PAVBindSession cSession C.PAVBindSession
lock sync.Mutex
closed bool
} }
// NewSession creates a new capturing session // NewSession creates a new capturing session
@@ -165,6 +184,13 @@ func NewSession(device Device) (*Session, error) {
// Close stops capturing session and frees up resources // Close stops capturing session and frees up resources
func (session *Session) Close() error { func (session *Session) Close() error {
session.lock.Lock()
defer session.lock.Unlock()
if session.closed {
return nil
}
session.closed = true
if session.cSession == nil { if session.cSession == nil {
return nil return nil
} }

View File

@@ -112,15 +112,37 @@ type VideoEncoderBuilder interface {
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error) BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
} }
// ReadCloser is an io.ReadCloser with methods for rate limiting: SetBitRate and ForceKeyFrame // ReadCloser is an io.ReadCloser with a controller
type ReadCloser interface { type ReadCloser interface {
Read() (b []byte, release func(), err error) Read() (b []byte, release func(), err error)
Close() error Close() error
Controllable
}
// EncoderController is the interface allowing to control the encoder behaviour after it's initialisation.
// It will possibly have common control method in the future.
// A controller can have optional methods represented by *Controller interfaces
type EncoderController interface{}
// Controllable is a interface representing a encoder which can be controlled
// after it's initialisation with an EncoderController
type Controllable interface {
Controller() EncoderController
}
// KeyFrameController is a interface representing an encoder that can be forced to produce key frame on demand
type KeyFrameController interface {
EncoderController
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
// BitRateController is a interface representing an encoder which can have a variable bit rate
type BitRateController interface {
EncoderController
// SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted // SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
// but this also means that the quality will also be lower. // but this also means that the quality will also be lower.
SetBitRate(int) error SetBitRate(int) error
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
} }
// BaseParams represents an codec's encoding properties // BaseParams represents an codec's encoding properties

View File

@@ -119,3 +119,41 @@ func VideoEncoderCloseTwiceTest(t *testing.T, c codec.VideoEncoderBuilder, p pro
t.Fatal(err) t.Fatal(err)
} }
} }
func AudioEncoderReadAfterCloseTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
return w, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, func() error {
_, _, err := enc.Read()
return err
}, "on Read()"); err != io.EOF {
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
}
}
func VideoEncoderReadAfterCloseTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return img, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, func() error {
_, _, err := enc.Read()
return err
}, "on Read()"); err != io.EOF {
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
}
}

View File

@@ -64,10 +64,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
imgReal := img.(*image.YCbCr) imgReal := img.(*image.YCbCr)
var y, cb, cr C.Slice var y, cb, cr C.Slice
y.data = (*C.uchar)(&imgReal.Y[0]) y.data = (*C.uchar)(&imgReal.Y[0])
@@ -91,12 +92,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err return encoded, func() {}, err
} }
func (e *encoder) SetBitRate(b int) error { func (e *encoder) Controller() codec.EncoderController {
panic("SetBitRate is not implemented") return e
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
} }
func (e *encoder) Close() error { func (e *encoder) Close() error {

View File

@@ -4,6 +4,7 @@ import (
"image" "image"
"testing" "testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest" "github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
@@ -43,4 +44,41 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderReadAfterCloseTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
} }

View File

@@ -21,29 +21,26 @@ Encoder *enc_new(const EncoderOptions opts, int *eresult) {
return NULL; return NULL;
} }
// TODO: Remove hardcoded values params.iUsageType = opts.usage_type;
params.iUsageType = CAMERA_VIDEO_REAL_TIME;
params.iPicWidth = opts.width; params.iPicWidth = opts.width;
params.iPicHeight = opts.height; params.iPicHeight = opts.height;
params.iTargetBitrate = opts.target_bitrate; params.iTargetBitrate = opts.target_bitrate;
params.iMaxBitrate = opts.target_bitrate; params.iMaxBitrate = opts.target_bitrate;
params.iRCMode = RC_BITRATE_MODE; params.iRCMode = opts.rc_mode;
params.fMaxFrameRate = opts.max_fps; params.fMaxFrameRate = opts.max_fps;
params.bEnableFrameSkip = true; params.bEnableFrameSkip = opts.enable_frame_skip;
params.uiMaxNalSize = 0; params.uiMaxNalSize = opts.max_nal_size;
params.uiIntraPeriod = 30; params.uiIntraPeriod = opts.intra_period;
// set to 0, so that it'll automatically use multi threads when needed params.iMultipleThreadIdc = opts.multiple_thread_idc;
params.iMultipleThreadIdc = 0;
// The base spatial layer 0 is the only one we use. // The base spatial layer 0 is the only one we use.
params.sSpatialLayers[0].iVideoWidth = params.iPicWidth; params.sSpatialLayers[0].iVideoWidth = params.iPicWidth;
params.sSpatialLayers[0].iVideoHeight = params.iPicHeight; params.sSpatialLayers[0].iVideoHeight = params.iPicHeight;
params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate; params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate;
params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate; params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate;
params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate; params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate;
// Single NAL unit mode params.sSpatialLayers[0].sSliceArgument.uiSliceNum = opts.slice_num;
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1; params.sSpatialLayers[0].sSliceArgument.uiSliceMode = opts.slice_mode;
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SIZELIMITED_SLICE; params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = opts.slice_size_constraint;
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = 12800;
rv = engine->InitializeExt(&params); rv = engine->InitializeExt(&params);
if (rv != 0) { if (rv != 0) {
@@ -81,7 +78,7 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
Slice payload = {0}; Slice payload = {0};
if(e->force_key_frame == 1) { if(e->force_key_frame == 1) {
info.eFrameType = videoFrameTypeI; e->engine->ForceIntraFrame(true);
e->force_key_frame = 0; e->force_key_frame = 0;
} }

View File

@@ -22,6 +22,15 @@ typedef struct EncoderOptions {
int width, height; int width, height;
int target_bitrate; int target_bitrate;
float max_fps; float max_fps;
EUsageType usage_type;
RC_MODES rc_mode;
bool enable_frame_skip;
unsigned int max_nal_size;
unsigned int intra_period;
int multiple_thread_idc;
unsigned int slice_num;
SliceModeEnum slice_mode;
unsigned int slice_size_constraint;
} EncoderOptions; } EncoderOptions;
typedef struct Encoder { typedef struct Encoder {

Binary file not shown.

View File

@@ -33,10 +33,19 @@ func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser,
var rv C.int var rv C.int
cEncoder := C.enc_new(C.EncoderOptions{ cEncoder := C.enc_new(C.EncoderOptions{
width: C.int(p.Width), width: C.int(p.Width),
height: C.int(p.Height), height: C.int(p.Height),
target_bitrate: C.int(params.BitRate), target_bitrate: C.int(params.BitRate),
max_fps: C.float(p.FrameRate), max_fps: C.float(p.FrameRate),
usage_type: C.EUsageType(params.UsageType),
rc_mode: C.RC_MODES(params.RCMode),
enable_frame_skip: C.bool(params.EnableFrameSkip),
max_nal_size: C.uint(params.MaxNalSize),
intra_period: C.uint(params.IntraPeriod),
multiple_thread_idc: C.int(params.MultipleThreadIdc),
slice_num: C.uint(params.SliceNum),
slice_mode: C.SliceModeEnum(params.SliceMode),
slice_size_constraint: C.uint(params.SliceSizeConstraint),
}, &rv) }, &rv)
if err := errResult(rv); err != nil { if err := errResult(rv); err != nil {
return nil, fmt.Errorf("failed in creating encoder: %v", err) return nil, fmt.Errorf("failed in creating encoder: %v", err)
@@ -56,10 +65,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
yuvImg := img.(*image.YCbCr) yuvImg := img.(*image.YCbCr)
bounds := yuvImg.Bounds() bounds := yuvImg.Bounds()
@@ -81,15 +91,15 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, nil return encoded, func() {}, nil
} }
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error { func (e *encoder) ForceKeyFrame() error {
e.engine.force_key_frame = C.int(1) e.engine.force_key_frame = C.int(1)
return nil return nil
} }
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error { func (e *encoder) Close() error {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()

View File

@@ -8,5 +8,6 @@ package openh264
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a //#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-x64.a //#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-x64.a
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-x64.a //#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-x64.a
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-arm64.a
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-windows-x64.a -lssp //#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-windows-x64.a -lssp
import "C" import "C"

View File

@@ -4,11 +4,28 @@ import (
"image" "image"
"testing" "testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest" "github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
) )
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestEncoder(t *testing.T) { func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) { t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams() p, err := NewParams()
@@ -43,4 +60,23 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderReadAfterCloseTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
} }

View File

@@ -1,5 +1,8 @@
package openh264 package openh264
// #include <openh264/codec_api.h>
import "C"
import ( import (
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/io/video"
@@ -9,14 +12,62 @@ import (
// Params stores libopenh264 specific encoding parameters. // Params stores libopenh264 specific encoding parameters.
type Params struct { type Params struct {
codec.BaseParams codec.BaseParams
UsageType UsageTypeEnum
RCMode RCModeEnum
EnableFrameSkip bool
MaxNalSize uint
IntraPeriod uint
MultipleThreadIdc int
SliceNum uint
SliceMode SliceModeEnum
SliceSizeConstraint uint
} }
type UsageTypeEnum int
const (
CameraVideoRealTime UsageTypeEnum = C.CAMERA_VIDEO_REAL_TIME ///< camera video for real-time communication
ScreenContentRealTime UsageTypeEnum = C.SCREEN_CONTENT_REAL_TIME ///< screen content signal
CameraVideoNonRealTime UsageTypeEnum = C.CAMERA_VIDEO_NON_REAL_TIME
ScreenContentNonRealTime UsageTypeEnum = C.SCREEN_CONTENT_NON_REAL_TIME
InputContentTypeAll UsageTypeEnum = C.INPUT_CONTENT_TYPE_ALL
)
type RCModeEnum int
const (
RCQualityMode RCModeEnum = C.RC_QUALITY_MODE ///< quality mode
RCBitrateMode RCModeEnum = C.RC_BITRATE_MODE ///< bitrate mode
RCBufferbaseedMode RCModeEnum = C.RC_BUFFERBASED_MODE ///< no bitrate control,only using buffer status,adjust the video quality
RCTimestampMode RCModeEnum = C.RC_TIMESTAMP_MODE //rate control based timestamp
RCBitrateModePostSkip RCModeEnum = C.RC_BITRATE_MODE_POST_SKIP ///< this is in-building RC MODE, WILL BE DELETED after algorithm tuning!
RCOffMode RCModeEnum = C.RC_OFF_MODE ///< rate control off mode
)
type SliceModeEnum uint
const (
SMSingleSlice SliceModeEnum = C.SM_SINGLE_SLICE ///< | SliceNum==1
SMFixedslcnumSlice SliceModeEnum = C.SM_FIXEDSLCNUM_SLICE ///< | according to SliceNum | enabled dynamic slicing for multi-thread
SMRasterSlice SliceModeEnum = C.SM_RASTER_SLICE ///< | according to SlicesAssign | need input of MB numbers each slice. In addition, if other constraint in SSliceArgument is presented, need to follow the constraints. Typically if MB num and slice size are both constrained, re-encoding may be involved.
SMSizelimitedSlice SliceModeEnum = C.SM_SIZELIMITED_SLICE ///< | according to SliceSize | slicing according to size, the slicing will be dynamic(have no idea about slice_nums until encoding current frame)
)
// NewParams returns default openh264 codec specific parameters. // NewParams returns default openh264 codec specific parameters.
func NewParams() (Params, error) { func NewParams() (Params, error) {
return Params{ return Params{
BaseParams: codec.BaseParams{ BaseParams: codec.BaseParams{
BitRate: 100000, BitRate: 100000,
}, },
UsageType: CameraVideoRealTime,
RCMode: RCBitrateMode,
EnableFrameSkip: true,
MaxNalSize: 0,
IntraPeriod: 30,
MultipleThreadIdc: 0, // Defaults to 0, so that it'll automatically use multi threads when needed
SliceNum: 1, // Defaults to single NAL unit mode
SliceMode: SMSizelimitedSlice,
SliceSizeConstraint: 12800,
}, nil }, nil
} }

Binary file not shown.

View File

@@ -3,6 +3,8 @@ package opus
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"sync"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/io/audio"
@@ -14,7 +16,7 @@ import (
/* /*
#include <opus.h> #include <opus.h>
int bridge_encoder_set_bitrate(OpusEncoder *e, opus_int32 bitrate) int pion_set_encoder_bitrate(OpusEncoder *e, opus_int32 bitrate)
{ {
return opus_encoder_ctl(e, OPUS_SET_BITRATE(bitrate)); return opus_encoder_ctl(e, OPUS_SET_BITRATE(bitrate));
} }
@@ -25,6 +27,8 @@ type encoder struct {
inBuff wave.Audio inBuff wave.Audio
reader audio.Reader reader audio.Reader
engine *C.OpusEncoder engine *C.OpusEncoder
mu sync.Mutex
} }
func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) { func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
@@ -79,6 +83,12 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, err return nil, func() {}, err
} }
e.mu.Lock()
defer e.mu.Unlock()
if e.engine == nil {
return nil, nil, io.EOF
}
encoded := make([]byte, 1024) encoded := make([]byte, 1024)
var n C.opus_int32 var n C.opus_int32
switch b := buff.(type) { switch b := buff.(type) {
@@ -110,7 +120,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
} }
func (e *encoder) SetBitRate(bitRate int) error { func (e *encoder) SetBitRate(bitRate int) error {
cerror := C.bridge_encoder_set_bitrate( cerror := C.pion_set_encoder_bitrate(
e.engine, e.engine,
C.int(bitRate), C.int(bitRate),
) )
@@ -121,11 +131,13 @@ func (e *encoder) SetBitRate(bitRate int) error {
return nil return nil
} }
func (e *encoder) ForceKeyFrame() error { func (e *encoder) Controller() codec.EncoderController {
panic("ForceKeyFrame is not implemented") return e
} }
func (e *encoder) Close() error { func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.engine == nil { if e.engine == nil {
return nil return nil
} }

View File

@@ -8,5 +8,6 @@ package opus
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-arm64.a -lm //#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-arm64.a -lm
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-x64.a -lm //#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-x64.a -lm
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-x64.a //#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-x64.a
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-arm64.a
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-windows-x64.a //#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-windows-x64.a
import "C" import "C"

View File

@@ -3,11 +3,28 @@ package opus
import ( import (
"testing" "testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest" "github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave" "github.com/pion/mediadevices/pkg/wave"
) )
func TestShouldImplementBitRateControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestEncoder(t *testing.T) { func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) { t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams() p, err := NewParams()
@@ -40,4 +57,23 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderReadAfterCloseTest(t, &p,
prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
},
wave.NewInt16Interleaved(wave.ChunkInfo{
Len: 960,
SamplingRate: 48000,
Channels: 2,
}),
)
})
} }

View File

@@ -65,6 +65,25 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderReadAfterCloseTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}) })
} }
} }

View File

@@ -62,6 +62,7 @@ import (
"image" "image"
"io" "io"
"sync" "sync"
"sync/atomic"
"unsafe" "unsafe"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
@@ -100,6 +101,8 @@ type encoderVP8 struct {
rate *framerateDetector rate *framerateDetector
forceKeyFrame atomic.Bool
mu sync.Mutex mu sync.Mutex
closed bool closed bool
} }
@@ -304,10 +307,11 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
yuvImg := img.(*image.YCbCr) yuvImg := img.(*image.YCbCr)
kf := e.frameCnt%e.params.KeyFrameInterval == 0 kf := e.frameCnt%e.params.KeyFrameInterval == 0
@@ -315,7 +319,7 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
e.frParam.data.framerate = C.uint(e.rate.Calc()) e.frParam.data.framerate = C.uint(e.rate.Calc())
if kf { if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
// Key frame // Key frame
C.setForceKFFlagVP8(&e.picParam, 1) C.setForceKFFlagVP8(&e.picParam, 1)
C.setFrameTypeFlagVP8(&e.picParam, 0) C.setFrameTypeFlagVP8(&e.picParam, 0)
@@ -541,12 +545,12 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
return encoded, func() {}, err return encoded, func() {}, err
} }
func (e *encoderVP8) SetBitRate(b int) error { func (e *encoderVP8) Controller() codec.EncoderController {
panic("SetBitRate is not implemented") return e
} }
func (e *encoderVP8) ForceKeyFrame() error { func (e *encoderVP8) ForceKeyFrame() {
panic("ForceKeyFrame is not implemented") e.forceKeyFrame.Store(true)
} }
func (e *encoderVP8) Close() error { func (e *encoderVP8) Close() error {

View File

@@ -0,0 +1,25 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
import (
"github.com/pion/mediadevices/pkg/codec"
"testing"
)
func TestVP8ShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoderVP8{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestVP8ShouldImplementKeyFrameControl(t *testing.T) {
e := &encoderVP8{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -45,6 +45,7 @@ import (
"image" "image"
"io" "io"
"sync" "sync"
"sync/atomic"
"unsafe" "unsafe"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
@@ -92,6 +93,8 @@ type encoderVP9 struct {
rate *framerateDetector rate *framerateDetector
forceKeyFrame atomic.Bool
mu sync.Mutex mu sync.Mutex
closed bool closed bool
} }
@@ -293,10 +296,11 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
yuvImg := img.(*image.YCbCr) yuvImg := img.(*image.YCbCr)
kf := e.frameCnt%e.params.KeyFrameInterval == 0 kf := e.frameCnt%e.params.KeyFrameInterval == 0
@@ -304,7 +308,7 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
e.frParam.data.framerate = C.uint(e.rate.Calc()) e.frParam.data.framerate = C.uint(e.rate.Calc())
if kf { if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
C.setForceKFFlag9(&e.picParam, 1) C.setForceKFFlag9(&e.picParam, 1)
C.setFrameTypeFlagVP9(&e.picParam, 0) C.setFrameTypeFlagVP9(&e.picParam, 0)
e.picParam.refresh_frame_flags = 0 e.picParam.refresh_frame_flags = 0
@@ -476,12 +480,12 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
return encoded, func() {}, err return encoded, func() {}, err
} }
func (e *encoderVP9) SetBitRate(b int) error { func (e *encoderVP9) Controller() codec.EncoderController {
panic("SetBitRate is not implemented") return e
} }
func (e *encoderVP9) ForceKeyFrame() error { func (e *encoderVP9) ForceKeyFrame() {
panic("ForceKeyFrame is not implemented") e.forceKeyFrame.Store(true)
} }
func (e *encoderVP9) Close() error { func (e *encoderVP9) Close() error {

View File

@@ -0,0 +1,25 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
import (
"github.com/pion/mediadevices/pkg/codec"
"testing"
)
func TestVP9ShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoderVP9{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestVP9ShouldImplementKeyFrameControl(t *testing.T) {
e := &encoderVP9{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -219,10 +219,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
yuvImg := img.(*image.YCbCr) yuvImg := img.(*image.YCbCr)
bounds := yuvImg.Bounds() bounds := yuvImg.Bounds()
height := C.int(bounds.Dy()) height := C.int(bounds.Dy())
@@ -295,10 +296,6 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err return encoded, func() {}, err
} }
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error { func (e *encoder) ForceKeyFrame() error {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()
@@ -306,6 +303,10 @@ func (e *encoder) ForceKeyFrame() error {
return nil return nil
} }
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error { func (e *encoder) Close() error {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()

View File

@@ -60,6 +60,25 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderReadAfterCloseTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}) })
} }
} }
@@ -193,7 +212,7 @@ func TestRequestKeyFrame(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
rel() rel()
r.ForceKeyFrame() r.Controller().(codec.KeyFrameController).ForceKeyFrame()
_, rel, err = r.Read() _, rel, err = r.Read()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -210,3 +229,19 @@ func TestRequestKeyFrame(t *testing.T) {
} }
} }
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -18,6 +18,7 @@ typedef struct Encoder {
x264_t *h; x264_t *h;
x264_picture_t pic_in; x264_picture_t pic_in;
x264_param_t param; x264_param_t param;
int force_key_frame;
} Encoder; } Encoder;
Encoder *enc_new(x264_param_t param, char *preset, int *rc) { Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
@@ -85,8 +86,14 @@ Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
e->pic_in.img.plane[0] = y; e->pic_in.img.plane[0] = y;
e->pic_in.img.plane[1] = cb; e->pic_in.img.plane[1] = cb;
e->pic_in.img.plane[2] = cr; e->pic_in.img.plane[2] = cr;
if (e->force_key_frame) {
e->pic_in.i_type = X264_TYPE_IDR;
} else {
e->pic_in.i_type = X264_TYPE_AUTO;
}
int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &pic_out); int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &pic_out);
e->force_key_frame = 0;
Slice s = {.data_len = frame_size}; Slice s = {.data_len = frame_size};
if (frame_size <= 0) { if (frame_size <= 0) {
*rc = ERR_ENCODE; *rc = ERR_ENCODE;

View File

@@ -102,10 +102,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
img, _, err := e.r.Read() img, release, err := e.r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
defer release()
yuvImg := img.(*image.YCbCr) yuvImg := img.(*image.YCbCr)
var rc C.int var rc C.int
@@ -124,12 +125,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err return encoded, func() {}, err
} }
func (e *encoder) SetBitRate(b int) error { // TODO: Implement bit rate control
panic("SetBitRate is not implemented") //var _ codec.BitRateController = (*encoder)(nil)
}
func (e *encoder) ForceKeyFrame() error { func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented") e.engine.force_key_frame = C.int(1)
return nil
}
func (e *encoder) Controller() codec.EncoderController {
return e
} }
func (e *encoder) Close() error { func (e *encoder) Close() error {

View File

@@ -4,6 +4,7 @@ import (
"image" "image"
"testing" "testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest" "github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
@@ -45,4 +46,42 @@ func TestEncoder(t *testing.T) {
}, },
}) })
}) })
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderReadAfterCloseTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
} }

View File

@@ -0,0 +1,28 @@
package availability
import (
"errors"
)
var (
ErrUnimplemented = NewError("not implemented")
ErrBusy = NewError("device or resource busy")
ErrNoDevice = NewError("no such device")
)
type errorString struct {
s string
}
func NewError(text string) error {
return &errorString{text}
}
func IsError(err error) bool {
var target *errorString
return errors.As(err, &target)
}
func (e *errorString) Error() string {
return e.s
}

View File

@@ -17,6 +17,11 @@ type camera struct {
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers camera devices. This is part of an experimental API.
func Initialize() {
devices, err := avfoundation.Devices(avfoundation.Video) devices, err := avfoundation.Devices(avfoundation.Video)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -27,6 +32,7 @@ func init() {
driver.GetManager().Register(cam, driver.Info{ driver.GetManager().Register(cam, driver.Info{
Label: device.UID, Label: device.UID,
DeviceType: driver.Camera, DeviceType: driver.Camera,
Name: device.Name,
}) })
} }
} }

View File

@@ -8,9 +8,15 @@ import (
"errors" "errors"
"image" "image"
"io" "io"
"math"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"sync" "sync"
"syscall"
"time"
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/blackjack/webcam" "github.com/blackjack/webcam"
"github.com/pion/mediadevices/pkg/driver" "github.com/pion/mediadevices/pkg/driver"
@@ -59,6 +65,8 @@ var (
} }
) )
const bufCount = 2
// Camera implementation using v4l2 // Camera implementation using v4l2
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev // Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
type camera struct { type camera struct {
@@ -69,10 +77,23 @@ type camera struct {
started bool started bool
mutex sync.Mutex mutex sync.Mutex
cancel func() cancel func()
prevFrameTime time.Time
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers camera devices. This is part of an experimental API.
func Initialize() {
// Clear all registered camera devices to prevent duplicates.
// If first initalize call, this will be a noop.
manager := driver.GetManager()
for _, d := range manager.Query(driver.FilterVideoRecorder()) {
manager.Delete(d.ID())
}
discovered := make(map[string]struct{}) discovered := make(map[string]struct{})
discover(discovered, "/dev/v4l/by-id/*")
discover(discovered, "/dev/v4l/by-path/*") discover(discovered, "/dev/v4l/by-path/*")
discover(discovered, "/dev/video*") discover(discovered, "/dev/video*")
} }
@@ -101,7 +122,22 @@ func discover(discovered map[string]struct{}, pattern string) {
if reallink == prioritizedDevice { if reallink == prioritizedDevice {
priority = driver.PriorityHigh priority = driver.PriorityHigh
} }
var name, busInfo string
if webcamCam, err := webcam.Open(cam.path); err == nil {
defer webcamCam.Close()
name, _ = webcamCam.GetName()
busInfo, _ = webcamCam.GetBusInfo()
}
driver.GetManager().Register(cam, driver.Info{ driver.GetManager().Register(cam, driver.Info{
// Source: https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-querycap.html
// Name of the device, a NUL-terminated UTF-8 string. For example: “Yoyodyne TV/FM”. One driver may support
// different brands or models of video hardware. This information is intended for users, for example in a
// menu of available devices. Since multiple TV cards of the same brand may be installed which are
// supported by the same driver, this name should be combined with the character device file name
// (e.g. /dev/video2) or the bus_info string to avoid ambiguities.
Name: name + LabelSeparator + busInfo,
Label: label + LabelSeparator + reallink, Label: label + LabelSeparator + reallink,
DeviceType: driver.Camera, DeviceType: driver.Camera,
Priority: priority, Priority: priority,
@@ -133,14 +169,32 @@ func newCamera(path string) *camera {
return c return c
} }
func getCameraReadTimeout() uint32 {
// default to 5 seconds
var readTimeoutSec uint32 = 5
if val, ok := os.LookupEnv("PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"); ok {
if valInt, err := strconv.Atoi(val); err == nil {
if valInt > 0 {
readTimeoutSec = uint32(valInt)
}
}
}
return readTimeoutSec
}
func (c *camera) Open() error { func (c *camera) Open() error {
cam, err := webcam.Open(c.path) cam, err := webcam.Open(c.path)
if err != nil { if err != nil {
return err return err
} }
// Late frames should be discarded. Buffering should be handled in higher level. // Buffering should be handled in higher level.
cam.SetBufferCount(1) err = cam.SetBufferCount(bufCount)
if err != nil {
return err
}
c.prevFrameTime = time.Now()
c.cam = cam c.cam = cam
return nil return nil
} }
@@ -192,6 +246,8 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
cam := c.cam cam := c.cam
readTimeoutSec := getCameraReadTimeout()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c.cancel = cancel c.cancel = cancel
var buf []byte var buf []byte
@@ -207,7 +263,14 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
err := cam.WaitForFrame(5) // 5 seconds if p.DiscardFramesOlderThan != 0 && time.Now().Sub(c.prevFrameTime) >= p.DiscardFramesOlderThan {
for i := 0; i < bufCount; i++ {
_ = cam.WaitForFrame(readTimeoutSec)
_, _ = cam.ReadFrame()
}
}
err := cam.WaitForFrame(readTimeoutSec)
switch err.(type) { switch err.(type) {
case nil: case nil:
case *webcam.Timeout: case *webcam.Timeout:
@@ -223,6 +286,10 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, func() {}, err return nil, func() {}, err
} }
if p.DiscardFramesOlderThan != 0 {
c.prevFrameTime = time.Now()
}
// Frame is empty. // Frame is empty.
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount. // Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
if len(b) == 0 { if len(b) == 0 {
@@ -256,13 +323,31 @@ func (c *camera) Properties() []prop.Media {
} }
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 { if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
properties = append(properties, prop.Media{ framerates := c.cam.GetSupportedFramerates(format, uint32(frameSize.MaxWidth), uint32(frameSize.MaxHeight))
Video: prop.Video{ // If the camera doesn't support framerate, we just add the resolution and format
Width: int(frameSize.MaxWidth), if len(framerates) == 0 {
Height: int(frameSize.MaxHeight), properties = append(properties, prop.Media{
FrameFormat: supportedFormat, Video: prop.Video{
}, Width: int(frameSize.MaxWidth),
}) Height: int(frameSize.MaxHeight),
FrameFormat: supportedFormat,
},
})
continue
}
for _, framerate := range framerates {
for _, fps := range enumFramerate(framerate) {
properties = append(properties, prop.Media{
Video: prop.Video{
Width: int(frameSize.MaxWidth),
Height: int(frameSize.MaxHeight),
FrameFormat: supportedFormat,
FrameRate: fps,
},
})
}
}
} else { } else {
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions // FIXME: we should probably use a custom data structure to capture all of the supported resolutions
for _, supportedResolution := range supportedResolutions { for _, supportedResolution := range supportedResolutions {
@@ -281,16 +366,100 @@ func (c *camera) Properties() []prop.Media {
continue continue
} }
properties = append(properties, prop.Media{ framerates := c.cam.GetSupportedFramerates(format, uint32(width), uint32(height))
Video: prop.Video{ if len(framerates) == 0 {
Width: width, properties = append(properties, prop.Media{
Height: height, Video: prop.Video{
FrameFormat: supportedFormat, Width: width,
}, Height: height,
}) FrameFormat: supportedFormat,
},
})
continue
}
for _, framerate := range framerates {
for _, fps := range enumFramerate(framerate) {
properties = append(properties, prop.Media{
Video: prop.Video{
Width: width,
Height: height,
FrameFormat: supportedFormat,
FrameRate: fps,
},
})
}
}
} }
} }
} }
} }
return properties return properties
} }
func (c *camera) IsAvailable() (bool, error) {
var err error
// close the opened file descriptor as quickly as possible and in all cases, including panics
func() {
var cam *webcam.Webcam
if cam, err = webcam.Open(c.path); err == nil {
defer cam.Close()
var index int32
// "Drivers must implement all the input ioctls when the device has one or more inputs..."
// Source: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/video.html?highlight=vidioc_enuminput
if index, err = cam.GetInput(); err == nil {
err = cam.SelectInput(uint32(index))
}
}
}()
var errno syscall.Errno
errors.As(err, &errno)
// See https://man7.org/linux/man-pages/man3/errno.3.html
switch {
case err == nil:
return true, nil
case errno == syscall.EBUSY:
return false, availability.ErrBusy
case errno == syscall.ENODEV || errno == syscall.ENOENT:
return false, availability.ErrNoDevice
default:
return false, availability.NewError(errno.Error())
}
}
// enumFramerate returns a list of fps options from a FrameRate struct.
// discrete framerates will return a list of 1 fps element.
// stepwise framerates will return a list of all possible fps options.
func enumFramerate(framerate webcam.FrameRate) []float32 {
var framerates []float32
if framerate.StepNumerator == 0 && framerate.StepDenominator == 0 {
fr, err := calcFramerate(framerate.MaxNumerator, framerate.MaxDenominator)
if err != nil {
return framerates
}
framerates = append(framerates, fr)
} else {
for n := framerate.MinNumerator; n <= framerate.MaxNumerator; n += framerate.StepNumerator {
for d := framerate.MinDenominator; d <= framerate.MaxDenominator; d += framerate.StepDenominator {
fr, err := calcFramerate(n, d)
if err != nil {
continue
}
framerates = append(framerates, fr)
}
}
}
return framerates
}
// calcFramerate turns fraction into a float32 fps value.
func calcFramerate(numerator uint32, denominator uint32) (float32, error) {
if denominator == 0 {
return 0, errors.New("framerate denominator is zero")
}
// round to three decimal places to avoid floating point precision issues
return float32(math.Round(1000.0/((float64(numerator))/float64(denominator))) / 1000), nil
}

View File

@@ -57,6 +57,7 @@ func TestDiscover(t *testing.T) {
drvs[0].Info().Label, drvs[0].Info().Label,
drvs[1].Info().Label, drvs[1].Info().Label,
} }
// Returned drivers are unordered. Sort to get static result. // Returned drivers are unordered. Sort to get static result.
sort.Sort(sort.StringSlice(labels)) sort.Sort(sort.StringSlice(labels))
@@ -70,3 +71,123 @@ func TestDiscover(t *testing.T) {
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label) t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
} }
} }
func TestDiscoverByID(t *testing.T) {
const (
shortName = "id-unittest-video0"
shortName2 = "id-unittest-video1"
longName = "id-unittest-long-device-name:0:1:2:3"
)
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
byIdDir := filepath.Join(dir, "v4l", "by-id")
if err := os.MkdirAll(byIdDir, 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
t.Fatal(err)
}
if err := os.Symlink(
filepath.Join(dir, shortName),
filepath.Join(byIdDir, longName),
); err != nil {
t.Fatal(err)
}
discovered := make(map[string]struct{})
discover(discovered, filepath.Join(byIdDir, "*"))
discover(discovered, filepath.Join(dir, "id-unittest-video*"))
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
// Ignore real cameras.
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "id-unittest")
})
if len(drvs) != 2 {
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
}
labels := []string{
drvs[0].Info().Label,
drvs[1].Info().Label,
}
// Returned drivers are unordered. Sort to get static result.
sort.Sort(sort.StringSlice(labels))
expected := longName + LabelSeparator + shortName
if label := labels[0]; label != expected {
t.Errorf("Expected label: %s, got: %s", expected, label)
}
expectedNoLink := shortName2 + LabelSeparator + shortName2
if label := labels[1]; label != expectedNoLink {
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
}
}
func TestGetCameraReadTimeout(t *testing.T) {
var expected uint32 = 5
value := getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
envVarName := "PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"
os.Setenv(envVarName, "text")
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
os.Setenv(envVarName, "-1")
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
os.Setenv(envVarName, "1")
expected = 1
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
}
func TestCalcFramerate(t *testing.T) {
framerates := []struct {
numerator uint32
denominator uint32
expected float32
}{
{1, 10, 10.0},
{1, 15, 15.0},
{1, 30, 30.0},
{1, 60, 60.0},
{1, 120, 120.0},
}
for _, framerate := range framerates {
value, err := calcFramerate(framerate.numerator, framerate.denominator)
if err != nil {
t.Fatal(err)
}
// make sure we do not have any rounding errors
if value != framerate.expected {
t.Errorf("Expected: %f, got: %f", framerate.expected, value)
}
}
// divide by zero check
_, err := calcFramerate(1, 0)
if err == nil {
t.Errorf("Expected divide by zero error")
}
}

View File

@@ -8,6 +8,18 @@
#include "camera_windows.hpp" #include "camera_windows.hpp"
#include "_cgo_export.h" #include "_cgo_export.h"
imageProp* getProp(camera* cam, int i)
{
return &cam->props[i];
}
char* getName(cameraList* list, int i)
{
return list->name[i];
}
// printErr shows string representation of HRESULT. // printErr shows string representation of HRESULT.
// This is for debugging. // This is for debugging.
void printErr(HRESULT hr) void printErr(HRESULT hr)
@@ -76,6 +88,7 @@ int listCamera(cameraList* list, const char** errstr)
{ {
list->name[i] = getCameraName(moniker); list->name[i] = getCameraName(moniker);
moniker->Release(); moniker->Release();
i++;
} }
} }

View File

@@ -32,6 +32,11 @@ type camera struct {
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers camera devices. This is part of an experimental API.
func Initialize() {
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED) C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
var list C.cameraList var list C.cameraList

View File

@@ -40,15 +40,9 @@ int listResolution(camera* cam, const char** errstr);
int listCamera(cameraList* list, const char** errstr); int listCamera(cameraList* list, const char** errstr);
int freeCameraList(cameraList* list, const char** errstr); int freeCameraList(cameraList* list, const char** errstr);
inline imageProp* getProp(camera* cam, int i) imageProp* getProp(camera* cam, int i);
{
return &cam->props[i];
}
inline char* getName(cameraList* list, int i) char* getName(cameraList* list, int i);
{
return list->name[i];
}
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -1,6 +1,7 @@
package driver package driver
import ( import (
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
@@ -30,6 +31,7 @@ type Info struct {
Label string Label string
DeviceType DeviceType DeviceType DeviceType
Priority Priority Priority Priority
Name string
} }
type Adapter interface { type Adapter interface {
@@ -44,3 +46,14 @@ type Driver interface {
Info() Info Info() Info
Status() State Status() State
} }
type AvailabilityAdapter interface {
IsAvailable() (bool, error)
}
func IsAvailable(d Driver) (bool, error) {
if aa, ok := d.(AvailabilityAdapter); ok {
return aa.IsAvailable()
}
return false, availability.ErrUnimplemented
}

View File

@@ -1,5 +1,7 @@
package driver package driver
import "sync"
// FilterFn is being used to decide if a driver should be included in the // FilterFn is being used to decide if a driver should be included in the
// query result. // query result.
type FilterFn func(Driver) bool type FilterFn func(Driver) bool
@@ -55,6 +57,7 @@ func FilterNot(filter FilterFn) FilterFn {
// Manager is a singleton to manage multiple drivers and their states // Manager is a singleton to manage multiple drivers and their states
type Manager struct { type Manager struct {
mu sync.Mutex
drivers map[string]Driver drivers map[string]Driver
} }
@@ -69,6 +72,8 @@ func GetManager() *Manager {
// Register registers adapter to be discoverable by Query // Register registers adapter to be discoverable by Query
func (m *Manager) Register(a Adapter, info Info) error { func (m *Manager) Register(a Adapter, info Info) error {
m.mu.Lock()
defer m.mu.Unlock()
d := wrapAdapter(a, info) d := wrapAdapter(a, info)
m.drivers[d.ID()] = d m.drivers[d.ID()] = d
return nil return nil
@@ -76,6 +81,8 @@ func (m *Manager) Register(a Adapter, info Info) error {
// Query queries by using f to filter drivers, and simply return the filtered results. // Query queries by using f to filter drivers, and simply return the filtered results.
func (m *Manager) Query(f FilterFn) []Driver { func (m *Manager) Query(f FilterFn) []Driver {
m.mu.Lock()
defer m.mu.Unlock()
results := make([]Driver, 0) results := make([]Driver, 0)
for _, d := range m.drivers { for _, d := range m.drivers {
if ok := f(d); ok { if ok := f(d); ok {
@@ -85,3 +92,10 @@ func (m *Manager) Query(f FilterFn) []Driver {
return results return results
} }
// Delete deletes a driver from manager given its ID
func (m *Manager) Delete(id string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.drivers, id)
}

View File

@@ -1,13 +1,17 @@
package driver package driver
import ( import (
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/stretchr/testify/assert"
"testing" "testing"
) )
func filterTrue(d Driver) bool { func filterTrue(_ Driver) bool {
return true return true
} }
func filterFalse(d Driver) bool { func filterFalse(_ Driver) bool {
return false return false
} }
@@ -40,3 +44,71 @@ func TestFilterAnd(t *testing.T) {
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true") t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
} }
} }
type fakeVideoAdapter struct{}
func (a *fakeVideoAdapter) Open() error { return nil }
func (a *fakeVideoAdapter) Close() error { return nil }
func (a *fakeVideoAdapter) Properties() []prop.Media { return nil }
func (a *fakeVideoAdapter) VideoRecord(_ prop.Media) (r video.Reader, err error) { return nil, nil }
type fakeAudioAdapter struct{}
func (a *fakeAudioAdapter) Open() error { return nil }
func (a *fakeAudioAdapter) Close() error { return nil }
func (a *fakeAudioAdapter) Properties() []prop.Media { return nil }
func (a *fakeAudioAdapter) AudioRecord(_ prop.Media) (r audio.Reader, err error) { return nil, nil }
type fakeAdapter struct{}
func (a *fakeAdapter) Open() error { return nil }
func (a *fakeAdapter) Close() error { return nil }
func (a *fakeAdapter) Properties() []prop.Media { return nil }
func TestRegister(t *testing.T) {
m := GetManager()
va := &fakeVideoAdapter{}
err := m.Register(va, Info{})
assert.NoError(t, err, "cannot register video adapter")
assert.Equal(t, len(m.Query(filterTrue)), 1)
aa := &fakeAudioAdapter{}
err = m.Register(aa, Info{})
assert.NoError(t, err, "cannot register audio adapter")
assert.Equal(t, len(m.Query(filterTrue)), 2)
a := &fakeAdapter{}
assert.Panics(t, func() { m.Register(a, Info{}) }, "should not register adapter that is neither audio nor video")
assert.Equal(t, len(m.Query(filterTrue)), 2)
}
func TestRegisterSync(t *testing.T) {
m := GetManager()
start := make(chan struct{})
race := func() {
<-start
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
}
go race()
go race()
close(start)
}
func TestQuerySync(t *testing.T) {
m := GetManager()
start := make(chan struct{})
race := func() {
<-start
m.Query(filterTrue)
}
go race()
go race()
close(start)
// write while reading
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
}

View File

@@ -1,10 +1,12 @@
package microphone package microphone
import ( import (
"context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"sync"
"time" "time"
"unsafe" "unsafe"
@@ -32,10 +34,16 @@ var (
type microphone struct { type microphone struct {
malgo.DeviceInfo malgo.DeviceInfo
chunkChan chan []byte chunkChan chan []byte
deviceCloseFunc func()
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers active playback or capture devices. This is part of an experimental API.
func Initialize() {
var err error var err error
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) { ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
logger.Debugf("%v\n", message) logger.Debugf("%v\n", message)
@@ -60,6 +68,7 @@ func init() {
Label: device.ID.String(), Label: device.ID.String(),
DeviceType: driver.Microphone, DeviceType: driver.Microphone,
Priority: priority, Priority: priority,
Name: info.Name(),
}) })
} }
} }
@@ -87,9 +96,8 @@ func (m *microphone) Open() error {
} }
func (m *microphone) Close() error { func (m *microphone) Close() error {
if m.chunkChan != nil { if m.deviceCloseFunc != nil {
close(m.chunkChan) m.deviceCloseFunc()
m.chunkChan = nil
} }
return nil return nil
} }
@@ -111,6 +119,9 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
config.PerformanceProfile = malgo.LowLatency config.PerformanceProfile = malgo.LowLatency
config.Capture.Channels = uint32(inputProp.ChannelCount) config.Capture.Channels = uint32(inputProp.ChannelCount)
config.SampleRate = uint32(inputProp.SampleRate) config.SampleRate = uint32(inputProp.SampleRate)
config.PeriodSizeInMilliseconds = uint32(inputProp.Latency.Milliseconds())
//FIX: Turn on the microphone with the current device id
config.Capture.DeviceID = m.ID.Pointer()
if inputProp.SampleSize == 4 && inputProp.IsFloat { if inputProp.SampleSize == 4 && inputProp.IsFloat {
config.Capture.Format = malgo.FormatF32 config.Capture.Format = malgo.FormatF32
} else if inputProp.SampleSize == 2 && !inputProp.IsFloat { } else if inputProp.SampleSize == 2 && !inputProp.IsFloat {
@@ -119,26 +130,44 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
return nil, errUnsupportedFormat return nil, errUnsupportedFormat
} }
cancelCtx, cancel := context.WithCancel(context.Background())
onRecvChunk := func(_, chunk []byte, framecount uint32) { onRecvChunk := func(_, chunk []byte, framecount uint32) {
m.chunkChan <- chunk select {
case <-cancelCtx.Done():
case m.chunkChan <- chunk:
}
} }
callbacks.Data = onRecvChunk callbacks.Data = onRecvChunk
device, err := malgo.InitDevice(ctx.Context, config, callbacks) device, err := malgo.InitDevice(ctx.Context, config, callbacks)
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
err = device.Start() err = device.Start()
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
var closeDeviceOnce sync.Once
m.deviceCloseFunc = func() {
closeDeviceOnce.Do(func() {
cancel() // Unblock onRecvChunk
device.Uninit()
if m.chunkChan != nil {
close(m.chunkChan)
m.chunkChan = nil
}
})
}
var reader audio.Reader = audio.ReaderFunc(func() (wave.Audio, func(), error) { var reader audio.Reader = audio.ReaderFunc(func() (wave.Audio, func(), error) {
chunk, ok := <-m.chunkChan chunk, ok := <-m.chunkChan
if !ok { if !ok {
device.Stop() m.deviceCloseFunc()
device.Uninit()
return nil, func() {}, io.EOF return nil, func() {}, io.EOF
} }
@@ -168,36 +197,39 @@ func (m *microphone) Properties() []prop.Media {
isBigEndian = true isBigEndian = true
} }
for ch := m.MinChannels; ch <= m.MaxChannels; ch++ { for _, format := range m.Formats {
// FIXME: Currently support 48kHz only. We need to implement a resampler first. // FIXME: Currently support 48kHz only. We need to implement a resampler first.
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep { // for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
sampleRate := 48000 sampleRate := 48000
for i := 0; i < int(m.FormatCount); i++ { supportedProp := prop.Media{
format := m.Formats[i] Audio: prop.Audio{
ChannelCount: int(format.Channels),
supportedProp := prop.Media{ SampleRate: int(sampleRate),
Audio: prop.Audio{ IsBigEndian: isBigEndian,
ChannelCount: int(ch), // miniaudio only supports interleaved at the moment
SampleRate: int(sampleRate), IsInterleaved: true,
IsBigEndian: isBigEndian, // FIXME: should change this to a less discrete value
// miniaudio only supports interleaved at the moment Latency: time.Millisecond * 20,
IsInterleaved: true, },
// FIXME: should change this to a less discrete value
Latency: time.Millisecond * 20,
},
}
switch malgo.FormatType(format) {
case malgo.FormatF32:
supportedProp.SampleSize = 4
supportedProp.IsFloat = true
case malgo.FormatS16:
supportedProp.SampleSize = 2
supportedProp.IsFloat = false
}
supportedProps = append(supportedProps, supportedProp)
} }
supportedFormat := true
switch malgo.FormatType(format.Format) {
case malgo.FormatF32:
supportedProp.SampleSize = 4
supportedProp.IsFloat = true
case malgo.FormatS16:
supportedProp.SampleSize = 2
supportedProp.IsFloat = false
default:
supportedFormat = false
}
if !supportedFormat {
logger.Warnf("format '%s' not supported", format.Format)
continue
}
supportedProps = append(supportedProps, supportedProp)
// } // }
} }
return supportedProps return supportedProps

View File

@@ -20,6 +20,11 @@ type screen struct {
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers active displays. This is part of an experimental API.
func Initialize() {
activeDisplays := screenshot.NumActiveDisplays() activeDisplays := screenshot.NumActiveDisplays()
for i := 0; i < activeDisplays; i++ { for i := 0; i < activeDisplays; i++ {
priority := driver.PriorityNormal priority := driver.PriorityNormal

View File

@@ -22,6 +22,11 @@ func deviceID(num int) string {
} }
func init() { func init() {
Initialize()
}
// Initialize finds and registers active displays. This is part of an experimental API.
func Initialize() {
dp, err := openDisplay() dp, err := openDisplay()
if err != nil { if err != nil {
// No x11 display available. // No x11 display available.

View File

@@ -3,7 +3,13 @@
go-vnc is a VNC library for Go, initially supporting VNC clients but go-vnc is a VNC library for Go, initially supporting VNC clients but
with the goal of eventually implementing a VNC server. with the goal of eventually implementing a VNC server.
This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143). This library implements [RFC 6143][rfc6143].
## RFCs
### Implemented
- **RFC **: [The Remote Framebuffer Protocol][rfc6143]
[rfc6143]: http://tools.ietf.org/html/rfc6143
## Usage & Installation ## Usage & Installation

View File

@@ -18,13 +18,32 @@ type Encoding interface {
// the proper data. // the proper data.
Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error) Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error)
} }
type CursorEncoding struct {
}
func (*CursorEncoding) Type() int32 {
return -239
}
func (*CursorEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) {
size := int(rect.Height) * int(rect.Width) * int(c.PixelFormat.BPP) / 8
pixelBytes := make([]uint8, size)
if _, err := io.ReadFull(r, pixelBytes); err != nil {
return nil, err
}
mask := ((int(rect.Width) + 7) / 8) * int(rect.Height)
maskBytes := make([]uint8, mask)
if _, err := io.ReadFull(r, maskBytes); err != nil {
return nil, err
}
return &CursorEncoding{}, nil
}
// RawEncoding is raw pixel data sent by the server. // RawEncoding is raw pixel data sent by the server.
// //
// See RFC 6143 Section 7.7.1 // See RFC 6143 Section 7.7.1
type RawEncoding struct { type RawEncoding struct {
Colors []Color Colors []Color
RawPixel []uint32 //RGBA RawPixel []uint32 //RGBA
} }
func (*RawEncoding) Type() int32 { func (*RawEncoding) Type() int32 {
@@ -32,6 +51,7 @@ func (*RawEncoding) Type() int32 {
} }
func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) {
//fmt.Println("RawEncoding")
bytesPerPixel := c.PixelFormat.BPP / 8 bytesPerPixel := c.PixelFormat.BPP / 8
pixelBytes := make([]uint8, bytesPerPixel) pixelBytes := make([]uint8, bytesPerPixel)
@@ -41,7 +61,7 @@ func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding,
} }
colors := make([]Color, int(rect.Height)*int(rect.Width)) colors := make([]Color, int(rect.Height)*int(rect.Width))
rawPixels:=make([]uint32,int(rect.Height)*int(rect.Width)) rawPixels := make([]uint32, int(rect.Height)*int(rect.Width))
for y := uint16(0); y < rect.Height; y++ { for y := uint16(0); y < rect.Height; y++ {
for x := uint16(0); x < rect.Width; x++ { for x := uint16(0); x < rect.Width; x++ {
if _, err := io.ReadFull(r, pixelBytes); err != nil { if _, err := io.ReadFull(r, pixelBytes); err != nil {
@@ -62,24 +82,30 @@ func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding,
color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax))
color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax))
color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax))
if c.PixelFormat.BPP == 16 {
color.B = color.B<<3 | color.B>>2
color.G = color.G<<2 | color.G>>2
color.R = color.R<<3 | color.R>>2
}
} else { } else {
*color = c.ColorMap[rawPixel] *color = c.ColorMap[rawPixel]
} }
rawPixels[int(y)*int(rect.Width)+int(x)]=uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R) rawPixels[int(y)*int(rect.Width)+int(x)] = uint32(0xff)<<24 | uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R)
//fmt.Printf("%x %x",rawPixel,rawPixels[int(y)*int(rect.Width)+int(x)]) //fmt.Printf("%x %x",rawPixel,rawPixels[int(y)*int(rect.Width)+int(x)])
} }
} }
return &RawEncoding{colors,rawPixels}, nil return &RawEncoding{colors, rawPixels}, nil
} }
// ZlibEncoding is raw pixel data sent by the server compressed by Zlib. // ZlibEncoding is raw pixel data sent by the server compressed by Zlib.
// //
// A single Zlib stream is created. There is only a single header for a framebuffer request response. // A single Zlib stream is created. There is only a single header for a framebuffer request response.
type ZlibEncoding struct { type ZlibEncoding struct {
Colors []Color Colors []Color
RawPixel[] uint32 RawPixel []uint32
ZStream *bytes.Buffer ZStream *bytes.Buffer
ZReader io.ReadCloser ZReader io.ReadCloser
} }
func (*ZlibEncoding) Type() int32 { func (*ZlibEncoding) Type() int32 {
@@ -87,6 +113,7 @@ func (*ZlibEncoding) Type() int32 {
} }
func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) {
//fmt.Println("ZlibEncoding")
bytesPerPixel := c.PixelFormat.BPP / 8 bytesPerPixel := c.PixelFormat.BPP / 8
pixelBytes := make([]uint8, bytesPerPixel) pixelBytes := make([]uint8, bytesPerPixel)
@@ -98,7 +125,6 @@ func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encod
// Format // Format
// 4 bytes | uint32 | length // 4 bytes | uint32 | length
// 'length' bytes | []byte | zlibData // 'length' bytes | []byte | zlibData
// Read zlib length // Read zlib length
var zipLength uint32 var zipLength uint32
err := binary.Read(r, binary.BigEndian, &zipLength) err := binary.Read(r, binary.BigEndian, &zipLength)
@@ -146,7 +172,7 @@ func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encod
colorReader := bytes.NewReader(colorBytes) colorReader := bytes.NewReader(colorBytes)
colors := make([]Color, int(rect.Height)*int(rect.Width)) colors := make([]Color, int(rect.Height)*int(rect.Width))
rawPixels:=make([]uint32,int(rect.Height)*int(rect.Width)) rawPixels := make([]uint32, int(rect.Height)*int(rect.Width))
for y := uint16(0); y < rect.Height; y++ { for y := uint16(0); y < rect.Height; y++ {
for x := uint16(0); x < rect.Width; x++ { for x := uint16(0); x < rect.Width; x++ {
if _, err := io.ReadFull(colorReader, pixelBytes); err != nil { if _, err := io.ReadFull(colorReader, pixelBytes); err != nil {
@@ -167,14 +193,19 @@ func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encod
color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax))
color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax))
color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax))
if c.PixelFormat.BPP == 16 {
color.B = color.B<<3 | color.B>>2
color.G = color.G<<2 | color.G>>2
color.R = color.R<<3 | color.R>>2
}
} else { } else {
*color = c.ColorMap[rawPixel] *color = c.ColorMap[rawPixel]
} }
rawPixels[int(y)*int(rect.Width)+int(x)]=uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R) rawPixels[int(y)*int(rect.Width)+int(x)] = uint32(0xff)<<24 | uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R)
} }
} }
return &ZlibEncoding{Colors: colors,RawPixel: rawPixels}, nil return &ZlibEncoding{Colors: colors, RawPixel: rawPixels}, nil
} }
func (ze *ZlibEncoding) Close() { func (ze *ZlibEncoding) Close() {
@@ -183,4 +214,4 @@ func (ze *ZlibEncoding) Close() {
ze.ZReader.Close() ze.ZReader.Close()
ze.ZReader = nil ze.ZReader = nil
} }
} }

View File

@@ -5,13 +5,14 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/pion/mediadevices/pkg/driver/vncdriver/vnc"
"image" "image"
"io" "io"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/pion/mediadevices/pkg/driver/vncdriver/vnc"
"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/mediadevices/pkg/prop"
@@ -32,12 +33,12 @@ func NewVnc(vncAddr string) *vncDevice {
return &vncDevice{vncAddr: vncAddr} return &vncDevice{vncAddr: vncAddr}
} }
func (d *vncDevice) PointerEvent(mask uint8, x, y uint16) { func (d *vncDevice) PointerEvent(mask uint8, x, y uint16) {
if d.vClient!=nil{ if d.vClient != nil {
d.vClient.PointerEvent(vnc.ButtonMask(mask), x, y) d.vClient.PointerEvent(vnc.ButtonMask(mask), x, y)
} }
} }
func (d *vncDevice) KeyEvent(keysym uint32, down bool) { func (d *vncDevice) KeyEvent(keysym uint32, down bool) {
if d.vClient!=nil { if d.vClient != nil {
d.vClient.KeyEvent(keysym, down) d.vClient.KeyEvent(keysym, down)
} }
} }
@@ -49,7 +50,10 @@ func (d *vncDevice) Open() error {
d.closed = ctx.Done() d.closed = ctx.Done()
d.cancel = cancel d.cancel = cancel
msg := make(chan vnc.ServerMessage, 1) msg := make(chan vnc.ServerMessage, 1)
//auth:=new(vnc.PasswordAuth)
//auth.Password="####"
conf := vnc.ClientConfig{ conf := vnc.ClientConfig{
//Auth: []vnc.ClientAuth{auth},
ServerMessageCh: msg, ServerMessageCh: msg,
Exclusive: false, Exclusive: false,
} }
@@ -66,6 +70,7 @@ func (d *vncDevice) Open() error {
d.vClient.SetEncodings([]vnc.Encoding{ d.vClient.SetEncodings([]vnc.Encoding{
&vnc.ZlibEncoding{}, &vnc.ZlibEncoding{},
&vnc.RawEncoding{}, &vnc.RawEncoding{},
&vnc.CursorEncoding{},
}) })
d.w = int(d.vClient.FrameBufferWidth) d.w = int(d.vClient.FrameBufferWidth)
d.h = int(d.vClient.FrameBufferHeight) d.h = int(d.vClient.FrameBufferHeight)
@@ -89,6 +94,9 @@ func (d *vncDevice) Open() error {
for _, rect := range t.Rectangles { for _, rect := range t.Rectangles {
var pix []uint32 var pix []uint32
switch t := rect.Enc.(type) { switch t := rect.Enc.(type) {
case *vnc.CursorEncoding:
//ignore remote cursor messages
continue
case *vnc.RawEncoding: case *vnc.RawEncoding:
pix = t.RawPixel pix = t.RawPixel
case *vnc.ZlibEncoding: case *vnc.ZlibEncoding:
@@ -97,19 +105,16 @@ func (d *vncDevice) Open() error {
for y := int(rect.Y); y < int(rect.Height+rect.Y); y++ { for y := int(rect.Y); y < int(rect.Height+rect.Y); y++ {
for x := int(rect.X); x < int(rect.Width+rect.X); x++ { for x := int(rect.X); x < int(rect.Width+rect.X); x++ {
binary.LittleEndian.PutUint32(d.rawPixel[(y*d.w+x)*4:], pix[(y-int(rect.Y))*int(rect.Width)+(x-int(rect.X))]) binary.LittleEndian.PutUint32(d.rawPixel[(y*d.w+x)*4:], pix[(y-int(rect.Y))*int(rect.Width)+(x-int(rect.X))])
//BigEndian
} }
} }
} }
//time.Sleep(33 * time.Millisecond)
d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h))
break break
default: default:
} }
case <-time.After(10 * time.Second): case <-time.After(10 * time.Second):
//fmt.Println("Timeout FramebufferUpdate")
if d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) != nil { if d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) != nil {
d.cancel() d.cancel()
return return
@@ -137,13 +142,12 @@ func (d *vncDevice) Close() error {
func (d *vncDevice) VideoRecord(p prop.Media) (video.Reader, error) { func (d *vncDevice) VideoRecord(p prop.Media) (video.Reader, error) {
if p.FrameRate == 0 { if p.FrameRate == 0 {
p.FrameRate = 15 p.FrameRate = 30
} }
tick := time.NewTicker(time.Duration(float32(time.Second) / p.FrameRate)) tick := time.NewTicker(time.Duration(float32(time.Second) / p.FrameRate))
d.tick = tick d.tick = tick
closed := d.closed closed := d.closed
pixs := make([]byte, d.h*d.w*4)
r := video.ReaderFunc(func() (image.Image, func(), error) { r := video.ReaderFunc(func() (image.Image, func(), error) {
select { select {
case <-closed: case <-closed:
@@ -153,9 +157,8 @@ func (d *vncDevice) VideoRecord(p prop.Media) (video.Reader, error) {
} }
<-tick.C <-tick.C
copy(pixs, d.rawPixel)
return &image.RGBA{ return &image.RGBA{
Pix: pixs, Pix: d.rawPixel,
Stride: 4, Stride: 4,
Rect: image.Rect(0, 0, d.w, d.h), Rect: image.Rect(0, 0, d.w, d.h),
}, func() {}, nil }, func() {}, nil

View File

@@ -2,6 +2,7 @@ package driver
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
@@ -21,6 +22,10 @@ func wrapAdapter(a Adapter, info Info) Driver {
state: StateClosed, state: StateClosed,
} }
if aa, ok := a.(AvailabilityAdapter); ok {
d.isAvailable = aa.IsAvailable
}
switch v := a.(type) { switch v := a.(type) {
case VideoRecorder: case VideoRecorder:
// Only expose Driver and VideoRecorder interfaces // Only expose Driver and VideoRecorder interfaces
@@ -28,7 +33,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
r := &struct { r := &struct {
Driver Driver
VideoRecorder VideoRecorder
}{d, d} AvailabilityAdapter
}{d, d, d}
return r return r
case AudioRecorder: case AudioRecorder:
// Only expose Driver and AudioRecorder interfaces // Only expose Driver and AudioRecorder interfaces
@@ -36,7 +42,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
return &struct { return &struct {
Driver Driver
AudioRecorder AudioRecorder
}{d, d} AvailabilityAdapter
}{d, d, d}
default: default:
panic("adapter has to be either VideoRecorder/AudioRecorder") panic("adapter has to be either VideoRecorder/AudioRecorder")
} }
@@ -46,9 +53,10 @@ type adapterWrapper struct {
Adapter Adapter
VideoRecorder VideoRecorder
AudioRecorder AudioRecorder
id string id string
info Info info Info
state State state State
isAvailable func() (bool, error)
} }
func (w *adapterWrapper) ID() string { func (w *adapterWrapper) ID() string {
@@ -104,3 +112,10 @@ func (w *adapterWrapper) AudioRecord(p prop.Media) (r audio.Reader, err error) {
} }
return return
} }
func (w *adapterWrapper) IsAvailable() (bool, error) {
if w.isAvailable == nil {
return false, availability.ErrUnimplemented
}
return w.isAvailable()
}

View File

@@ -39,6 +39,10 @@ func (a *audioAdapterBrokenMock) AudioRecord(p prop.Media) (r audio.Reader, err
return nil, recordErr return nil, recordErr
} }
type availabilityAdapterMock struct{ videoAdapterMock }
func (a *availabilityAdapterMock) IsAvailable() (bool, error) { return true, nil }
func TestVideoWrapperState(t *testing.T) { func TestVideoWrapperState(t *testing.T) {
var a videoAdapterMock var a videoAdapterMock
d := wrapAdapter(&a, Info{}) d := wrapAdapter(&a, Info{})
@@ -136,3 +140,38 @@ func TestAudioWrapperWithBrokenRecorderState(t *testing.T) {
t.Errorf("expected the status to be %v, but got %v", StateClosed, d.Status()) t.Errorf("expected the status to be %v, but got %v", StateClosed, d.Status())
} }
} }
func TestWrapperAvailabilityAdapter(t *testing.T) {
var aa availabilityAdapterMock
d := wrapAdapter(&aa, Info{})
ok, err := IsAvailable(d)
if err != nil {
t.Errorf("expected nil, but got %v", err)
}
if !ok {
t.Errorf("expected true, but got %v", ok)
}
var v videoAdapterMock
d = wrapAdapter(&v, Info{})
ok, err = IsAvailable(d)
if err == nil {
t.Errorf("expected err, but got %v", err)
}
if ok {
t.Errorf("expected false, but got %v", ok)
}
var a audioAdapterMock
d = wrapAdapter(&a, Info{})
ok, err = IsAvailable(d)
if err == nil {
t.Errorf("expected err, but got %v", err)
}
if ok {
t.Errorf("expected false, but got %v", ok)
}
}

View File

@@ -2,11 +2,86 @@ package frame
import ( import (
"bytes" "bytes"
"errors"
"image" "image"
"image/jpeg" "image/jpeg"
) )
/*
Thank you to https://github.com/filiptc/gorbit/blob/fa87ff39b68a6706306f34c318e0b9a5a3c97110/image/overlay.go#L37-L40
for addMotionDht, dhtMarker, dht, and sosMarker. These are protected under the following license:
The MIT License (MIT)
Copyright (c) 2016 Philip Thomas Casado
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var (
dhtMarker = []byte{255, 196}
dht = []byte{1, 162, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250}
sosMarker = []byte{255, 218}
huffmanTableInfoLength = len(dhtMarker) + len(dht) + len(sosMarker)
uninitializedHuffmanTableError jpeg.FormatError = jpeg.FormatError("uninitialized Huffman table")
)
func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) { func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) {
img, err := jpeg.Decode(bytes.NewReader(frame)) img, err := jpeg.Decode(bytes.NewReader(frame))
if err == nil {
return img, func() {}, err
}
if errors.As(err, &uninitializedHuffmanTableError) {
if err.Error() == uninitializedHuffmanTableError.Error() {
img, err = jpeg.Decode(bytes.NewReader(addMotionDht(frame)))
}
}
if err != nil {
return nil, nil, err
}
return img, func() {}, err return img, func() {}, err
} }
func addMotionDht(frame []byte) []byte {
jpegParts := bytes.Split(frame, sosMarker)
if len(jpegParts) != 2 {
return frame
}
correctedFrame := make([]byte, len(jpegParts[0])+huffmanTableInfoLength+len(jpegParts[1]))
correctedFrameOffset := 0
copy(correctedFrame[correctedFrameOffset:], jpegParts[0])
correctedFrameOffset += len(jpegParts[0])
copy(correctedFrame[correctedFrameOffset:], dhtMarker)
correctedFrameOffset += len(dhtMarker)
copy(correctedFrame[correctedFrameOffset:], dht)
correctedFrameOffset += len(dht)
copy(correctedFrame[correctedFrameOffset:], sosMarker)
correctedFrameOffset += len(sosMarker)
copy(correctedFrame[correctedFrameOffset:], jpegParts[1])
return correctedFrame
}

View File

@@ -0,0 +1,51 @@
package frame
import (
"bytes"
"image/jpeg"
"testing"
)
func TestAddMotionDht(t *testing.T) {
uninitializedHuffmanTableFrame, err := jpeg.Decode(bytes.NewReader(UninitializedHuffmanTable))
// Decode fails with an uninitialized Huffman table error for sample input
expectedErrorMessage := "invalid JPEG format: uninitialized Huffman table"
if err.Error() != expectedErrorMessage {
t.Fatalf("Wrong decode error result,\nexpected:\n%+v\ngot:\n%+v", expectedErrorMessage, err)
}
// Decode passes after adding default Huffman table to
defaultHuffmanTableFrame, err := jpeg.Decode(bytes.NewReader(addMotionDht(UninitializedHuffmanTable)))
if err != nil {
t.Fatalf("Expected decode function to pass after adding default Huffman table. Failed with %v\n", err)
}
// Adding default Huffman table to a valid frame without a Huffman table changes the table
if uninitializedHuffmanTableFrame == defaultHuffmanTableFrame {
t.Fatalf("Expected addMotionDht to update frame. Instead returned original frame")
}
// Check that an improperly constructed frame does not get updated by addMotionDht
randomBytes := []byte{1, 2, 3, 4}
frame1, err := jpeg.Decode(bytes.NewReader(randomBytes))
if err == nil {
t.Fatalf("Expected decode function to fail with random bytes but passed.")
}
frame2, err := jpeg.Decode(bytes.NewReader(addMotionDht(randomBytes)))
if err == nil {
t.Fatalf("Expected decode function to fail with random bytes but passed.")
}
if frame1 != frame2 {
t.Fatalf("addMotionDht updated the frame despite being improperly constructed")
}
}
func TestDecodeMJPEG(t *testing.T) {
_, _, err := decodeMJPEG(UninitializedHuffmanTable, 640, 480)
if err != nil {
t.Fatalf("Expected decode function to pass. Failed with %v\n", err)
}
}

View File

@@ -7,36 +7,38 @@ import (
type Format string type Format string
const ( const (
// FormatI420 https://www.fourcc.org/pixel-format/yuv-i420/ // FormatI420 https://wiki.videolan.org/YUV#I420
FormatI420 Format = "I420" FormatI420 Format = "I420"
// FormatI444 is a YUV format without sub-sampling // FormatI444 is a YUV format without sub-sampling
FormatI444 Format = "I444" FormatI444 Format = "I444"
// FormatNV21 https://www.fourcc.org/pixel-format/yuv-nv21/ // FormatNV21 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
FormatNV21 = "NV21" FormatNV21 = "NV21"
// FormatNV12 https://www.fourcc.org/pixel-format/yuv-nv12/ // FormatNV12 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
FormatNV12 = "NV12" FormatNV12 = "NV12"
// FormatYUY2 https://www.fourcc.org/pixel-format/yuv-yuy2/ // FormatYUY2 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-yuyv.html
// YUY2 is what Windows calls YUYV
FormatYUY2 = "YUY2" FormatYUY2 = "YUY2"
// FormatUYVY https://www.fourcc.org/pixel-format/yuv-uyvy/ // FormatYUYV https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-yuyv.html
FormatYUYV = "YUYV"
// FormatUYVY https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-uyvy.html
FormatUYVY = "UYVY" FormatUYVY = "UYVY"
// FormatRGBA https://www.fourcc.org/pixel-format/rgb-rgba/ // FormatRGBA https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-rgb.html
FormatRGBA Format = "RGBA" FormatRGBA Format = "RGBA"
// FormatMJPEG https://www.fourcc.org/mjpg/ // FormatMJPEG https://wiki.videolan.org/MJPEG
FormatMJPEG = "MJPEG" FormatMJPEG = "MJPEG"
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html // FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
FormatZ16 = "Z16" FormatZ16 = "Z16"
) )
const FormatYUYV = FormatYUY2
var decoderMap = map[Format]decoderFunc{ var decoderMap = map[Format]decoderFunc{
FormatI420: decodeI420, FormatI420: decodeI420,
FormatNV21: decodeNV21, FormatNV21: decodeNV21,
FormatNV12: decodeNV12, FormatNV12: decodeNV12,
FormatYUY2: decodeYUY2, FormatYUY2: decodeYUY2,
FormatYUYV: decodeYUY2,
FormatUYVY: decodeUYVY, FormatUYVY: decodeUYVY,
FormatMJPEG: decodeMJPEG, FormatMJPEG: decodeMJPEG,
FormatZ16: decodeZ16, FormatZ16: decodeZ16,

File diff suppressed because one or more lines are too long

View File

@@ -141,7 +141,9 @@ func (broadcaster *Broadcaster) NewReader(copyFn func(interface{}) interface{})
data, err, currentCount = ringData.data, ringData.err, ringData.count data, err, currentCount = ringData.data, ringData.err, ringData.count
} }
data = copyFn(data) if data != nil { // data is nil if an error occurred during reading
data = copyFn(data)
}
return return
}) })
} }

View File

@@ -1,6 +1,7 @@
package video package video
import ( import (
"errors"
"image" "image"
"reflect" "reflect"
"testing" "testing"
@@ -47,3 +48,21 @@ func TestBroadcast(t *testing.T) {
t.Fatal("Expected actual frame without copy to be the same with the original") t.Fatal("Expected actual frame without copy to be the same with the original")
} }
} }
func TestBroadcastWithCopyOnReadError(t *testing.T) {
expectedError := errors.New("expected error")
source := ReaderFunc(func() (image.Image, func(), error) {
return nil, func() {}, expectedError
})
broadcaster := NewBroadcaster(source, nil)
readerWithCopy := broadcaster.NewReader(true)
actualWithCopy, _, err := readerWithCopy.Read()
if actualWithCopy != nil {
t.Fatal("Expected actual frame with copy to be nil")
}
if err != expectedError {
t.Fatal("Expected error to be the same")
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"sync"
) )
// imageToYCbCr converts src to *image.YCbCr and store it to dst // imageToYCbCr converts src to *image.YCbCr and store it to dst
@@ -60,30 +61,61 @@ func imageToYCbCr(dst *image.YCbCr, src image.Image) {
} }
} }
// bytePool stores slices to be reused
// New method is not set as the slice size
// should be allocated according to subsample ratio
var bytesPool sync.Pool
// ToI420 converts r to a new reader that will output images in I420 format // ToI420 converts r to a new reader that will output images in I420 format
func ToI420(r Reader) Reader { func ToI420(r Reader) Reader {
var yuvImg image.YCbCr var yuvImg image.YCbCr
getSlice := func(cLen int) []uint8 {
// Retrieve slice from pool
dst, ok := bytesPool.Get().([]byte)
// Compare value or capacity of retrieved object
// If less than expected, reallocate new object
if !ok || cap(dst) < 2*cLen {
// Allocating memory for Cb and Cr
dst = make([]byte, 2*cLen, 2*cLen)
}
return dst
}
return ReaderFunc(func() (image.Image, func(), error) { return ReaderFunc(func() (image.Image, func(), error) {
img, _, err := r.Read() img, _, err := r.Read()
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
var releaseFunc func() = func() {}
imageToYCbCr(&yuvImg, img) imageToYCbCr(&yuvImg, img)
// Covert pixel format to I420 // Covert pixel format to I420
switch yuvImg.SubsampleRatio { switch yuvImg.SubsampleRatio {
case image.YCbCrSubsampleRatio444:
i444ToI420(&yuvImg)
case image.YCbCrSubsampleRatio422:
i422ToI420(&yuvImg)
case image.YCbCrSubsampleRatio420: case image.YCbCrSubsampleRatio420:
case image.YCbCrSubsampleRatio444:
cLen := yuvImg.CStride * yuvImg.Rect.Dy() / 4
dst := getSlice(cLen)
yuvImg = i444ToI420(yuvImg, dst)
releaseFunc = func() {
bytesPool.Put(dst)
}
case image.YCbCrSubsampleRatio422:
cLen := yuvImg.CStride * (yuvImg.Rect.Dy() / 2)
dst := getSlice(cLen)
yuvImg = i422ToI420(yuvImg, dst)
releaseFunc = func() {
bytesPool.Put(dst)
}
default: default:
return nil, func() {}, fmt.Errorf("unsupported pixel format: %s", yuvImg.SubsampleRatio) return nil, releaseFunc, fmt.Errorf("unsupported pixel format: %s", yuvImg.SubsampleRatio)
} }
yuvImg.SubsampleRatio = image.YCbCrSubsampleRatio420 return &yuvImg, releaseFunc, nil
return &yuvImg, func() {}, nil
}) })
} }

View File

@@ -3,6 +3,8 @@
#include "_cgo_export.h" #include "_cgo_export.h"
void i444ToI420CGO( void i444ToI420CGO(
unsigned char *cb_dst,
unsigned char *cr_dst,
unsigned char* cb, unsigned char* cb,
unsigned char* cr, unsigned char* cr,
const int stride, const int h) const int stride, const int h)
@@ -22,8 +24,8 @@ void i444ToI420CGO(
((uint16_t)cr[isrc0] + (uint16_t)cr[isrc1] + ((uint16_t)cr[isrc0] + (uint16_t)cr[isrc1] +
(uint16_t)cr[isrc0 + 1] + (uint16_t)cr[isrc1 + 1]) / (uint16_t)cr[isrc0 + 1] + (uint16_t)cr[isrc1 + 1]) /
4; 4;
cb[idst] = cb2; cb_dst[idst] = cb2;
cr[idst] = cr2; cr_dst[idst] = cr2;
isrc0 += 2; isrc0 += 2;
isrc1 += 2; isrc1 += 2;
idst++; idst++;
@@ -34,6 +36,8 @@ void i444ToI420CGO(
} }
void i422ToI420CGO( void i422ToI420CGO(
unsigned char *cb_dst,
unsigned char *cr_dst,
unsigned char* cb, unsigned char* cb,
unsigned char* cr, unsigned char* cr,
const int stride, const int h) const int stride, const int h)
@@ -46,8 +50,8 @@ void i422ToI420CGO(
{ {
const uint8_t cb2 = ((uint16_t)cb[isrc] + (uint16_t)cb[isrc + stride]) / 2; const uint8_t cb2 = ((uint16_t)cb[isrc] + (uint16_t)cb[isrc + stride]) / 2;
const uint8_t cr2 = ((uint16_t)cr[isrc] + (uint16_t)cr[isrc + stride]) / 2; const uint8_t cr2 = ((uint16_t)cr[isrc] + (uint16_t)cr[isrc + stride]) / 2;
cb[idst] = cb2; cb_dst[idst] = cb2;
cr[idst] = cr2; cr_dst[idst] = cr2;
isrc++; isrc++;
idst++; idst++;
} }

View File

@@ -1,3 +1,4 @@
//go:build cgo
// +build cgo // +build cgo
package video package video
@@ -14,27 +15,41 @@ import "C"
// All functions switched at runtime must be declared also in convert_nocgo.go. // All functions switched at runtime must be declared also in convert_nocgo.go.
const hasCGOConvert = true const hasCGOConvert = true
func i444ToI420(img *image.YCbCr) { func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
h := img.Rect.Dy() h := img.Rect.Dy()
cLen := img.CStride * h / 4
// Divide preallocated memory to cbDst and crDst
// and truncate cap and len to cLen
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
crDst = crDst[:cLen:cLen]
C.i444ToI420CGO( C.i444ToI420CGO(
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]), (*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
C.int(img.CStride), C.int(h), C.int(img.CStride), C.int(h),
) )
img.CStride = img.CStride / 2 img.CStride = img.CStride / 2
cLen := img.CStride * (h / 2) img.Cb = cbDst
img.Cb = img.Cb[:cLen] img.Cr = crDst
img.Cr = img.Cr[:cLen] img.SubsampleRatio = image.YCbCrSubsampleRatio420
return img
} }
func i422ToI420(img *image.YCbCr) { func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
h := img.Rect.Dy() h := img.Rect.Dy()
cLen := img.CStride * (h / 2)
// Divide preallocated memory to cbDst and crDst
// and truncate cap and len to cLen
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
crDst = crDst[:cLen:cLen]
C.i422ToI420CGO( C.i422ToI420CGO(
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]), (*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
C.int(img.CStride), C.int(h), C.int(img.CStride), C.int(h),
) )
cLen := img.CStride * (h / 2) img.Cb = cbDst
img.Cb = img.Cb[:cLen] img.Cr = crDst
img.Cr = img.Cr[:cLen] img.SubsampleRatio = image.YCbCrSubsampleRatio420
return img
} }
func rgbToYCbCrCGO(y, cb, cr *uint8, r, g, b uint8) { // For testing func rgbToYCbCrCGO(y, cb, cr *uint8, r, g, b uint8) { // For testing

View File

@@ -1,9 +1,13 @@
void i444ToI420CGO( void i444ToI420CGO(
unsigned char *cb_dst,
unsigned char *cr_dst,
unsigned char* cb, unsigned char* cb,
unsigned char* cr, unsigned char* cr,
const int stride, const int h); const int stride, const int h);
void i422ToI420CGO( void i422ToI420CGO(
unsigned char *cb_dst,
unsigned char *cr_dst,
unsigned char* cb, unsigned char* cb,
unsigned char* cr, unsigned char* cr,
const int stride, const int h); const int stride, const int h);

View File

@@ -1,3 +1,4 @@
//go:build !cgo
// +build !cgo // +build !cgo
package video package video
@@ -9,19 +10,25 @@ import (
const hasCGOConvert = false const hasCGOConvert = false
func i444ToI420(img *image.YCbCr) { func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
h := img.Rect.Dy() h := img.Rect.Dy()
addrSrc0 := 0 addrSrc0 := 0
addrSrc1 := img.CStride addrSrc1 := img.CStride
cLen := img.CStride * (h / 4)
addrDst := 0 addrDst := 0
// Divide preallocated memory to cbDst and crDst
// and truncate cap and len to cLen
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
crDst = crDst[:cLen:cLen]
for i := 0; i < h/2; i++ { for i := 0; i < h/2; i++ {
for j := 0; j < img.CStride/2; j++ { for j := 0; j < img.CStride/2; j++ {
cb := uint16(img.Cb[addrSrc0]) + uint16(img.Cb[addrSrc1]) + cb := uint16(img.Cb[addrSrc0]) + uint16(img.Cb[addrSrc1]) +
uint16(img.Cb[addrSrc0+1]) + uint16(img.Cb[addrSrc1+1]) uint16(img.Cb[addrSrc0+1]) + uint16(img.Cb[addrSrc1+1])
cr := uint16(img.Cr[addrSrc0]) + uint16(img.Cr[addrSrc1]) + cr := uint16(img.Cr[addrSrc0]) + uint16(img.Cr[addrSrc1]) +
uint16(img.Cr[addrSrc0+1]) + uint16(img.Cr[addrSrc1+1]) uint16(img.Cr[addrSrc0+1]) + uint16(img.Cr[addrSrc1+1])
img.Cb[addrDst] = uint8(cb / 4) cbDst[addrDst] = uint8(cb / 4)
img.Cr[addrDst] = uint8(cr / 4) crDst[addrDst] = uint8(cr / 4)
addrSrc0 += 2 addrSrc0 += 2
addrSrc1 += 2 addrSrc1 += 2
addrDst++ addrDst++
@@ -30,29 +37,37 @@ func i444ToI420(img *image.YCbCr) {
addrSrc1 += img.CStride addrSrc1 += img.CStride
} }
img.CStride = img.CStride / 2 img.CStride = img.CStride / 2
cLen := img.CStride * (h / 2) img.Cb = cbDst
img.Cb = img.Cb[:cLen] img.Cr = crDst
img.Cr = img.Cr[:cLen] img.SubsampleRatio = image.YCbCrSubsampleRatio420
return img
} }
func i422ToI420(img *image.YCbCr) { func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
h := img.Rect.Dy() h := img.Rect.Dy()
addrSrc := 0 addrSrc := 0
cLen := img.CStride * (h / 2)
// Divide preallocated memory to cbDst and crDst
// and truncate cap and len to cLen
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
crDst = crDst[:cLen:cLen]
addrDst := 0 addrDst := 0
for i := 0; i < h/2; i++ { for i := 0; i < h/2; i++ {
for j := 0; j < img.CStride; j++ { for j := 0; j < img.CStride; j++ {
cb := uint16(img.Cb[addrSrc]) + uint16(img.Cb[addrSrc+img.CStride]) cb := uint16(img.Cb[addrSrc]) + uint16(img.Cb[addrSrc+img.CStride])
cr := uint16(img.Cr[addrSrc]) + uint16(img.Cr[addrSrc+img.CStride]) cr := uint16(img.Cr[addrSrc]) + uint16(img.Cr[addrSrc+img.CStride])
img.Cb[addrDst] = uint8(cb / 2) cbDst[addrDst] = uint8(cb / 2)
img.Cr[addrDst] = uint8(cr / 2) crDst[addrDst] = uint8(cr / 2)
addrDst++
addrSrc++ addrSrc++
addrDst++
} }
addrSrc += img.CStride addrSrc += img.CStride
} }
cLen := img.CStride * (h / 2) img.Cb = cbDst
img.Cb = img.Cb[:cLen] img.Cr = crDst
img.Cr = img.Cr[:cLen] img.SubsampleRatio = image.YCbCrSubsampleRatio420
return img
} }
func i444ToRGBA(dst *image.RGBA, src *image.YCbCr) { func i444ToRGBA(dst *image.RGBA, src *image.YCbCr) {

View File

@@ -147,10 +147,11 @@ func TestToI420(t *testing.T) {
r := ToI420(ReaderFunc(func() (image.Image, func(), error) { r := ToI420(ReaderFunc(func() (image.Image, func(), error) {
return c.src, func() {}, nil return c.src, func() {}, nil
})) }))
out, _, err := r.Read() out, release, err := r.Read()
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
defer release()
if !reflect.DeepEqual(c.expected, out) { if !reflect.DeepEqual(c.expected, out) {
t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out) t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out)
} }
@@ -222,18 +223,19 @@ func BenchmarkToI420(b *testing.B) {
"RGBA": image.NewRGBA(image.Rect(0, 0, sz[0], sz[1])), "RGBA": image.NewRGBA(image.Rect(0, 0, sz[0], sz[1])),
} }
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
for name, img := range cases { for _, name := range [...]string{"I444", "I422", "I420", "RGBA"} {
img := img img := cases[name]
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
r := ToI420(ReaderFunc(func() (image.Image, func(), error) { r := ToI420(ReaderFunc(func() (image.Image, func(), error) {
return img, func() {}, nil return img, func() {}, nil
})) }))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _, err := r.Read() _, release, err := r.Read()
if err != nil { if err != nil {
b.Fatalf("Unexpected error: %v", err) b.Fatalf("Unexpected error: %v", err)
} }
release()
} }
}) })
} }

View File

@@ -526,6 +526,9 @@ func TestScale(t *testing.T) {
} }
func TestScaleFastBoxSampling(t *testing.T) { func TestScaleFastBoxSampling(t *testing.T) {
if !hasCGOConvert {
t.Skip("Skip: nocgo implementation is not supported for FastBoxSampling")
}
cases := map[string]struct { cases := map[string]struct {
src image.Image src image.Image
width, height int width, height int
@@ -535,19 +538,19 @@ func TestScaleFastBoxSampling(t *testing.T) {
src: &image.YCbCr{ src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420, SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{ Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10, 0xF0, 0xF8, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10, 0xF8, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
}, },
Cb: []uint8{ Cb: []uint8{
0x20, 0x20, 0x80, 0x80, 0x50, 0x50, 0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
0x20, 0x20, 0x80, 0x80, 0x50, 0x50, 0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30, 0x80, 0x80, 0xE0, 0xE0, 0x34, 0x34,
0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30, 0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30,
}, },
Cr: []uint8{ Cr: []uint8{
@@ -565,24 +568,104 @@ func TestScaleFastBoxSampling(t *testing.T) {
expected: &image.YCbCr{ expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420, SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{ Y: []uint8{
0xF0, 0x80, 0x08, 0x00, 0x78, 0x80, 0xF4, 0x10, 0x00, 0x00, 0xF0, 0x10,
0x08, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x40, 0x58, 0x18, 0x18, 0x18, 0x00, 0x80, 0x30, 0x00, 0x30, 0x00,
}, },
Cb: []uint8{ Cb: []uint8{
0x20, 0x50, 0x68, 0x20, 0x80, 0x50,
0x68, 0xB0, 0x88, 0x80, 0xE0, 0x32,
}, },
Cr: []uint8{ Cr: []uint8{
0xE0, 0xB0, 0x98, 0xE0, 0x80, 0xB0,
0xD0, 0x98, 0x80, 0xF0, 0x40, 0xC0,
}, },
YStride: 6, YStride: 6,
CStride: 3, CStride: 3,
Rect: image.Rect(0, 0, 6, 4), Rect: image.Rect(0, 0, 6, 4),
}, },
}, },
"I420Uneven6x4to3x2": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
},
Cb: []uint8{
0x20, 0x20, 0x80,
0x20, 0x20, 0x80,
},
Cr: []uint8{
0xE0, 0xE0, 0x80,
0xE0, 0xE0, 0x80,
0xFF, // dummy data to detect out of range read
},
YStride: 6,
CStride: 3,
Rect: image.Rect(0, 0, 6, 3),
},
width: 4,
height: 2,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0x10, 0x08, 0x00,
0x00, 0x00, 0x20, 0x40,
},
Cb: []uint8{
0x20, 0x80,
},
Cr: []uint8{
0xE0, 0x80,
},
YStride: 4,
CStride: 2,
Rect: image.Rect(0, 0, 4, 2),
},
},
"I420Uneven6x3to5x2": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x50,
},
Cb: []uint8{
0x20, 0x20, 0x80,
0x20, 0x20, 0x80,
},
Cr: []uint8{
0xE0, 0xE0, 0x80,
0xE0, 0xE0, 0x80,
0xFF, // dummy data to detect out of range read
},
YStride: 6,
CStride: 3,
Rect: image.Rect(0, 0, 6, 3),
},
width: 5,
height: 2,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0x10, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x50,
},
Cb: []uint8{
0x20, 0x80, 0x00,
},
Cr: []uint8{
0xE0, 0x80, 0x00,
},
YStride: 5,
CStride: 3,
Rect: image.Rect(0, 0, 5, 2),
},
},
} }
for name, c := range cases { for name, c := range cases {

View File

@@ -29,7 +29,7 @@ void fastBoxSampling(
const int sw, const int sh, const int sstride, const int sw, const int sh, const int sstride,
uint32_t* tmp) uint32_t* tmp)
{ {
memset(tmp, 0, dw * dh * ch * sizeof(tmp[0])); memset(tmp, 0, dstride * dh * ch * sizeof(tmp[0]));
for (int sy = 0; sy < sh; sy++) for (int sy = 0; sy < sh; sy++)
{ {
@@ -39,13 +39,14 @@ void fastBoxSampling(
uint32_t* tmp2 = &tmp[ty * dstride]; uint32_t* tmp2 = &tmp[ty * dstride];
for (int sx = 0; sx < sw * ch; sx += ch) for (int sx = 0; sx < sw * ch; sx += ch)
{ {
if (tx * sw < sx * dw)
tx += ch;
for (int c = 0; c < ch; c++) for (int c = 0; c < ch; c++)
{ {
tmp2[tx + c] += 0x10000 | src2[sx + c]; tmp2[tx + c] += 0x10000 | src2[sx + c];
} }
if (tx * sw < sx * dw)
{
tx += ch;
}
} }
} }

View File

@@ -0,0 +1,22 @@
//go:build !cgo
// +build !cgo
package video
import (
"golang.org/x/image/draw"
"image"
)
// ScalerFastBoxSampling mock scaler for nocgo implementation to pass tests for CGO_ENABLED=0
var (
ScalerFastBoxSampling = Scaler(&FastBoxSampling{})
)
// FastBoxSampling mock implementation for nocgo implementation
// TODO implement nocgo FastBoxSampling scaling algorithm
type FastBoxSampling struct {
}
func (f *FastBoxSampling) Scale(_ draw.Image, _ image.Rectangle, _ image.Image, _ image.Rectangle, _ draw.Op, _ *draw.Options) {
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
) )
// MediaConstraints represents set of media propaty constraints. // MediaConstraints represents set of media property constraints.
// Each field constrains property by min/ideal/max range, exact match, or oneof match. // Each field constrains property by min/ideal/max range, exact match, or oneof match.
type MediaConstraints struct { type MediaConstraints struct {
DeviceID StringConstraint DeviceID StringConstraint
@@ -145,14 +145,16 @@ func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
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.FrameRate, o.FrameRate) // skip framerate if not available in media properties
if o.FrameRate > 0.0 {
cmps.add(p.FrameRate, o.FrameRate)
}
cmps.add(p.SampleRate, o.SampleRate) cmps.add(p.SampleRate, o.SampleRate)
cmps.add(p.Latency, o.Latency) cmps.add(p.Latency, o.Latency)
cmps.add(p.ChannelCount, o.ChannelCount) cmps.add(p.ChannelCount, o.ChannelCount)
cmps.add(p.IsBigEndian, o.IsBigEndian) cmps.add(p.IsBigEndian, o.IsBigEndian)
cmps.add(p.IsFloat, o.IsFloat) cmps.add(p.IsFloat, o.IsFloat)
cmps.add(p.IsInterleaved, o.IsInterleaved) cmps.add(p.IsInterleaved, o.IsInterleaved)
return cmps.fitnessDistance() return cmps.fitnessDistance()
} }
@@ -226,16 +228,18 @@ func (c *comparisons) fitnessDistance() (float64, bool) {
// VideoConstraints represents a video's constraints // VideoConstraints represents a video's constraints
type VideoConstraints struct { type VideoConstraints struct {
Width, Height IntConstraint Width, Height IntConstraint
FrameRate FloatConstraint FrameRate FloatConstraint
FrameFormat FrameFormatConstraint FrameFormat FrameFormatConstraint
DiscardFramesOlderThan time.Duration
} }
// Video represents a video's constraints // 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
DiscardFramesOlderThan time.Duration
} }
// AudioConstraints represents an audio's constraints // AudioConstraints represents an audio's constraints

View File

@@ -85,6 +85,60 @@ func TestCompareMatch(t *testing.T) {
}}, }},
true, true,
}, },
"FloatExactMatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: FloatExact(30),
}},
Media{Video: Video{
FrameRate: 30.0,
}},
true,
},
"FloatExactUnmatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: FloatExact(30),
}},
Media{Video: Video{
FrameRate: 30.1,
}},
false,
},
"FloatIdealMatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: Float(30),
}},
Media{Video: Video{
FrameRate: 30.0,
}},
true,
},
"FloatIdealUnmatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: Float(30),
}},
Media{Video: Video{
FrameRate: 10.0,
}},
true,
},
"FloatRangeMatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: FloatRanged{Min: 30, Max: 40},
}},
Media{Video: Video{
FrameRate: 35.0,
}},
true,
},
"FloatRangeUnmatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: FloatRanged{Min: 30, Max: 40},
}},
Media{Video: Video{
FrameRate: 50.0,
}},
false,
},
"FrameFormatOneOfUnmatch": { "FrameFormatOneOfUnmatch": {
MediaConstraints{VideoConstraints: VideoConstraints{ MediaConstraints{VideoConstraints: VideoConstraints{
FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY}, FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY},
@@ -366,3 +420,63 @@ func TestString(t *testing.T) {
}) })
}) })
} }
func TestFrameRateProps(t *testing.T) {
testDataSet := map[string]struct {
a MediaConstraints
b Media
score float64
match bool
}{
"FrameRateIdealMatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: Float(30.0),
}},
Media{Video: Video{
FrameRate: 30.0,
}},
0.0,
true,
},
"FrameRateIdealUnmatch": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: Float(30.0),
}},
Media{Video: Video{
FrameRate: 60.0,
}},
0.5,
true,
},
"FrameRateConstraintMissing": {
// empty video fps constraint
MediaConstraints{VideoConstraints: VideoConstraints{}},
Media{Video: Video{
FrameRate: 30.0,
}},
0.0,
true,
},
"FrameRatePropMissing": {
MediaConstraints{VideoConstraints: VideoConstraints{
FrameRate: Float(30.0),
}},
// empty video fps property
Media{Video: Video{}},
0.0,
true,
},
}
for name, data := range testDataSet {
t.Run(name, func(t *testing.T) {
score, match := data.a.FitnessDistance(data.b)
if score != data.score {
t.Errorf("expected score %f, got %f", data.score, score)
}
if match != data.match {
t.Errorf("expected match %t, got %t", data.match, match)
}
})
}
}

View File

@@ -53,7 +53,7 @@ func TestBufferStoreCopyAndLoad(t *testing.T) {
for i := range cloneReal.Data { for i := range cloneReal.Data {
if reflect.ValueOf(originalReal.Data[i]).Pointer() == reflect.ValueOf(cloneReal.Data[i]).Pointer() { if reflect.ValueOf(originalReal.Data[i]).Pointer() == reflect.ValueOf(cloneReal.Data[i]).Pointer() {
err := fmt.Errorf("Channel %d memory address should be different", i) err := fmt.Errorf("Channel %d memory address should be different", i)
t.Errorf("%v: %w", errIdenticalAddress, err) t.Errorf("%v: %s", errIdenticalAddress, err)
} }
} }
}, },
@@ -89,7 +89,7 @@ func TestBufferStoreCopyAndLoad(t *testing.T) {
for i := range cloneReal.Data { for i := range cloneReal.Data {
if reflect.ValueOf(originalReal.Data[i]).Pointer() == reflect.ValueOf(cloneReal.Data[i]).Pointer() { if reflect.ValueOf(originalReal.Data[i]).Pointer() == reflect.ValueOf(cloneReal.Data[i]).Pointer() {
err := fmt.Errorf("Channel %d memory address should be different", i) err := fmt.Errorf("Channel %d memory address should be different", i)
t.Errorf("%v: %w", errIdenticalAddress, err) t.Errorf("%v: %s", errIdenticalAddress, err)
} }
} }
}, },

View File

@@ -11,6 +11,16 @@
{ {
"packagePatterns": ["^golang.org/x/"], "packagePatterns": ["^golang.org/x/"],
"schedule": ["on the first day of the month"] "schedule": ["on the first day of the month"]
},
{
"description": "Disable updating minimum Go version: https://github.com/renovatebot/renovate/issues/16715",
"matchManagers": ["gomod"],
"matchDepTypes": ["golang"],
"enabled": false
} }
],
"ignorePaths": [],
"ignoreDeps": [
"github.com/pion/mediadevices"
] ]
} }

View File

@@ -1,15 +1,20 @@
package mediadevices package mediadevices
import "github.com/pion/rtp" import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/rtp"
)
type RTPReadCloser interface { type RTPReadCloser interface {
Read() (pkts []*rtp.Packet, release func(), err error) Read() (pkts []*rtp.Packet, release func(), err error)
Close() error Close() error
codec.Controllable
} }
type rtpReadCloserImpl struct { type rtpReadCloserImpl struct {
readFn func() ([]*rtp.Packet, func(), error) readFn func() ([]*rtp.Packet, func(), error)
closeFn func() error closeFn func() error
controllerFn func() codec.EncoderController
} }
func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) { func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
@@ -19,3 +24,7 @@ func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
func (r *rtpReadCloserImpl) Close() error { func (r *rtpReadCloserImpl) Close() error {
return r.closeFn() return r.closeFn()
} }
func (r *rtpReadCloserImpl) Controller() codec.EncoderController {
return r.controllerFn()
}

107
track.go
View File

@@ -8,6 +8,9 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/pion/interceptor"
"github.com/pion/rtcp"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/driver" "github.com/pion/mediadevices/pkg/driver"
@@ -20,6 +23,7 @@ import (
const ( const (
rtpOutboundMTU = 1200 rtpOutboundMTU = 1200
rtcpInboundMTU = 1500
) )
var ( var (
@@ -79,6 +83,7 @@ type baseTrack struct {
Source Source
err error err error
onErrorHandler func(error) onErrorHandler func(error)
errMu sync.Mutex
mu sync.Mutex mu sync.Mutex
endOnce sync.Once endOnce sync.Once
kind MediaDeviceType kind MediaDeviceType
@@ -125,10 +130,10 @@ func (track *baseTrack) RID() string {
// 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
// error occurs, handler will get called with the error given to the parameter. // error occurs, handler will get called with the error given to the parameter.
func (track *baseTrack) OnEnded(handler func(error)) { func (track *baseTrack) OnEnded(handler func(error)) {
track.mu.Lock() track.errMu.Lock()
track.onErrorHandler = handler track.onErrorHandler = handler
err := track.err err := track.err
track.mu.Unlock() track.errMu.Unlock()
if err != nil && handler != nil { if err != nil && handler != nil {
// Already errored. // Already errored.
@@ -140,10 +145,10 @@ func (track *baseTrack) OnEnded(handler func(error)) {
// onError is a callback when an error occurs // onError is a callback when an error occurs
func (track *baseTrack) onError(err error) { func (track *baseTrack) onError(err error) {
track.mu.Lock() track.errMu.Lock()
track.err = err track.err = err
handler := track.onErrorHandler handler := track.onErrorHandler
track.mu.Unlock() track.errMu.Unlock()
if handler != nil { if handler != nil {
track.endOnce.Do(func() { track.endOnce.Do(func() {
@@ -157,6 +162,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
defer track.mu.Unlock() defer track.mu.Unlock()
signalCh := make(chan chan<- struct{}) signalCh := make(chan chan<- struct{})
stopRead := make(chan struct{})
track.activePeerConnections[ctx.ID()] = signalCh track.activePeerConnections[ctx.ID()] = signalCh
var encodedReader RTPReadCloser var encodedReader RTPReadCloser
@@ -166,6 +172,14 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
for _, wantedCodec := range ctx.CodecParameters() { for _, wantedCodec := range ctx.CodecParameters() {
logger.Debugf("trying to build %s rtp reader", wantedCodec.MimeType) logger.Debugf("trying to build %s rtp reader", wantedCodec.MimeType)
encodedReader, err = specializedTrack.NewRTPReader(wantedCodec.MimeType, uint32(ctx.SSRC()), rtpOutboundMTU) encodedReader, err = specializedTrack.NewRTPReader(wantedCodec.MimeType, uint32(ctx.SSRC()), rtpOutboundMTU)
track.errMu.Lock()
if track.err != nil {
err = track.err
encodedReader = nil
}
track.errMu.Unlock()
if err == nil { if err == nil {
selectedCodec = wantedCodec selectedCodec = wantedCodec
break break
@@ -182,9 +196,11 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
var doneCh chan<- struct{} var doneCh chan<- struct{}
writer := ctx.WriteStream() writer := ctx.WriteStream()
defer func() { defer func() {
close(stopRead)
encodedReader.Close() encodedReader.Close()
// When there's another call to unbind, it won't block since we mark the signalCh to be closed // When there's another call to unbind, it won't block since we remove the current ctx from active connections
track.removeActivePeerConnection(ctx.ID())
close(signalCh) close(signalCh)
if doneCh != nil { if doneCh != nil {
close(doneCh) close(doneCh)
@@ -214,13 +230,57 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
} }
}() }()
keyFrameController, ok := encodedReader.Controller().(codec.KeyFrameController)
if ok {
go track.rtcpReadLoop(ctx.RTCPReader(), keyFrameController, stopRead)
}
return selectedCodec, nil return selectedCodec, nil
} }
func (track *baseTrack) rtcpReadLoop(reader interceptor.RTCPReader, keyFrameController codec.KeyFrameController, stopRead chan struct{}) {
readerBuffer := make([]byte, rtcpInboundMTU)
readLoop:
for {
select {
case <-stopRead:
return
default:
}
readLength, _, err := reader.Read(readerBuffer, interceptor.Attributes{})
if err != nil {
if errors.Is(err, io.EOF) {
return
}
logger.Warnf("failed to read rtcp packet: %s", err)
continue
}
pkts, err := rtcp.Unmarshal(readerBuffer[:readLength])
if err != nil {
logger.Warnf("failed to unmarshal rtcp packet: %s", err)
continue
}
for _, pkt := range pkts {
switch pkt.(type) {
case *rtcp.PictureLossIndication, *rtcp.FullIntraRequest:
if err := keyFrameController.ForceKeyFrame(); err != nil {
logger.Warnf("failed to force key frame: %s", err)
continue readLoop
}
}
}
}
}
func (track *baseTrack) unbind(ctx webrtc.TrackLocalContext) error { func (track *baseTrack) unbind(ctx webrtc.TrackLocalContext) error {
ch, err := track.removeActivePeerConnection(ctx.ID()) ch := track.removeActivePeerConnection(ctx.ID())
if err != err { // If there isn't a registered chanel for this ctx, it means it has already been unbound
return err if ch == nil {
return nil
} }
doneCh := make(chan struct{}) doneCh := make(chan struct{})
@@ -229,17 +289,17 @@ func (track *baseTrack) unbind(ctx webrtc.TrackLocalContext) error {
return nil return nil
} }
func (track *baseTrack) removeActivePeerConnection(id string) (chan<- chan<- struct{}, error) { func (track *baseTrack) removeActivePeerConnection(id string) chan<- chan<- struct{} {
track.mu.Lock() track.mu.Lock()
defer track.mu.Unlock() defer track.mu.Unlock()
ch, ok := track.activePeerConnections[id] ch, ok := track.activePeerConnections[id]
if !ok { if !ok {
return nil, errNotFoundPeerConnection return nil
} }
delete(track.activePeerConnections, id) delete(track.activePeerConnections, id)
return ch, nil return ch
} }
func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) { func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
@@ -261,6 +321,7 @@ func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, sele
type VideoTrack struct { type VideoTrack struct {
*baseTrack *baseTrack
*video.Broadcaster *video.Broadcaster
shouldCopyFrames bool
} }
// NewVideoTrack constructs a new VideoTrack // NewVideoTrack constructs a new VideoTrack
@@ -268,6 +329,16 @@ func NewVideoTrack(source VideoSource, selector *CodecSelector) Track {
return newVideoTrackFromReader(source, source, selector) return newVideoTrackFromReader(source, source, selector)
} }
// ShouldCopyFrames indicates if readers on this track should receive a clopy of the read buffer instead of sharing one.
func (track *VideoTrack) ShouldCopyFrames() bool {
return track.shouldCopyFrames
}
// SetShouldCopyFrames enables frame copy for this track, sending each reader a different read buffer instead of sharing one.
func (track *VideoTrack) SetShouldCopyFrames(shouldCopyFrames bool) {
track.shouldCopyFrames = shouldCopyFrames
}
func newVideoTrackFromReader(source Source, reader video.Reader, selector *CodecSelector) Track { func newVideoTrackFromReader(source Source, reader video.Reader, selector *CodecSelector) Track {
base := newBaseTrack(source, VideoInput, selector) base := newBaseTrack(source, VideoInput, selector)
wrappedReader := video.ReaderFunc(func() (img image.Image, release func(), err error) { wrappedReader := video.ReaderFunc(func() (img image.Image, release func(), err error) {
@@ -312,7 +383,7 @@ func (track *VideoTrack) Unbind(ctx webrtc.TrackLocalContext) error {
} }
func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadCloser, *codec.RTPCodec, error) { func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadCloser, *codec.RTPCodec, error) {
reader := track.NewReader(false) reader := track.NewReader(track.shouldCopyFrames)
inputProp, err := detectCurrentVideoProp(track.Broadcaster) inputProp, err := detectCurrentVideoProp(track.Broadcaster)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -334,7 +405,8 @@ func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadClos
} }
return buffer, release, err return buffer, release, err
}, },
closeFn: encodedReader.Close, closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, selectedCodec, nil }, selectedCodec, nil
} }
@@ -372,7 +444,8 @@ func (track *VideoTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
pkts := packetizer.Packetize(encoded.Data, encoded.Samples) pkts := packetizer.Packetize(encoded.Data, encoded.Samples)
return pkts, release, err return pkts, release, err
}, },
closeFn: encodedReader.Close, closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, nil }, nil
} }
@@ -454,7 +527,8 @@ func (track *AudioTrack) newEncodedReader(codecNames ...string) (EncodedReadClos
} }
return buffer, release, err return buffer, release, err
}, },
closeFn: encodedReader.Close, closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, selectedCodec, nil }, selectedCodec, nil
} }
@@ -492,6 +566,7 @@ func (track *AudioTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
pkts := packetizer.Packetize(encoded.Data, encoded.Samples) pkts := packetizer.Packetize(encoded.Data, encoded.Samples)
return pkts, release, err return pkts, release, err
}, },
closeFn: encodedReader.Close, closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, nil }, nil
} }

View File

@@ -2,12 +2,33 @@ package mediadevices
import ( import (
"errors" "errors"
"io"
"sync"
"testing" "testing"
"time" "time"
"github.com/pion/interceptor"
"github.com/pion/webrtc/v3"
) )
var errExpected error = errors.New("an error")
type DummyBindTrack struct {
*baseTrack
}
func (track *DummyBindTrack) Bind(ctx webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) {
track.mu.Lock()
defer track.mu.Unlock()
track.onError(errExpected)
<-time.After(5 * time.Millisecond)
return webrtc.RTPCodecParameters{}, nil
}
func TestOnEnded(t *testing.T) { func TestOnEnded(t *testing.T) {
errExpected := errors.New("an error")
t.Run("ErrorAfterRegister", func(t *testing.T) { t.Run("ErrorAfterRegister", func(t *testing.T) {
tr := &baseTrack{} tr := &baseTrack{}
@@ -52,4 +73,134 @@ func TestOnEnded(t *testing.T) {
t.Error("Timeout") t.Error("Timeout")
} }
}) })
t.Run("ErrorDurringBind", func(t *testing.T) {
tr := &DummyBindTrack{
baseTrack: &baseTrack{
activePeerConnections: make(map[string]chan<- chan<- struct{}),
mu: sync.Mutex{},
},
}
called := make(chan error, 1)
tr.OnEnded(func(err error) {
called <- errExpected
})
_, err := tr.Bind(&fakeTrackLocalContext{})
if err != nil {
t.Fatal(err)
}
select {
case err := <-called:
if err != errExpected {
t.Errorf("Expected to receive error: %v, got: %v", errExpected, err)
}
case <-time.After(10 * time.Millisecond):
t.Error("Timeout")
}
})
}
type fakeTrackLocalContext struct {
webrtc.TrackLocalContext
}
type fakeRTCPReader struct {
mockReturn chan []byte
end chan struct{}
}
func (mock *fakeRTCPReader) Read(buffer []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
select {
case <-mock.end:
return 0, nil, io.EOF
case mockReturn := <-mock.mockReturn:
if len(buffer) < len(mock.mockReturn) {
return 0, nil, io.ErrShortBuffer
}
return copy(buffer, mockReturn), attributes, nil
}
}
type fakeKeyFrameController struct {
called chan struct{}
}
func (mock *fakeKeyFrameController) ForceKeyFrame() error {
mock.called <- struct{}{}
return nil
}
func TestRtcpHandler(t *testing.T) {
t.Run("ShouldStopReading", func(t *testing.T) {
tr := &baseTrack{}
stop := make(chan struct{}, 1)
stopped := make(chan struct{})
go func() {
tr.rtcpReadLoop(&fakeRTCPReader{end: stop}, &fakeKeyFrameController{}, stop)
stopped <- struct{}{}
}()
stop <- struct{}{}
select {
case <-time.After(100 * time.Millisecond):
t.Error("Timeout")
case <-stopped:
}
})
t.Run("ShouldForceKeyFrame", func(t *testing.T) {
for packetType, packet := range map[string][]byte{
"PLI": {
// v=2, p=0, FMT=1, PSFB, len=1
0x81, 0xce, 0x00, 0x02,
// ssrc=0x0
0x00, 0x00, 0x00, 0x00,
// ssrc=0x4bc4fcb4
0x4b, 0xc4, 0xfc, 0xb4,
},
"FIR": {
// v=2, p=0, FMT=4, PSFB, len=3
0x84, 0xce, 0x00, 0x04,
// ssrc=0x0
0x00, 0x00, 0x00, 0x00,
// ssrc=0x4bc4fcb4
0x4b, 0xc4, 0xfc, 0xb4,
// ssrc=0x12345678
0x12, 0x34, 0x56, 0x78,
// Seqno=0x42
0x42, 0x00, 0x00, 0x00,
},
} {
t.Run(packetType, func(t *testing.T) {
tr := &baseTrack{}
tr.OnEnded(func(err error) {
if err != io.EOF {
t.Error(err)
}
})
stop := make(chan struct{}, 1)
defer func() {
stop <- struct{}{}
}()
mockKeyFrameController := &fakeKeyFrameController{called: make(chan struct{}, 1)}
mockRTCPReader := &fakeRTCPReader{end: stop, mockReturn: make(chan []byte, 1)}
go tr.rtcpReadLoop(mockRTCPReader, mockKeyFrameController, stop)
mockRTCPReader.mockReturn <- packet
select {
case <-time.After(1000 * time.Millisecond):
t.Error("Timeout")
case <-mockKeyFrameController.called:
}
})
}
})
} }