Compare commits

...

192 Commits

Author SHA1 Message Date
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
renovate[bot]
5ba49e03e7 Update module github.com/pion/webrtc/v3 to v3.1.27 (#369)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-04 14:58:13 +09:00
renovate[bot]
1250e06923 Update module github.com/pion/rtp to v1.7.9 (#386)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-04 14:52:29 +09:00
renovate[bot]
651c847674 Update golang.org/x/image digest to a8550c1 (#392)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-04 14:49:59 +09:00
f-fl0
3b2316081e Update webcam module to set the camera framerate (#390)
* Update webcam module to set the camera framerate
* Add framerate to property fitness function
* Set framerate to dummy VideoRecord
* Add tests about video constraints
* Set framerate only when a positive value is specified
2022-04-04 14:48:56 +09:00
Eric Daniels
70261260cb Release AVFoundation ReadCloser on camera close (#375) 2022-03-28 06:57:15 -04:00
f-fl0
548cdac668 Fix scaling functions (#385)
This commit corrects two small errors that causes the improper
results reported in #312.

- A copy paste typo (most likely) which sets the Dy component
  of the image rectangle to Dx.
- The tmp array in fastBoxSampling was not properly
  reset because the last argument in memset should be
  the number of bytes to fill in and not the number of elements.
2022-03-18 15:04:36 +09:00
renovate[bot]
79f9fc31f6 Update golang.org/x/image commit hash to 723b81c (#370)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-17 16:09:36 +09:00
Renovate Bot
1f92ea40da Update actions/setup-go action to v3
Generated by Renovate Bot
2022-03-14 10:34:25 -04:00
Renovate Bot
4beb7e5a23 Update actions/checkout action to v3
Generated by Renovate Bot
2022-03-14 10:34:10 -04:00
adamroach
9bb5755cd2 Fixing Stride Handling for OpenH264 (#381)
The golang image.YCbCr struct can contain fields of arbitrary stride
lengths, which don't necessarily match the field widths. This
patch passes the stride values from the image.YCbCr struct into
the OpenH264 encode calls.
2022-03-08 03:09:01 -06:00
Sean DuBois
e316b30964 Add RID to Track and baseTrack (#376)
This was added in pion/webrtc@f644649
2022-01-15 20:15:56 -08:00
代码人生
596b8c4e11 Add VNC as a video source (#372)
Add VNC as a video source
2021-12-08 19:55:58 +08:00
renovate[bot]
be5f684ea6 Update module github.com/pion/webrtc/v3 to v3.1.10 (#363)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-21 23:08:53 +09:00
Atsushi Watanabe
a88c2daf89 Allow double close of codecs (#364)
Video/AudioTrack.NewRTPReader() internally closes encoder on error.
It caused double free if user closes reader after error.
2021-11-21 22:57:50 +09:00
Atsushi Watanabe
1f313a9d61 Add simple read test to codecs (#367) 2021-11-20 15:42:24 +09:00
Atsushi Watanabe
19eaf375ff Fix build test target without CGO (#366)
Fix `go: warning: "pkg/..." matched no packages`
2021-11-20 13:39:57 +09:00
renovate[bot]
b3c94a1f7b Update module github.com/pion/webrtc/v3 to v3.1.6 (#361)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-01 09:36:17 +09:00
renovate[bot]
86cb9f8ce8 Update module github.com/pion/webrtc/v3 to v3.1.5 (#345)
* Update module github.com/pion/webrtc/v3 to v3.1.5
* Cast MTU type

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2021-11-01 09:20:03 +09:00
Renovate Bot
f8d1f974cf Update github.com/kbinani/screenshot commit hash to 7d3a670
Generated by Renovate Bot
2021-09-05 09:42:41 +09:00
Renovate Bot
809f74cafc Update codecov/codecov-action action to v2
Generated by Renovate Bot
2021-09-05 09:41:13 +09:00
Renovate Bot
eb2db82766 Update module github.com/gen2brain/malgo to v0.10.35
Generated by Renovate Bot
2021-07-22 09:19:54 +09:00
Renovate Bot
b4c6eb5409 Update module github.com/google/uuid to v1.3.0
Generated by Renovate Bot
2021-07-22 09:11:07 +09:00
Will Forcey
b263026d52 Fixed Comment Error 2021-07-22 08:55:34 +09:00
Renovate Bot
070ab924f9 Update golang.org/x/image commit hash to a66eb64
Generated by Renovate Bot
2021-07-12 09:55:11 +09:00
Renovate Bot
23177a5d75 Update golang.org/x/image commit hash to e6eecd4
Generated by Renovate Bot
2021-06-22 19:57:31 +09:00
Valentin Cocaud
3a04686875 Implements RequireKeyFrame for VPX codec (#334) 2021-06-12 11:28:59 -07:00
Valentin Cocaud
5f95b84719 Fix dead lock when closing peer connection with running tracks (#337) 2021-06-12 11:28:19 -07:00
renovate[bot]
8919ba4fe5 Update module github.com/pion/webrtc/v3 to v3.0.29 (#330)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-19 07:30:48 -07:00
Lukas Herman
153c36e461 Remove github.com/satori/go.uuid dependency (#329)
Resolves: https://github.com/pion/mediadevices/issues/325
2021-04-11 17:58:04 -07:00
Renovate Bot
10769b702e Update module github.com/pion/webrtc/v3 to v3.0.20
Generated by Renovate Bot
2021-04-03 11:57:41 +09:00
Renovate Bot
2948735964 Update github.com/kbinani/screenshot commit hash to b96eb33
Generated by Renovate Bot
2021-04-03 11:11:34 +09:00
Atsushi Watanabe
ba848b3416 Test on Go 1.16 and 1.15 2021-03-26 08:27:08 +09:00
Atsushi Watanabe
94c6b66e46 Include full examples/go.(sum|mod)
Since Go 1.16, go build command fails
if go.mod or go.sum is incomplete.
2021-03-26 08:27:08 +09:00
Atsushi Watanabe
96fd92142c Fix go vet errors on 1.16
- Avoid 'possible misuse of reflect.SliceHeader'
- Fix 'call to (*T).Fatal from a non-test goroutine'
2021-03-26 08:27:08 +09:00
Atsushi Watanabe
d71b72c64d Fix audio codec latency handling (#317)
To avoid buffering audio data multiple times, remove buffer from
malgo audio driver and pass expected codec latency as a codec
parameter.
2021-03-25 14:09:57 -07:00
Renovate Bot
8c2c8a9b27 Update module github.com/pion/webrtc/v3 to v3.0.17
Generated by Renovate Bot
2021-03-21 18:17:35 +09:00
Atsushi Watanabe
1e03f61b4b Stabilize pkg/driver/camera.TestDiscover
Make it testable on a computer with real cameras.
Fix order of test result.
2021-03-19 10:25:09 +09:00
Patryk Rogalski
b863c105c8 Implemented ForceKeyFrame for openh264 encoder (#319) 2021-03-12 15:26:30 -08:00
Renovate Bot
6411b00e93 Update golang.org/x/image commit hash to ac19c3e
Generated by Renovate Bot
2021-03-11 10:29:36 +09:00
Markus Tzoe
acd2cb992b Fixes pixel format mapping in avfoundation 2021-03-11 10:18:42 +09:00
Atsushi Watanabe
dafd208de7 Add test to validate Linux camera label rule 2021-03-08 14:45:45 +09:00
Atsushi Watanabe
fcec5a9149 Fix linux camera label rule document 2021-03-08 14:45:45 +09:00
Eric Daniels
3d3830f7ff Use correct NV12 decoder in camera_linux (#313) 2021-03-04 16:09:05 -08:00
Eric Daniels
655b513810 Add z16 decode support (#310) 2021-03-05 01:35:39 +09:00
Eric Daniels
eaaaacfc6b Combine reallink and label for cameras on linux (#311) 2021-03-04 11:46:50 +09:00
Eric Daniels
fa95e47bad Support RGB24 and RGB16 in x11capture_linux (#308) 2021-03-03 23:17:49 +09:00
Atsushi Watanabe
020de77bc9 Tidy go.sum 2021-03-01 13:12:20 +09:00
Eric Daniels
33b6412c26 Fix 32-bit usage of x11capture_linux 2021-03-01 11:59:19 +09:00
renovate[bot]
f29d08ae6b Update module pion/webrtc/v3 to v3.0.11 (#300)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-21 12:17:14 -08:00
Markus
b5b0653697 Fixes avfoundation panic when using NV12 frame (#302)
* Fixes frame.FormatI420 in avfoundation

* Fixes avfoundation: frame size

which causes panic when using NV12

* avfoundation: uses CVPixelBufferGetDataSize() as frame buffer size

frame: fix frame size checking for YUY2, UYVY

* add camera_darwin_test
2021-02-21 12:13:04 -08:00
Renovate Bot
a1087f7f4e Update module pion/webrtc/v3 to v3.0.5
Generated by Renovate Bot
2021-02-08 18:13:54 -08:00
Lukas Herman
217e634f7e Fix unsupported NV12 frame format
Changes:
  * Move format constants to decode.go so that the mapping and values
  are within a single file. This will help with code readability.
  * Frame decoder unit tests now use NewDecoder instead of calling
  internal decode functions to increase test coverage for public APIs
2021-02-08 18:04:36 -08:00
Lukas Herman
60b8e3ae1b Add logging for unincluded raw frame formats 2021-02-03 08:59:06 -08:00
Lukas Herman
7df3114cdc Add NV12 support for avfoundation 2021-02-03 08:59:06 -08:00
Lukas Herman
9741508d2b Add NV12 frame format decoder
Reference: https://www.fourcc.org/pixel-format/yuv-nv12/
2021-02-03 08:59:06 -08:00
Lukas Herman
7a569f0901 Fix wrong Cb and Cr order in NV21 2021-02-03 08:59:06 -08:00
Renovate Bot
ee40fcd070 Update module google/uuid to v1.2.0
Generated by Renovate Bot
2021-02-02 22:55:49 -08:00
Renovate Bot
499c08d513 Update module pion/webrtc/v3 to v3.0.4
Generated by Renovate Bot
2021-02-02 22:49:31 -08:00
Andrei Nistor
5a1bd11087 Add AVBindFrameFormatYUY2 mapping in AVFoundationBind 2021-01-14 21:25:24 -08:00
Will Forcey
7ce935eac8 Update README.md
Fixed a typo in readme
2021-01-12 19:48:36 -08:00
wawesomeNOGUI
a359005a7d Use GatheringCompletePromise in examples/webrtc
pion/webrtc@v3 enables trickle ICE by default now. Use provided helper
to block until ICE is finished gathering. For a production application
setting OnICECandidate is recommended.
2021-01-11 22:46:28 -08:00
Lukas Herman
ca4116b5ce Recompile Opus with PIC
Resolves https://github.com/pion/mediadevices/issues/284
2021-01-09 08:30:52 -08:00
122 changed files with 5856 additions and 604 deletions

View File

@@ -13,13 +13,15 @@ jobs:
strategy:
fail-fast: false
matrix:
go: [ '1.15', '1.14' ]
go:
- '1.20'
- '1.21'
name: Linux Go ${{ matrix.go }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
@@ -32,24 +34,67 @@ jobs:
libx264-dev
- name: Run Test Suite
run: make test
- uses: codecov/codecov-action@v1
if: matrix.go == '1.15'
- uses: codecov/codecov-action@v3
if: matrix.go == '1.21'
build-darwin:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
go: [ '1.15', '1.14' ]
name: Darwin Go ${{ matrix.go }}
runs-on:
- 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:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
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@v3
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
run: |
which brew
brew install \
pkg-config \
opus \
@@ -62,12 +107,12 @@ jobs:
name: Check Licenses
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: '1.15'
go-version: '1.21'
- name: Installing go-licenses
run: go get github.com/google/go-licenses
run: go install github.com/google/go-licenses@latest
- name: Checking licenses
run: go-licenses check ./...

View File

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

2
.gitignore vendored
View File

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

View File

@@ -16,7 +16,8 @@ supported_platforms := \
linux-arm64 \
linux-x64 \
windows-x64 \
darwin-x64
darwin-x64 \
darwin-arm64
cmd_build := build
cmd_test := test
examples_dir := examples
@@ -24,7 +25,8 @@ codec_dir := pkg/codec
codec_list := $(shell ls $(codec_dir)/*/Makefile)
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
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)
define BUILD_TEMPLATE
ifneq (,$$(findstring $(2)-$(3),$$(supported_platforms)))
@@ -71,11 +73,11 @@ $(foreach codec, $(codec_list), \
# Description:
# Run a series of tests
$(cmd_test):
go vet $(pkgs_without_mmal)
go build $(pkgs_without_mmal)
go vet $(pkgs_without_ext_device)
go build $(pkgs_without_ext_device)
# go build without CGO
CGO_ENABLED=0 go build . pkg/...
CGO_ENABLED=0 go build $(pkgs_without_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)
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>
<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://github.com/pion/mediadevices/actions"><img src="https://github.com/pion/mediadevices/workflows/CI/badge.svg?branch=master" alt="Build status"></a>
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://godoc.org/github.com/pion/mediadevices?status.svg" alt="GoDoc"></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://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="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</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!
## 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:
@@ -67,27 +69,23 @@ func main() {
output, _ := os.Create("frame.jpg")
jpeg.Encode(output, frame, nil)
}
```
## More Examples
### More Examples
* [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
* [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
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
## Available Media Inputs
| Input | Linux | Mac | Windows |
### Available Media Inputs
| Input | Linux | Mac | Windows |
| :--------: | :---: | :-: | :-----: |
| Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | ✔️ | ✔️ |
| Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | ✔️ | ✔️ |
By default, there's no media input registered. This decision was made to allow you to pay 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:
```go
import (
@@ -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`:
```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.
### 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.
* 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`
* Ubuntu: `apt install libx264-dev`
#### mmal
##### mmal
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)
* 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.
* 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
#### vpx
##### vpx
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)
@@ -160,7 +157,7 @@ A free software video codec library from Google and the Alliance for Open Media
* Mac: `brew install libvpx`
* 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).
* 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`
### Audio Codecs
#### Audio Codecs
#### opus
##### opus
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)
@@ -178,16 +175,14 @@ A totally open, royalty-free, highly versatile audio codec.
* Mac: `brew install opus`
* 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**.
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
### Failed to find the best driver that fits the constraints
### FAQ
#### 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:
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`
* 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`
* 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.
If you see the following error message at compile time:
```
```bash
# pkg-config --cflags -- vpx
Package vpx was not found in the pkg-config search path.
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).
* 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).
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!
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
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
### Contributing
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
* [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
## License
### License
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})

1
examples/.gitignore vendored
View File

@@ -1 +0,0 @@
go.sum

View File

@@ -5,4 +5,4 @@ examples := $(filter-out internal,$(examples))
all: $(examples)
$(examples):
cd $@ && go build
cd $@ && go build -mod=mod

View File

@@ -8,8 +8,8 @@ import (
pigo "github.com/esimov/pigo/core"
"github.com/pion/mediadevices"
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/frame"
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/prop"
)
@@ -77,7 +77,7 @@ func main() {
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = prop.FrameFormatExact(frame.FormatI420)
c.FrameFormat = prop.FrameFormatOneOf{frame.FormatI420, frame.FormatYUY2}
c.Width = prop.Int(640)
c.Height = prop.Int(480)
},
@@ -97,7 +97,7 @@ func main() {
frame, release, err := videoReader.Read()
must(err)
// Since we asked the frame format to be exactly YUY2 in GetUserMedia, we can guarantee that it must be YCbCr
// Since we asked the frame format to be exactly I420/YUY2 in GetUserMedia, we can guarantee that it must be YCbCr
if detectFace(frame.(*image.YCbCr)) {
log.Println("Detect a face")
}

View File

@@ -2,8 +2,10 @@ module github.com/pion/mediadevices/examples
go 1.14
// Please don't commit require entries of examples.
// `git checkout master examples/go.mod` to revert this file.
require github.com/pion/mediadevices v0.0.0
require (
github.com/esimov/pigo v1.4.6
github.com/pion/mediadevices v0.0.0
github.com/pion/webrtc/v3 v3.2.21
)
replace github.com/pion/mediadevices v0.0.0 => ../

220
examples/go.sum Normal file
View File

@@ -0,0 +1,220 @@
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc h1:7cMZ/f4xwkD3FUOcThPAm0uecSP5kSTUU/3RWsrmcww=
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
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.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gen2brain/malgo v0.11.10 h1:u41QchDBS7Z2rwEVPu7uycK6HA8IyzKoUOhLU7IvYW4=
github.com/gen2brain/malgo v0.11.10/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
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/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.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
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.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.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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/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.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
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.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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/ice/v2 v2.3.11 h1:rZjVmUwyT55cmN8ySMpL7rsS8KYsJERsrxJLLxpKhdw=
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I=
github.com/pion/interceptor v0.1.22 h1:khhimAF0/VmGaIfeE+bA3X1jm0lD8C8HOGcU7vpWcPA=
github.com/pion/interceptor v0.1.22/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/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.8 h1:HhicWIg7OX5PVilyBO6plhMetInbzkVJAhbdJiAeVaI=
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
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/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.2 h1:oKMM0K1/QYQ5b5qH+ikqDSZRipP5mIxPJcgcvw5sH0w=
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.8 h1:5EdnnKI4gpyR1a1TwbiS/wxEgcUWBHsc7ILAjARJB+U=
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v2 v2.0.17 h1:ECuOk+7uIpY6HUlTb0nXhfvu4REG2hjtC4ronYFCZE4=
github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.21 h1:c8fy5JcqJkAQBwwy3Sk9huQLTBUSqaggyRlv9Lnh2zY=
github.com/pion/webrtc/v3 v3.2.21/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg=
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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
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.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
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-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-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-20210226172049-e18ecbb05110/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
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.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
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-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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-20200323222414-85ca7c5b95cd/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-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-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-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 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/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-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.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
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/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.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.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
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.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 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.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

46
examples/vnc/README.md Normal file
View File

@@ -0,0 +1,46 @@
## Instructions
### Install required codecs
In this example, we'll be using x264 and opus as our video and audio codecs. Therefore, we need to make sure that these codecs are installed within our system.
Installation steps:
* [x264](https://github.com/pion/mediadevices#x264)
### Download vnc example
```
git clone https://github.com/pion/mediadevices.git
```
#### Compile vnc example
```
cd mediadevices/examples/vnc && go build
```
### Open example page
[jsfiddle.net](https://jsfiddle.net/gh/get/library/pure/pion/mediadevices/tree/master/examples/internal/jsfiddle/audio-and-video) you should see two text-areas and a 'Start Session' button
### Run the webrtc example with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser, copy that, and store the session description in an environment variable, `export SDP=<put_the_sdp_here>`
Run `echo $SDP | ./vnc`
In Windows
```powershell
type sdp.txt| .\vnc.exe
```
### Input webrtc's SessionDescription into your browser
Copy the text that `./webrtc` just emitted and copy into second text area
### Hit 'Start Session' in jsfiddle, enjoy your video!
A video should start playing in your browser above the input boxes, and will continue playing until you close the application.
Congrats, you have used pion-MediaDevices! Now start building something cool

127
examples/vnc/main.go Normal file
View File

@@ -0,0 +1,127 @@
package main
import (
"fmt"
"github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/driver/vncdriver"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/webrtc/v3"
// If you don't like x264, you can also use vpx by importing as below
// "github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
// or you can also use openh264 for alternative h264 implementation
// "github.com/pion/mediadevices/pkg/codec/openh264"
// or if you use a raspberry pi like, you can use mmal for using its hardware encoder
// "github.com/pion/mediadevices/pkg/codec/mmal"
"github.com/pion/mediadevices/pkg/codec/x264" // This is required to use h264 video encoder
// Note: If you don't have a camera or microphone or your adapters are not supported,
// you can always swap your adapters with our dummy adapters below.
// _ "github.com/pion/mediadevices/pkg/driver/videotest"
// _ "github.com/pion/mediadevices/pkg/driver/audiotest"
)
func main() {
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
driver.GetManager().Register(
vncdriver.NewVnc("127.0.0.1:5900"),
driver.Info{Label: "VNC", DeviceType: driver.Camera, Priority: driver.PriorityLow},
)
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
x264Params, err := x264.NewParams()
if err != nil {
panic(err)
}
x264Params.BitRate = 500_000 // 500kbps
if err != nil {
panic(err)
}
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
)
mediaEngine := webrtc.MediaEngine{}
codecSelector.Populate(&mediaEngine)
api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine))
peerConnection, err := api.NewPeerConnection(config)
if err != nil {
panic(err)
}
// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
},
Codec: codecSelector,
})
if err != nil {
panic(err)
}
for _, track := range s.GetTracks() {
track.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s) ended with error: %v\n",
track.ID(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(track,
webrtc.RtpTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
},
)
if err != nil {
panic(err)
}
}
// Set the remote SessionDescription
err = peerConnection.SetRemoteDescription(offer)
if err != nil {
panic(err)
}
// Create an answer
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
// Block forever
select {}
}

View File

@@ -111,13 +111,23 @@ func main() {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
// Block forever
select {}
}

49
go.mod
View File

@@ -1,20 +1,41 @@
module github.com/pion/mediadevices
go 1.13
go 1.19
require (
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
github.com/gen2brain/malgo v0.10.29
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/google/uuid v1.1.4
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 // indirect
github.com/blackjack/webcam v0.5.0
github.com/gen2brain/malgo v0.11.10
github.com/google/uuid v1.4.0
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/rtp v1.6.2
github.com/pion/webrtc/v3 v3.0.3
github.com/satori/go.uuid v1.2.0
golang.org/x/image v0.0.0-20201208152932-35266b937fa6
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
github.com/pion/rtcp v1.2.10
github.com/pion/rtp v1.8.2
github.com/pion/webrtc/v3 v3.2.21
github.com/stretchr/testify v1.8.4
golang.org/x/image v0.13.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.11 // indirect
github.com/pion/mdns v0.0.8 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/sctp v1.8.8 // indirect
github.com/pion/sdp/v3 v3.0.6 // indirect
github.com/pion/srtp/v2 v2.0.17 // 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.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

221
go.sum
View File

@@ -1,16 +1,15 @@
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd h1:u7K2oMFMd8APDV3fM1j2rO3U/XJf1g1qC3DDTKou8iM=
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/blackjack/webcam v0.5.0 h1:NImYpsAbWxglejopcQGQ3FClxhJZaBBgr5fV7nsGdvk=
github.com/blackjack/webcam v0.5.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM=
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/gen2brain/malgo v0.11.10 h1:u41QchDBS7Z2rwEVPu7uycK6HA8IyzKoUOhLU7IvYW4=
github.com/gen2brain/malgo v0.11.10/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
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/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.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -18,100 +17,128 @@ 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/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
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.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0=
github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/lxn/win v0.0.0-20201111105847-2a20daff6a55 h1:4BxFx5XCtXc+nFtXDGDW+Uu5sPtsAbvPh6RObj3fG9o=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
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.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
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.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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
github.com/pion/ice/v2 v2.0.14 h1:FxXxauyykf89SWAtkQCfnHkno6G8+bhRkNguSh9zU+4=
github.com/pion/ice/v2 v2.0.14/go.mod h1:wqaUbOq5ObDNU5ox1hRsEst0rWfsKuH1zXjQFEWiZwM=
github.com/pion/interceptor v0.0.9 h1:fk5hTdyLO3KURQsf/+RjMpEm4NE3yeTY9Kh97b5BvwA=
github.com/pion/interceptor v0.0.9/go.mod h1:dHgEP5dtxOTf21MObuBAjJeAayPxLUAZjerGH8Xr07c=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/ice/v2 v2.3.11 h1:rZjVmUwyT55cmN8ySMpL7rsS8KYsJERsrxJLLxpKhdw=
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I=
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
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/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
github.com/pion/mdns v0.0.8 h1:HhicWIg7OX5PVilyBO6plhMetInbzkVJAhbdJiAeVaI=
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
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/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sdp/v3 v3.0.3 h1:gJK9hk+JFD2NGIM1nXmqNCq1DkVaIZ9dlA3u3otnkaw=
github.com/pion/sdp/v3 v3.0.3/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
github.com/pion/srtp/v2 v2.0.1/go.mod h1:c8NWHhhkFf/drmHTAblkdu8++lsISEBBdAuiyxgqIsE=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.0 h1:UFmOBBZkTZ3LgvLRf/NGrfWdZEubcU6zkLU3PsA9YvU=
github.com/pion/transport v0.12.0/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pion/webrtc/v3 v3.0.3 h1:nAXJ5niRFRRMneuhM56xsh3J76sv+HAHCNuBmab3YR0=
github.com/pion/webrtc/v3 v3.0.3/go.mod h1:DZonLDfkjMlsY/IGixAbcq8izHu0zJIk04DYx51KUvk=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.2 h1:oKMM0K1/QYQ5b5qH+ikqDSZRipP5mIxPJcgcvw5sH0w=
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.8 h1:5EdnnKI4gpyR1a1TwbiS/wxEgcUWBHsc7ILAjARJB+U=
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v2 v2.0.17 h1:ECuOk+7uIpY6HUlTb0nXhfvu4REG2hjtC4ronYFCZE4=
github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.21 h1:c8fy5JcqJkAQBwwy3Sk9huQLTBUSqaggyRlv9Lnh2zY=
github.com/pion/webrtc/v3 v3.2.21/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg=
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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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-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-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/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-20210226172049-e18ecbb05110/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
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.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -119,17 +146,51 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-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-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.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-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.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
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/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.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.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -137,6 +198,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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -146,5 +209,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.4/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.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
import "github.com/pion/mediadevices/pkg/codec"
type EncodedBuffer struct {
Data []byte
Samples uint32
@@ -8,11 +10,13 @@ type EncodedBuffer struct {
type EncodedReadCloser interface {
Read() (EncodedBuffer, func(), error)
Close() error
codec.Controllable
}
type encodedReadCloserImpl struct {
readFn func() (EncodedBuffer, func(), error)
closeFn func() error
readFn func() (EncodedBuffer, func(), error)
closeFn func() error
controllerFn func() codec.EncoderController
}
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
@@ -23,9 +27,14 @@ func (r *encodedReadCloserImpl) Close() error {
return r.closeFn()
}
func (r *encodedReadCloserImpl) Controller() codec.EncoderController {
return r.controllerFn()
}
type encodedIOReadCloserImpl struct {
readFn func([]byte) (int, error)
closeFn func() error
readFn func([]byte) (int, error)
closeFn func() error
controller func() codec.EncoderController
}
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
@@ -48,7 +57,8 @@ func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserIm
encoded.Data = encoded.Data[n:]
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 {
return r.closeFn()
}
func (r *encodedIOReadCloserImpl) Controller() codec.EncoderController {
return r.controller()
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/pion/mediadevices/pkg/driver"
_ "github.com/pion/mediadevices/pkg/driver/audiotest"
_ "github.com/pion/mediadevices/pkg/driver/videotest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
@@ -97,37 +98,145 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
t.Fatal("expect to get at least 1 property")
}
expectedProp := driver.Properties()[0]
// Since this is a continuous value, bestConstraints should be set with the value that user specified
expectedProp.FrameRate = 30.0
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: prop.VideoConstraints{
// By reducing the width from the driver by a tiny amount, this property should be chosen.
// At the same time, we'll be able to find out if the return constraints will be properly set
// to the best constraints.
Width: prop.Int(expectedProp.Width - 1),
Height: prop.Int(expectedProp.Width),
FrameFormat: prop.FrameFormat(expectedProp.FrameFormat),
FrameRate: prop.Float(30.0),
},
// By reducing the value from the driver by a tiny amount, this property should be chosen.
// At the same time, we'll be able to find out if the return constraints will be properly set
// to the best constraints.
cases := map[string]struct {
width, height int
frameFormat frame.Format
frameRate float32
}{
"DifferentWidth": {
width: expectedProp.Width - 1,
height: expectedProp.Height,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentHeight": {
width: expectedProp.Width,
height: expectedProp.Height - 1,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentFrameFormat": {
width: expectedProp.Width,
height: expectedProp.Height,
frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate,
},
}
bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
var vc prop.VideoConstraints
if c.frameRate >= 0 {
vc = prop.VideoConstraints{
Width: prop.Int(c.width),
Height: prop.Int(c.height),
FrameFormat: prop.FrameFormat(c.frameFormat),
FrameRate: prop.Float(c.frameRate),
}
} else {
// do not specify the framerate
vc = prop.VideoConstraints{
Width: prop.Int(c.width),
Height: prop.Int(c.height),
FrameFormat: prop.FrameFormat(c.frameFormat),
}
}
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: vc,
},
}
bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
if err != nil {
t.Fatal(err)
}
if driver != bestDriver {
t.Fatal("best driver is not expected")
}
s := bestConstraints.selectedMedia
if s.Width != expectedProp.Width ||
s.Height != expectedProp.Height ||
s.FrameFormat != expectedProp.FrameFormat ||
s.FrameRate != expectedProp.FrameRate {
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
}
})
}
}
func TestSelectBestDriverConstraintsNoFit(t *testing.T) {
filterFn := driver.FilterVideoRecorder()
drivers := driver.GetManager().Query(filterFn)
if len(drivers) == 0 {
t.Fatal("expect to get at least 1 driver")
}
driver := drivers[0]
err := driver.Open()
if err != nil {
t.Fatal(err)
t.Fatal("expect to open driver successfully")
}
defer driver.Close()
if len(driver.Properties()) == 0 {
t.Fatal("expect to get at least 1 property")
}
expectedProp := driver.Properties()[0]
cases := map[string]struct {
width, height int
frameFormat frame.Format
frameRate float32
}{
"DifferentWidth": {
width: expectedProp.Width - 1,
height: expectedProp.Height,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentHeight": {
width: expectedProp.Width,
height: expectedProp.Height - 1,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentFrameFormat": {
width: expectedProp.Width,
height: expectedProp.Height,
frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate,
},
}
if driver != bestDriver {
t.Fatal("best driver is not expected")
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: prop.VideoConstraints{
Width: prop.IntExact(c.width),
Height: prop.IntExact(c.height),
FrameFormat: prop.FrameFormatExact(c.frameFormat),
FrameRate: prop.FloatExact(c.frameRate),
},
},
}
s := bestConstraints.selectedMedia
if s.Width != expectedProp.Width ||
s.Height != expectedProp.Height ||
s.FrameFormat != expectedProp.FrameFormat ||
s.FrameRate != expectedProp.FrameRate {
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
_, _, err := selectBestDriver(filterFn, wantConstraints)
if err == nil {
t.Fatal("expect to not find a driver that fits the constraints")
}
})
}
}

View File

@@ -19,6 +19,10 @@ func (track *mockMediaStreamTrack) StreamID() string {
return ""
}
func (track *mockMediaStreamTrack) RID() string {
return ""
}
func (track *mockMediaStreamTrack) Close() error {
return nil
}

View File

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

View File

@@ -1,17 +1,17 @@
// MIT License
//
//
// Copyright (c) 2019-2020 Pion
//
//
// 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
@@ -46,6 +46,8 @@
} \
} while(0)
static NSString *const UnrecognizedMacOSVersionException = @"UnrecognizedMacOSVersionException";
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property (readonly) AVBindDataCallback mCallback;
@@ -76,29 +78,23 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
!CMSampleBufferDataIsReady(sampleBuffer)) {
return;
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer == NULL) {
return;
}
imageBuffer = CVBufferRetain(imageBuffer);
CVReturn ret =
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
if (ret != kCVReturnSuccess) {
return;
}
size_t heightY = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
size_t heightUV = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
int len = (int)((heightY * bytesPerRowY) + (2 * heightUV * bytesPerRowUV));
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
_mCallback(_mPUserData, buf, len);
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
_mCallback(_mPUserData, buf, (int)dataSize);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
CVBufferRelease(imageBuffer);
}
@@ -133,13 +129,21 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
STATUS retStatus = STATUS_OK;
// Useful mapping reference from ffmpeg:
// https://github.com/FFmpeg/FFmpeg/blob/c810a9502cebe32e1dd08ee3d0d17053dde44aa9/libavdevice/avfoundation.m#L53-L80
switch (format) {
case AVBindFrameFormatNV21:
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
case AVBindFrameFormatI420:
*pFourCC = kCVPixelFormatType_420YpCbCr8Planar;
break;
case AVBindFrameFormatNV12:
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
break;
case AVBindFrameFormatUYVY:
*pFourCC = kCVPixelFormatType_422YpCbCr8;
break;
case AVBindFrameFormatYUYV:
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
@@ -150,15 +154,22 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
STATUS retStatus = STATUS_OK;
switch (fourCC) {
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
*pFormat = AVBindFrameFormatNV21;
break;
case kCVPixelFormatType_422YpCbCr8:
*pFormat = AVBindFrameFormatUYVY;
break;
case kCVPixelFormatType_420YpCbCr8Planar:
*pFormat = AVBindFrameFormatI420;
break;
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
*pFormat = AVBindFrameFormatNV12;
break;
case kCVPixelFormatType_422YpCbCr8:
*pFormat = AVBindFrameFormatUYVY;
break;
case kCVPixelFormatType_422YpCbCr8_yuvs:
*pFormat = AVBindFrameFormatYUYV;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
}
return retStatus;
}
@@ -170,34 +181,53 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(mediaType == AVBindMediaTypeVideo || mediaType == AVBindMediaTypeAudio, STATUS_UNSUPPORTED_MEDIA_TYPE);
CHK(ppDevices != NULL && pLen != NULL, STATUS_NULL_ARG);
PAVBindDevice pDevice;
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
NSArray *refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeExternalUnknown
];
NSArray *refAllTypes;
#if defined(MAC_OS_VERSION_14_0)
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
discoverySessionWithDeviceTypes: refAllTypes
mediaType: _mediaType
position: AVCaptureDevicePositionUnspecified];
discoverySessionWithDeviceTypes: refAllTypes
mediaType: _mediaType
position: AVCaptureDevicePositionUnspecified];
int i = 0;
for (AVCaptureDevice *refDevice in refSession.devices) {
if (i >= MAX_DEVICES) {
break;
}
pDevice = devices + i;
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
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++;
}
*ppDevices = devices;
*pLen = i;
cleanup:
[refPool drain];
return retStatus;
@@ -217,7 +247,7 @@ STATUS AVBindSessionInit(AVBindDevice device, PAVBindSession *ppSessionResult) {
pSession->device = device;
pSession->refCaptureSession = NULL;
*ppSessionResult = pSession;
cleanup:
return retStatus;
}
@@ -244,15 +274,15 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
STATUS retStatus = STATUS_OK;
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(pSession != NULL && dataCallback != NULL, STATUS_NULL_ARG);
AVCaptureDeviceInput *refInput;
NSError *refErr = NULL;
NSString *refUID = [NSString stringWithUTF8String: pSession->device.uid];
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refUID];
refInput = [[AVCaptureDeviceInput alloc] initWithDevice: refDevice error: &refErr];
CHK(refErr == NULL, STATUS_DEVICE_INIT_FAILED);
AVCaptureSession *refCaptureSession = [[AVCaptureSession alloc] init];
refCaptureSession.sessionPreset = AVCaptureSessionPresetMedium;
[refCaptureSession addInput: refInput];
@@ -261,7 +291,7 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
VideoDataDelegate *pDelegate = [[VideoDataDelegate alloc]
init: dataCallback
withUserData: pUserData];
AVCaptureVideoDataOutput *pOutput = [[AVCaptureVideoDataOutput alloc] init];
FourCharCode fourCC;
CHK_STATUS(frameFormatToFourCC(property.frameFormat, &fourCC));
@@ -279,10 +309,10 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
} else {
// TODO: implement audio pipeline
}
pSession->refCaptureSession = [refCaptureSession retain];
[refCaptureSession startRunning];
cleanup:
[refPool drain];
return retStatus;
@@ -293,20 +323,30 @@ STATUS AVBindSessionClose(PAVBindSession pSession) {
STATUS retStatus = STATUS_OK;
CHK(pSession != NULL, STATUS_NULL_ARG);
CHK(pSession->refCaptureSession != NULL, STATUS_OK);
[pSession->refCaptureSession stopRunning];
[pSession->refCaptureSession release];
pSession->refCaptureSession = NULL;
cleanup:
return retStatus;
}
static NSString* FourCCString(FourCharCode code) {
NSString *result = [NSString stringWithFormat:@"%c%c%c%c",
(code >> 24) & 0xff,
(code >> 16) & 0xff,
(code >> 8) & 0xff,
code & 0xff];
NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet];
return [result stringByTrimmingCharactersInSet:characterSet];
}
STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *ppProperties, int *pLen) {
STATUS retStatus = STATUS_OK;
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(pSession != NULL && ppProperties != NULL && pLen != NULL, STATUS_NULL_ARG);
NSString *refDeviceUID = [NSString stringWithUTF8String: pSession->device.uid];
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refDeviceUID];
FourCharCode fourCC;
@@ -319,15 +359,17 @@ STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *pp
for (AVCaptureDeviceFormat *refFormat in refDevice.formats) {
// TODO: Probably gives a warn to the user
if (len >= MAX_PROPERTIES) {
NSLog(@"[WARNING] skipping the rest of properties due to MAX_PROPERTIES");
break;
}
if ([refFormat.mediaType isEqual:AVMediaTypeVideo]) {
fourCC = CMFormatDescriptionGetMediaSubType(refFormat.formatDescription);
if (frameFormatFromFourCC(fourCC, &pProperty->frameFormat) != STATUS_OK) {
NSLog(@"[WARNING] skipping %@ %dx%d since it's not supported", FourCCString(fourCC), videoDimensions.width, videoDimensions.height);
continue;
}
videoFormat = (CMVideoFormatDescriptionRef) refFormat.formatDescription;
videoDimensions = CMVideoFormatDescriptionGetDimensions(videoFormat);
pProperty->height = videoDimensions.height;
@@ -335,16 +377,16 @@ STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *pp
} else {
// TODO: Get audio properties
}
pProperty++;
len++;
}
*ppProperties = pSession->properties;
*pLen = len;
cleanup:
[refPool drain];
return retStatus;
}

View File

@@ -1,6 +1,5 @@
package avfoundation
// extern void onData(void*, void*, int);
import "C"
import (
"sync"
@@ -18,11 +17,10 @@ type handleID int
//export onData
func onData(userData unsafe.Pointer, buf unsafe.Pointer, length C.int) {
data := C.GoBytes(buf, length)
handleNum := (*C.int)(userData)
cb, ok := lookup(handleID(*handleNum))
if ok {
data := C.GoBytes(buf, length)
cb(data)
}
}

View File

@@ -11,8 +11,10 @@ package avfoundation
// }
import "C"
import (
"context"
"fmt"
"io"
"sync"
"unsafe"
"github.com/pion/mediadevices/pkg/frame"
@@ -32,6 +34,7 @@ type Device struct {
// UID is a unique identifier for a device
UID string
cDevice C.AVBindDevice
Name string
}
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
@@ -40,8 +43,10 @@ func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
return C.AVBindFrameFormatI420, true
case frame.FormatNV21:
return C.AVBindFrameFormatNV21, true
case frame.FormatYUY2:
return C.AVBindFrameFormatYUY2, true
case frame.FormatNV12:
return C.AVBindFrameFormatNV12, true
case frame.FormatYUYV:
return C.AVBindFrameFormatYUYV, true
case frame.FormatUYVY:
return C.AVBindFrameFormatUYVY, true
default:
@@ -55,8 +60,10 @@ func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
return frame.FormatI420, true
case C.AVBindFrameFormatNV21:
return frame.FormatNV21, true
case C.AVBindFrameFormatYUY2:
return frame.FormatYUY2, true
case C.AVBindFrameFormatNV12:
return frame.FormatNV12, true
case C.AVBindFrameFormatYUYV:
return frame.FormatYUYV, true
case C.AVBindFrameFormatUYVY:
return frame.FormatUYVY, true
default:
@@ -81,6 +88,7 @@ func Devices(mediaType MediaType) ([]Device, error) {
for i := range devices {
devices[i].UID = C.GoString(&cDevices[i].uid[0])
devices[i].cDevice = cDevices[i]
devices[i].Name = C.GoString(&cDevices[i].name[0])
}
return devices, nil
@@ -89,9 +97,13 @@ func Devices(mediaType MediaType) ([]Device, error) {
// ReadCloser is a wrapper around the data callback from AVFoundation. The data received from the
// the underlying callback can be retrieved by calling Read.
type ReadCloser struct {
dataChan chan []byte
id handleID
onClose func()
dataChan chan []byte
id handleID
onClose func()
cancelCtx context.Context
cancelFunc func()
closeWG sync.WaitGroup
lock sync.Mutex
}
func newReadCloser(onClose func()) *ReadCloser {
@@ -99,12 +111,25 @@ func newReadCloser(onClose func()) *ReadCloser {
rc.dataChan = make(chan []byte, 1)
rc.onClose = onClose
rc.id = register(rc.dataCb)
cancelCtx, cancelFunc := context.WithCancel(context.Background())
rc.cancelCtx = cancelCtx
rc.cancelFunc = cancelFunc
return &rc
}
func (rc *ReadCloser) dataCb(data []byte) {
rc.closeWG.Add(1)
defer rc.closeWG.Done()
// TODO: add a policy for slow reader
rc.dataChan <- data
if rc.cancelCtx.Err() != nil {
return
}
select {
// Use the Done channel to avoid waiting for new data from closed camera
case <-rc.cancelCtx.Done():
case rc.dataChan <- data:
}
}
// Read reads raw data, the format is determined by the media type and property:
@@ -120,17 +145,28 @@ func (rc *ReadCloser) Read() ([]byte, func(), error) {
// Close closes the capturing session, and no data will flow anymore
func (rc *ReadCloser) Close() {
rc.lock.Lock()
defer rc.lock.Unlock()
if rc.cancelCtx.Err() != nil {
return // already closed
}
if rc.onClose != nil {
rc.onClose()
}
close(rc.dataChan)
rc.cancelFunc()
unregister(rc.id)
rc.closeWG.Wait()
close(rc.dataChan)
}
// Session represents a capturing session.
type Session struct {
device Device
cSession C.PAVBindSession
lock sync.Mutex
closed bool
}
// NewSession creates a new capturing session
@@ -148,6 +184,13 @@ func NewSession(device Device) (*Session, error) {
// Close stops capturing session and frees up resources
func (session *Session) Close() error {
session.lock.Lock()
defer session.lock.Unlock()
if session.closed {
return nil
}
session.closed = true
if session.cSession == nil {
return nil
}

View File

@@ -1,6 +1,8 @@
package codec
import (
"time"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
@@ -13,6 +15,9 @@ import (
type RTPCodec struct {
webrtc.RTPCodecParameters
rtp.Payloader
// Latency of static frame size codec.
Latency time.Duration
}
// NewRTPH264Codec is a helper to create an H264 codec
@@ -107,15 +112,37 @@ type VideoEncoderBuilder interface {
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 {
Read() (b []byte, release func(), err 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
// but this also means that the quality will also be lower.
SetBitRate(int) error
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
// BaseParams represents an codec's encoding properties

View File

@@ -0,0 +1,159 @@
// Package codectest provides shared test for codec implementations.
package codectest
import (
"image"
"io"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
func assertNoPanic(t *testing.T, fn func() error, msg string) error {
defer func() {
if r := recover(); r != nil {
t.Errorf("panic: %v: %s", r, msg)
}
}()
return fn()
}
func AudioEncoderSimpleReadTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
var eof bool
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
if eof {
return nil, nil, io.EOF
}
return w, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Fatal("Encoded frame is empty")
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func VideoEncoderSimpleReadTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
var eof bool
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
if eof {
return nil, nil, io.EOF
}
return img, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Errorf("Encoded frame is empty (%d)", i)
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func AudioEncoderCloseTwiceTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media) {
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
t.Fatal(err)
}
}
func VideoEncoderCloseTwiceTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media) {
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
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
}
img, _, err := e.r.Read()
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
imgReal := img.(*image.YCbCr)
var y, cb, cr C.Slice
y.data = (*C.uchar)(&imgReal.Y[0])
@@ -91,12 +92,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {

View File

@@ -0,0 +1,84 @@
package mmal
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
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;
}
// TODO: Remove hardcoded values
params.iUsageType = CAMERA_VIDEO_REAL_TIME;
params.iUsageType = opts.usage_type;
params.iPicWidth = opts.width;
params.iPicHeight = opts.height;
params.iTargetBitrate = opts.target_bitrate;
params.iMaxBitrate = opts.target_bitrate;
params.iRCMode = RC_BITRATE_MODE;
params.iRCMode = opts.rc_mode;
params.fMaxFrameRate = opts.max_fps;
params.bEnableFrameSkip = true;
params.uiMaxNalSize = 0;
params.uiIntraPeriod = 30;
// set to 0, so that it'll automatically use multi threads when needed
params.iMultipleThreadIdc = 0;
params.bEnableFrameSkip = opts.enable_frame_skip;
params.uiMaxNalSize = opts.max_nal_size;
params.uiIntraPeriod = opts.intra_period;
params.iMultipleThreadIdc = opts.multiple_thread_idc;
// The base spatial layer 0 is the only one we use.
params.sSpatialLayers[0].iVideoWidth = params.iPicWidth;
params.sSpatialLayers[0].iVideoHeight = params.iPicHeight;
params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate;
params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate;
params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate;
// Single NAL unit mode
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SIZELIMITED_SLICE;
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = 12800;
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = opts.slice_num;
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = opts.slice_mode;
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = opts.slice_size_constraint;
rv = engine->InitializeExt(&params);
if (rv != 0) {
@@ -80,12 +77,16 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
SFrameBSInfo info = {0};
Slice payload = {0};
if(e->force_key_frame == 1) {
e->engine->ForceIntraFrame(true);
e->force_key_frame = 0;
}
pic.iPicWidth = f.width;
pic.iPicHeight = f.height;
pic.iColorFormat = videoFormatI420;
// We always received I420 format
pic.iStride[0] = pic.iPicWidth;
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
pic.iStride[0] = f.ystride;
pic.iStride[1] = pic.iStride[2] = f.cstride;
pic.pData[0] = (unsigned char *)f.y;
pic.pData[1] = (unsigned char *)f.u;
pic.pData[2] = (unsigned char *)f.v;

View File

@@ -12,6 +12,8 @@ typedef struct Slice {
typedef struct Frame {
void *y, *u, *v;
int ystride;
int cstride;
int height;
int width;
} Frame;
@@ -20,6 +22,15 @@ typedef struct EncoderOptions {
int width, height;
int target_bitrate;
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;
typedef struct Encoder {
@@ -27,6 +38,7 @@ typedef struct Encoder {
ISVCEncoder *engine;
unsigned char *buff;
int buff_size;
int force_key_frame;
} Encoder;
Encoder *enc_new(const EncoderOptions params, int *eresult);

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
cEncoder := C.enc_new(C.EncoderOptions{
width: C.int(p.Width),
height: C.int(p.Height),
target_bitrate: C.int(params.BitRate),
max_fps: C.float(p.FrameRate),
width: C.int(p.Width),
height: C.int(p.Height),
target_bitrate: C.int(params.BitRate),
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)
if err := errResult(rv); err != nil {
return nil, fmt.Errorf("failed in creating encoder: %v", err)
@@ -56,20 +65,23 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF
}
img, _, err := e.r.Read()
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
yuvImg := img.(*image.YCbCr)
bounds := yuvImg.Bounds()
var rv C.int
s := C.enc_encode(e.engine, C.Frame{
y: unsafe.Pointer(&yuvImg.Y[0]),
u: unsafe.Pointer(&yuvImg.Cb[0]),
v: unsafe.Pointer(&yuvImg.Cr[0]),
height: C.int(bounds.Max.Y - bounds.Min.Y),
width: C.int(bounds.Max.X - bounds.Min.X),
y: unsafe.Pointer(&yuvImg.Y[0]),
u: unsafe.Pointer(&yuvImg.Cb[0]),
v: unsafe.Pointer(&yuvImg.Cr[0]),
ystride: C.int(yuvImg.YStride),
cstride: C.int(yuvImg.CStride),
height: C.int(bounds.Max.Y - bounds.Min.Y),
width: C.int(bounds.Max.X - bounds.Min.X),
}, &rv)
if err := errResult(rv); err != nil {
return nil, func() {}, fmt.Errorf("failed in encoding: %v", err)
@@ -79,18 +91,23 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, nil
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
func (e *encoder) ForceKeyFrame() error {
e.engine.force_key_frame = C.int(1)
return nil
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
var rv C.int

View File

@@ -8,5 +8,6 @@ package openh264
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-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
import "C"

View File

@@ -0,0 +1,82 @@
package openh264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"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) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
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
// #include <openh264/codec_api.h>
import "C"
import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
@@ -9,14 +12,62 @@ import (
// Params stores libopenh264 specific encoding parameters.
type Params struct {
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.
func NewParams() (Params, error) {
return Params{
BaseParams: codec.BaseParams{
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
}

View File

@@ -18,8 +18,8 @@ $(output_path): $(src_dir)/$(lib_prefix).a | $(lib_dir)
$(src_dir)/$(lib_prefix).a: | $(src_dir)
cd $(src_dir) && \
$(MEDIADEVICES_TOOLCHAIN_BIN) cmake -DOPUS_STACK_PROTECTOR=OFF && \
$(MEDIADEVICES_TOOLCHAIN_BIN) make
$(MEDIADEVICES_TOOLCHAIN_BIN) cmake -DOPUS_STACK_PROTECTOR=OFF -DCMAKE_C_FLAGS="-fpic" && \
$(MEDIADEVICES_TOOLCHAIN_BIN) make VERBOSE=1
$(src_dir): | $(src_root_dir)
git clone --depth=1 --branch=$(version) $(git_url) $@

Binary file not shown.

View File

@@ -3,7 +3,8 @@ package opus
import (
"errors"
"fmt"
"math"
"io"
"sync"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
@@ -15,7 +16,7 @@ import (
/*
#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));
}
@@ -26,9 +27,9 @@ type encoder struct {
inBuff wave.Audio
reader audio.Reader
engine *C.OpusEncoder
}
var latencies = []float64{5, 10, 20, 40, 60}
mu sync.Mutex
}
func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
var cerror C.int
@@ -37,10 +38,6 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
return nil, fmt.Errorf("opus: inProp.SampleRate is required")
}
if p.Latency == 0 {
p.Latency = 20
}
if params.BitRate == 0 {
params.BitRate = 32000
}
@@ -49,19 +46,8 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
params.ChannelMixer = &mixer.MonoMixer{}
}
// Select the nearest supported latency
var targetLatency float64
// TODO: use p.Latency.Milliseconds() after Go 1.12 EOL
latencyInMS := float64(p.Latency.Nanoseconds() / 1000000)
nearestDist := math.Inf(+1)
for _, latency := range latencies {
dist := math.Abs(latency - latencyInMS)
if dist >= nearestDist {
break
}
nearestDist = dist
targetLatency = latency
if !params.Latency.Validate() {
return nil, fmt.Errorf("opus: unsupported latency %v", params.Latency)
}
channels := p.ChannelCount
@@ -77,7 +63,7 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
}
rMix := audio.NewChannelMixer(channels, params.ChannelMixer)
rBuf := audio.NewBuffer(int(targetLatency * float64(p.SampleRate) / 1000))
rBuf := audio.NewBuffer(params.Latency.samples(p.SampleRate))
e := encoder{
engine: engine,
reader: rMix(rBuf(r)),
@@ -97,6 +83,12 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, err
}
e.mu.Lock()
defer e.mu.Unlock()
if e.engine == nil {
return nil, nil, io.EOF
}
encoded := make([]byte, 1024)
var n C.opus_int32
switch b := buff.(type) {
@@ -128,7 +120,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
}
func (e *encoder) SetBitRate(bitRate int) error {
cerror := C.bridge_encoder_set_bitrate(
cerror := C.pion_set_encoder_bitrate(
e.engine,
C.int(bitRate),
)
@@ -139,11 +131,16 @@ func (e *encoder) SetBitRate(bitRate int) error {
return nil
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.engine == nil {
return nil
}
C.opus_encoder_destroy(e.engine)
e.engine = nil
return nil

View File

@@ -8,5 +8,6 @@ package opus
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-arm64.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,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-arm64.a
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-windows-x64.a
import "C"

View File

@@ -0,0 +1,79 @@
package opus
import (
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/prop"
"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) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderSimpleReadTest(t, &p,
prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
},
wave.NewInt16Interleaved(wave.ChunkInfo{
Len: 960,
SamplingRate: 48000,
Channels: 2,
}),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderCloseTwiceTest(t, &p, prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
})
})
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

@@ -1,27 +1,69 @@
package opus
import (
"time"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave/mixer"
)
// Latency is a type of OPUS codec frame duration.
type Latency time.Duration
// Latency values available in OPUS codec.
const (
Latency2500us Latency = Latency(2500 * time.Microsecond)
Latency5ms Latency = Latency(5 * time.Millisecond)
Latency10ms Latency = Latency(10 * time.Millisecond)
Latency20ms Latency = Latency(20 * time.Millisecond)
Latency40ms Latency = Latency(40 * time.Millisecond)
Latency60ms Latency = Latency(60 * time.Millisecond)
)
// Validate that the Latency is allowed in OPUS.
func (l Latency) Validate() bool {
switch l {
case Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms:
return true
default:
return false
}
}
// Duration returns latency in time.Duration.
func (l Latency) Duration() time.Duration {
return time.Duration(l)
}
// samples returns number of samples for given sample rate.
func (l Latency) samples(sampleRate int) int {
return int(l.Duration() * time.Duration(sampleRate) / time.Second)
}
// Params stores opus specific encoding parameters.
type Params struct {
codec.BaseParams
// ChannelMixer is a mixer to be used if number of given and expected channels differ.
ChannelMixer mixer.ChannelMixer
// Expected latency of the codec.
Latency Latency
}
// NewParams returns default opus codec specific parameters.
func NewParams() (Params, error) {
return Params{}, nil
return Params{
Latency: Latency20ms,
}, nil
}
// RTPCodec represents the codec metadata
func (p *Params) RTPCodec() *codec.RTPCodec {
return codec.NewRTPOpusCodec(48000)
c := codec.NewRTPOpusCodec(48000)
c.Latency = time.Duration(p.Latency)
return c
}
// BuildAudioEncoder builds opus encoder with given params

View File

@@ -0,0 +1,49 @@
package opus
import (
"fmt"
"testing"
"time"
)
func TestLatency_Validate(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
for _, l := range []Latency{
Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms,
} {
if !l.Validate() {
t.Errorf("Defined Latency(%v) must be valid", l)
}
}
})
t.Run("Invalid", func(t *testing.T) {
for _, l := range []Latency{
0, Latency(time.Second),
} {
if l.Validate() {
t.Errorf("Latency(%v) must be valid", l)
}
}
})
}
func TestLatency_samples(t *testing.T) {
testCases := []struct {
latency Latency
sampleRate int
samples int
}{
{Latency5ms, 48000, 240},
{Latency20ms, 16000, 320},
{Latency20ms, 48000, 960},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(fmt.Sprintf("%v_%d", time.Duration(testCase.latency), testCase.sampleRate), func(t *testing.T) {
samples := testCase.latency.samples(testCase.sampleRate)
if samples != testCase.samples {
t.Errorf("Expected samples: %d, got: %d", testCase.samples, samples)
}
})
}
}

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
// Package vaapi implements hardware accelerated codecs.

View File

@@ -0,0 +1,89 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
import (
"errors"
"image"
"os"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
if _, err := os.Stat("/dev/dri/card0"); errors.Is(err, os.ErrNotExist) {
t.Skip("/dev/dri/card0 not found")
}
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
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

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -61,6 +62,7 @@ import (
"image"
"io"
"sync"
"sync/atomic"
"unsafe"
"github.com/pion/mediadevices/pkg/codec"
@@ -99,6 +101,8 @@ type encoderVP8 struct {
rate *framerateDetector
forceKeyFrame atomic.Bool
mu sync.Mutex
closed bool
}
@@ -303,10 +307,11 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF
}
img, _, err := e.r.Read()
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
yuvImg := img.(*image.YCbCr)
kf := e.frameCnt%e.params.KeyFrameInterval == 0
@@ -314,7 +319,7 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
e.frParam.data.framerate = C.uint(e.rate.Calc())
if kf {
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
// Key frame
C.setForceKFFlagVP8(&e.picParam, 1)
C.setFrameTypeFlagVP8(&e.picParam, 0)
@@ -540,18 +545,22 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoderVP8) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
func (e *encoderVP8) Controller() codec.EncoderController {
return e
}
func (e *encoderVP8) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoderVP8) ForceKeyFrame() {
e.forceKeyFrame.Store(true)
}
func (e *encoderVP8) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

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

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -44,6 +45,7 @@ import (
"image"
"io"
"sync"
"sync/atomic"
"unsafe"
"github.com/pion/mediadevices/pkg/codec"
@@ -91,6 +93,8 @@ type encoderVP9 struct {
rate *framerateDetector
forceKeyFrame atomic.Bool
mu sync.Mutex
closed bool
}
@@ -292,10 +296,11 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF
}
img, _, err := e.r.Read()
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
yuvImg := img.(*image.YCbCr)
kf := e.frameCnt%e.params.KeyFrameInterval == 0
@@ -303,7 +308,7 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
e.frParam.data.framerate = C.uint(e.rate.Calc())
if kf {
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
C.setForceKFFlag9(&e.picParam, 1)
C.setFrameTypeFlagVP9(&e.picParam, 0)
e.picParam.refresh_frame_flags = 0
@@ -475,18 +480,22 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoderVP9) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
func (e *encoderVP9) Controller() codec.EncoderController {
return e
}
func (e *encoderVP9) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoderVP9) ForceKeyFrame() {
e.forceKeyFrame.Store(true)
}
func (e *encoderVP9) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

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

@@ -16,6 +16,7 @@ type Params struct {
RateControlOvershootPercent uint
RateControlMinQuantizer uint
RateControlMaxQuantizer uint
LagInFrames uint
ErrorResilient ErrorResilientMode
}

View File

@@ -23,6 +23,9 @@ package vpx
// int pktSz(vpx_codec_cx_pkt_t *pkt) {
// return pkt->data.frame.sz;
// }
// vpx_codec_frame_flags_t pktFrameFlags(vpx_codec_cx_pkt_t *pkt) {
// return pkt->data.frame.flags;
// }
//
// // Alloc helpers
// vpx_codec_ctx_t *newCtx() {
@@ -61,15 +64,17 @@ import (
)
type encoder struct {
codec *C.vpx_codec_ctx_t
raw *C.vpx_image_t
cfg *C.vpx_codec_enc_cfg_t
r video.Reader
frameIndex int
tStart int
tLastFrame int
frame []byte
deadline int
codec *C.vpx_codec_ctx_t
raw *C.vpx_image_t
cfg *C.vpx_codec_enc_cfg_t
r video.Reader
frameIndex int
tStart int
tLastFrame int
frame []byte
deadline int
requireKeyFrame bool
isKeyFrame bool
mu sync.Mutex
closed bool
@@ -141,6 +146,7 @@ func newParams(codecIface *C.vpx_codec_iface_t) (Params, error) {
RateControlOvershootPercent: uint(cfg.rc_overshoot_pct),
RateControlMinQuantizer: uint(cfg.rc_min_quantizer),
RateControlMaxQuantizer: uint(cfg.rc_max_quantizer),
LagInFrames: uint(cfg.g_lag_in_frames),
ErrorResilient: ErrorResilientMode(cfg.g_error_resilient),
}, nil
}
@@ -171,6 +177,7 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
cfg.g_h = C.uint(p.Height)
cfg.g_timebase.num = 1
cfg.g_timebase.den = 1000
cfg.g_lag_in_frames = C.uint(params.LagInFrames)
cfg.rc_target_bitrate = C.uint(params.BitRate) / 1000
cfg.kf_max_dist = C.uint(params.KeyFrameInterval)
@@ -212,10 +219,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, io.EOF
}
img, _, err := e.r.Read()
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
yuvImg := img.(*image.YCbCr)
bounds := yuvImg.Bounds()
height := C.int(bounds.Dy())
@@ -254,6 +262,9 @@ func (e *encoder) Read() ([]byte, func(), error) {
duration = 1
}
var flags int
if e.requireKeyFrame {
flags = flags | C.VPX_EFLAG_FORCE_KF
}
if ec := C.encode_wrapper(
e.codec, e.raw,
C.long(t-e.tStart), C.ulong(duration), C.long(flags), C.ulong(e.deadline),
@@ -262,6 +273,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
return nil, func() {}, fmt.Errorf("vpx_codec_encode failed (%d)", ec)
}
e.requireKeyFrame = false
e.frameIndex++
e.tLastFrame = t
@@ -273,6 +285,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
break
}
if pkt.kind == C.VPX_CODEC_CX_FRAME_PKT {
e.isKeyFrame = C.pktFrameFlags(pkt)&C.VPX_FRAME_IS_KEY == C.VPX_FRAME_IS_KEY
encoded := C.GoBytes(unsafe.Pointer(C.pktBuf(pkt)), C.pktSz(pkt))
e.frame = append(e.frame, encoded...)
}
@@ -283,18 +296,25 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
func (e *encoder) ForceKeyFrame() error {
e.mu.Lock()
defer e.mu.Unlock()
e.requireKeyFrame = true
return nil
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
C.free(unsafe.Pointer(e.raw))

View File

@@ -7,11 +7,82 @@ import (
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
p.LagInFrames = 0
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
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,
),
)
})
})
}
}
func TestImageSizeChange(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
@@ -20,6 +91,8 @@ func TestImageSizeChange(t *testing.T) {
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
// Disable latency to ease test and begin to receive packets for each input frame
p.LagInFrames = 0
return &p, err
},
} {
@@ -87,3 +160,88 @@ func TestImageSizeChange(t *testing.T) {
})
}
}
func TestRequestKeyFrame(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
// Disable latency to ease test and begin to receive packets for each input frame
p.LagInFrames = 0
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
param, err := factory()
if err != nil {
t.Fatal(err)
}
var initialWidth, initialHeight, width, height int = 320, 240, 320, 240
var cnt uint32
r, err := param.BuildVideoEncoder(
video.ReaderFunc(func() (image.Image, func(), error) {
i := atomic.AddUint32(&cnt, 1)
if i == 3 {
return nil, nil, io.EOF
}
return image.NewYCbCr(
image.Rect(0, 0, width, height),
image.YCbCrSubsampleRatio420,
), func() {}, nil
}),
prop.Media{
Video: prop.Video{
Width: initialWidth,
Height: initialHeight,
FrameRate: 1,
FrameFormat: frame.FormatI420,
},
},
)
if err != nil {
t.Fatal(err)
}
_, rel, err := r.Read()
if err != nil {
t.Fatal(err)
}
rel()
r.Controller().(codec.KeyFrameController).ForceKeyFrame()
_, rel, err = r.Read()
if err != nil {
t.Fatal(err)
}
if !r.(*encoder).isKeyFrame {
t.Fatal("Not a key frame")
}
rel()
_, _, err = r.Read()
if err != io.EOF {
t.Fatal(err)
}
})
}
}
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_picture_t pic_in;
x264_param_t param;
int force_key_frame;
} Encoder;
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[1] = cb;
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);
e->force_key_frame = 0;
Slice s = {.data_len = frame_size};
if (frame_size <= 0) {
*rc = ERR_ENCODE;

View File

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

View File

@@ -0,0 +1,87 @@
package x264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
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

@@ -1 +1,16 @@
/*
Package camera provides a video camera driver.
Device Label Generation Rules
On Linux, the device label will be in the format of:
pci-0000:00:00.0-usb-0:0:0.0-video-index0;video0
If /dev/v4l/by-path/* is not available (for example in a docker container without
bindings in /dev/v4l/by-path/), it will be:
video0;video0
*/
package camera
// LabelSeparator is used to separate labels for a driver that
// is found from multiple locations on a host.
const LabelSeparator = ";"

View File

@@ -56,7 +56,7 @@ func BenchmarkRead(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := r.Read()
_, _, err := r.Read()
if err != nil {
b.Fatalf("Failed to read: %v", err)
}

View File

@@ -13,9 +13,15 @@ import (
type camera struct {
device avfoundation.Device
session *avfoundation.Session
rcClose func()
}
func init() {
Initialize()
}
// Initialize finds and registers camera devices. This is part of an experimental API.
func Initialize() {
devices, err := avfoundation.Devices(avfoundation.Video)
if err != nil {
panic(err)
@@ -26,6 +32,7 @@ func init() {
driver.GetManager().Register(cam, driver.Info{
Label: device.UID,
DeviceType: driver.Camera,
Name: device.Name,
})
}
}
@@ -43,6 +50,9 @@ func (cam *camera) Open() error {
}
func (cam *camera) Close() error {
if cam.rcClose != nil {
cam.rcClose()
}
return cam.session.Close()
}
@@ -56,6 +66,7 @@ func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) {
if err != nil {
return nil, err
}
cam.rcClose = rc.Close
r := video.ReaderFunc(func() (image.Image, func(), error) {
frame, _, err := rc.Read()
if err != nil {

View File

@@ -0,0 +1,63 @@
// +build darwin
// $ go test -v . -tags darwin -run="^TestCameraFrameFormatSupport$"
package camera
import (
"testing"
"github.com/pion/mediadevices/pkg/avfoundation"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestCameraFrameFormatSupport(t *testing.T) {
devices, err := avfoundation.Devices(avfoundation.Video)
if err != nil {
t.Fatal(err)
}
if len(devices) > 0 {
c := newCamera(devices[0])
if err := c.Open(); err != nil {
t.Fatal(err)
}
defer c.Close()
supportedFormats := make(map[frame.Format]struct{})
for _, p := range c.Properties() {
supportedFormats[p.FrameFormat] = struct{}{}
}
for _, format := range []frame.Format{
frame.FormatI420,
frame.FormatNV12,
frame.FormatNV21,
frame.FormatYUY2,
frame.FormatUYVY,
} {
if _, ok := supportedFormats[format]; !ok {
t.Logf("[%v] UNSUPPORTED", format)
continue
}
r, err := c.VideoRecord(prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameFormat: format,
}})
if err != nil {
t.Logf("[%v] Failed to capture image: %v", format, err)
continue
}
for i := 0; i < 10; i++ {
_, _, err := r.Read()
if err != nil {
t.Logf("[%v] Failed to read: %v", format, err)
continue
}
}
t.Logf("[%v] OK", format)
}
}
}

View File

@@ -8,9 +8,15 @@ import (
"errors"
"image"
"io"
"math"
"os"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/blackjack/webcam"
"github.com/pion/mediadevices/pkg/driver"
@@ -59,6 +65,8 @@ var (
}
)
const bufCount = 2
// Camera implementation using v4l2
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
type camera struct {
@@ -69,55 +77,82 @@ type camera struct {
started bool
mutex sync.Mutex
cancel func()
prevFrameTime time.Time
}
func init() {
discovered := make(map[string]struct{})
Initialize()
}
discover := func(pattern string) {
devices, err := filepath.Glob(pattern)
if err != nil {
// No v4l device.
return
}
for _, device := range devices {
label := filepath.Base(device)
reallink, err := os.Readlink(device)
if err != nil {
reallink = label
} else {
reallink = filepath.Base(reallink)
}
if _, ok := discovered[reallink]; ok {
continue
}
discovered[reallink] = struct{}{}
cam := newCamera(device)
priority := driver.PriorityNormal
if label == prioritizedDevice {
priority = driver.PriorityHigh
}
driver.GetManager().Register(cam, driver.Info{
Label: label,
DeviceType: driver.Camera,
Priority: priority,
})
}
// 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{})
discover(discovered, "/dev/v4l/by-id/*")
discover(discovered, "/dev/v4l/by-path/*")
discover(discovered, "/dev/video*")
}
discover("/dev/v4l/by-path/*")
discover("/dev/video*")
func discover(discovered map[string]struct{}, pattern string) {
devices, err := filepath.Glob(pattern)
if err != nil {
// No v4l device.
return
}
for _, device := range devices {
label := filepath.Base(device)
reallink, err := os.Readlink(device)
if err != nil {
reallink = label
} else {
reallink = filepath.Base(reallink)
}
if _, ok := discovered[reallink]; ok {
continue
}
discovered[reallink] = struct{}{}
cam := newCamera(device)
priority := driver.PriorityNormal
if reallink == prioritizedDevice {
priority = driver.PriorityHigh
}
var name, busInfo string
if webcamCam, err := webcam.Open(cam.path); err == nil {
name, _ = webcamCam.GetName()
busInfo, _ = webcamCam.GetBusInfo()
}
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,
DeviceType: driver.Camera,
Priority: priority,
})
}
}
func newCamera(path string) *camera {
formats := map[webcam.PixelFormat]frame.Format{
webcam.PixelFormat(C.V4L2_PIX_FMT_YUV420): frame.FormatI420,
webcam.PixelFormat(C.V4L2_PIX_FMT_NV21): frame.FormatNV21,
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV12,
webcam.PixelFormat(C.V4L2_PIX_FMT_YUYV): frame.FormatYUYV,
webcam.PixelFormat(C.V4L2_PIX_FMT_UYVY): frame.FormatUYVY,
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV21,
webcam.PixelFormat(C.V4L2_PIX_FMT_MJPEG): frame.FormatMJPEG,
webcam.PixelFormat(C.V4L2_PIX_FMT_Z16): frame.FormatZ16,
}
reversedFormats := make(map[frame.Format]webcam.PixelFormat)
@@ -133,14 +168,32 @@ func newCamera(path string) *camera {
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 {
cam, err := webcam.Open(c.path)
if err != nil {
return err
}
// Late frames should be discarded. Buffering should be handled in higher level.
cam.SetBufferCount(1)
// Buffering should be handled in higher level.
err = cam.SetBufferCount(bufCount)
if err != nil {
return err
}
c.prevFrameTime = time.Now()
c.cam = cam
return nil
}
@@ -179,12 +232,21 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, err
}
if p.FrameRate > 0 {
err = c.cam.SetFramerate(float32(p.FrameRate))
if err != nil {
return nil, err
}
}
if err := c.cam.StartStreaming(); err != nil {
return nil, err
}
cam := c.cam
readTimeoutSec := getCameraReadTimeout()
ctx, cancel := context.WithCancel(context.Background())
c.cancel = cancel
var buf []byte
@@ -200,7 +262,14 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
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) {
case nil:
case *webcam.Timeout:
@@ -216,6 +285,10 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, func() {}, err
}
if p.DiscardFramesOlderThan != 0 {
c.prevFrameTime = time.Now()
}
// Frame is empty.
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
if len(b) == 0 {
@@ -249,13 +322,31 @@ func (c *camera) Properties() []prop.Media {
}
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
properties = append(properties, prop.Media{
Video: prop.Video{
Width: int(frameSize.MaxWidth),
Height: int(frameSize.MaxHeight),
FrameFormat: supportedFormat,
},
})
framerates := c.cam.GetSupportedFramerates(format, uint32(frameSize.MaxWidth), uint32(frameSize.MaxHeight))
// If the camera doesn't support framerate, we just add the resolution and format
if len(framerates) == 0 {
properties = append(properties, prop.Media{
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 {
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
for _, supportedResolution := range supportedResolutions {
@@ -274,16 +365,100 @@ func (c *camera) Properties() []prop.Media {
continue
}
properties = append(properties, prop.Media{
Video: prop.Video{
Width: width,
Height: height,
FrameFormat: supportedFormat,
},
})
framerates := c.cam.GetSupportedFramerates(format, uint32(width), uint32(height))
if len(framerates) == 0 {
properties = append(properties, prop.Media{
Video: prop.Video{
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
}
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

@@ -0,0 +1,193 @@
package camera
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/pion/mediadevices/pkg/driver"
)
func TestDiscover(t *testing.T) {
const (
shortName = "unittest-video0"
shortName2 = "unittest-video1"
longName = "unittest-long-device-name:0:1:2:3"
)
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
byPathDir := filepath.Join(dir, "v4l", "by-path")
if err := os.MkdirAll(byPathDir, 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(byPathDir, longName),
); err != nil {
t.Fatal(err)
}
discovered := make(map[string]struct{})
discover(discovered, filepath.Join(byPathDir, "*"))
discover(discovered, filepath.Join(dir, "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, "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 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

@@ -76,6 +76,7 @@ int listCamera(cameraList* list, const char** errstr)
{
list->name[i] = getCameraName(moniker);
moniker->Release();
i++;
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package driver
import (
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
@@ -30,6 +31,7 @@ type Info struct {
Label string
DeviceType DeviceType
Priority Priority
Name string
}
type Adapter interface {
@@ -44,3 +46,14 @@ type Driver interface {
Info() Info
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
import "sync"
// FilterFn is being used to decide if a driver should be included in the
// query result.
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
type Manager struct {
mu sync.Mutex
drivers map[string]Driver
}
@@ -69,6 +72,8 @@ func GetManager() *Manager {
// Register registers adapter to be discoverable by Query
func (m *Manager) Register(a Adapter, info Info) error {
m.mu.Lock()
defer m.mu.Unlock()
d := wrapAdapter(a, info)
m.drivers[d.ID()] = d
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.
func (m *Manager) Query(f FilterFn) []Driver {
m.mu.Lock()
defer m.mu.Unlock()
results := make([]Driver, 0)
for _, d := range m.drivers {
if ok := f(d); ok {
@@ -85,3 +92,10 @@ func (m *Manager) Query(f FilterFn) []Driver {
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
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"
)
func filterTrue(d Driver) bool {
func filterTrue(_ Driver) bool {
return true
}
func filterFalse(d Driver) bool {
func filterFalse(_ Driver) bool {
return false
}
@@ -40,3 +44,71 @@ func TestFilterAnd(t *testing.T) {
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
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"time"
"unsafe"
@@ -32,10 +34,16 @@ var (
type microphone struct {
malgo.DeviceInfo
chunkChan chan []byte
chunkChan chan []byte
deviceCloseFunc func()
}
func init() {
Initialize()
}
// Initialize finds and registers active playback or capture devices. This is part of an experimental API.
func Initialize() {
var err error
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
logger.Debugf("%v\n", message)
@@ -60,6 +68,7 @@ func init() {
Label: device.ID.String(),
DeviceType: driver.Microphone,
Priority: priority,
Name: info.Name(),
})
}
}
@@ -87,9 +96,8 @@ func (m *microphone) Open() error {
}
func (m *microphone) Close() error {
if m.chunkChan != nil {
close(m.chunkChan)
m.chunkChan = nil
if m.deviceCloseFunc != nil {
m.deviceCloseFunc()
}
return nil
}
@@ -111,6 +119,9 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
config.PerformanceProfile = malgo.LowLatency
config.Capture.Channels = uint32(inputProp.ChannelCount)
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 {
config.Capture.Format = malgo.FormatF32
} else if inputProp.SampleSize == 2 && !inputProp.IsFloat {
@@ -119,26 +130,44 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
return nil, errUnsupportedFormat
}
cancelCtx, cancel := context.WithCancel(context.Background())
onRecvChunk := func(_, chunk []byte, framecount uint32) {
m.chunkChan <- chunk
select {
case <-cancelCtx.Done():
case m.chunkChan <- chunk:
}
}
callbacks.Data = onRecvChunk
device, err := malgo.InitDevice(ctx.Context, config, callbacks)
if err != nil {
cancel()
return nil, err
}
err = device.Start()
if err != nil {
cancel()
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) {
chunk, ok := <-m.chunkChan
if !ok {
device.Stop()
device.Uninit()
m.deviceCloseFunc()
return nil, func() {}, io.EOF
}
@@ -155,9 +184,6 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
return decodedChunk, func() {}, err
})
// FIXME: The current audio detection and audio encoder can only work with a static latency. Since the latency from the driver
// can fluctuate, we need to stabilize it. Maybe there's a better way for doing this?
reader = audio.NewBuffer(int(inputProp.Latency.Seconds() * float64(inputProp.SampleRate)))(reader)
return reader, nil
}
@@ -171,36 +197,32 @@ func (m *microphone) Properties() []prop.Media {
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.
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
sampleRate := 48000
for i := 0; i < int(m.FormatCount); i++ {
format := m.Formats[i]
supportedProp := prop.Media{
Audio: prop.Audio{
ChannelCount: int(ch),
SampleRate: int(sampleRate),
IsBigEndian: isBigEndian,
// miniaudio only supports interleaved at the moment
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)
supportedProp := prop.Media{
Audio: prop.Audio{
ChannelCount: int(format.Channels),
SampleRate: int(sampleRate),
IsBigEndian: isBigEndian,
// miniaudio only supports interleaved at the moment
IsInterleaved: true,
// FIXME: should change this to a less discrete value
Latency: time.Millisecond * 20,
},
}
switch malgo.FormatType(format.Format) {
case malgo.FormatF32:
supportedProp.SampleSize = 4
supportedProp.IsFloat = true
case malgo.FormatS16:
supportedProp.SampleSize = 2
supportedProp.IsFloat = false
}
supportedProps = append(supportedProps, supportedProp)
// }
}
return supportedProps

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package screen
// #cgo pkg-config: x11 xext
// #include <stdint.h>
// #include <string.h>
// #include <sys/shm.h>
// #include <X11/Xlib.h>
// #define XUTIL_DEFINE_FUNCTIONS
@@ -125,6 +126,14 @@ func (c colorFunc) RGBA() (r, g, b, a uint32) {
func (s *shmImage) At(x, y int) color.Color {
switch s.pixFmt {
case pixFmtRGB24:
addr := (x + y*int(s.img.width)) * 4
r := uint32(s.b[addr]) * 0x100
g := uint32(s.b[addr+1]) * 0x100
b := uint32(s.b[addr+2]) * 0x100
return colorFunc(func() (_, _, _, _ uint32) {
return r, g, b, 0xFFFF
})
case pixFmtBGR24:
addr := (x + y*int(s.img.width)) * 4
b := uint32(s.b[addr]) * 0x100
@@ -133,6 +142,15 @@ func (s *shmImage) At(x, y int) color.Color {
return colorFunc(func() (_, _, _, _ uint32) {
return r, g, b, 0xFFFF
})
case pixFmtRGB16:
addr := (x + y*int(s.img.width)) * 2
b1, b2 := s.b[addr], s.b[addr+1]
r := uint32(b1>>3) * 0x100
g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100
b := uint32(b2&0x1F) * 0x100
return colorFunc(func() (_, _, _, _ uint32) {
return r, g, b, 0xFFFF
})
case pixFmtBGR16:
addr := (x + y*int(s.img.width)) * 2
b1, b2 := s.b[addr], s.b[addr+1]
@@ -149,12 +167,25 @@ func (s *shmImage) At(x, y int) color.Color {
func (s *shmImage) RGBAAt(x, y int) color.RGBA {
switch s.pixFmt {
case pixFmtRGB24:
addr := (x + y*int(s.img.width)) * 4
r := s.b[addr]
g := s.b[addr+1]
b := s.b[addr+2]
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
case pixFmtBGR24:
addr := (x + y*int(s.img.width)) * 4
b := s.b[addr]
g := s.b[addr+1]
r := s.b[addr+2]
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
case pixFmtRGB16:
addr := (x + y*int(s.img.width)) * 2
b1, b2 := s.b[addr], s.b[addr+1]
r := b1 >> 3
g := (b1&0x7)<<3 | (b2&0xE0)>>5
b := b2 & 0x1F
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
case pixFmtBGR16:
addr := (x + y*int(s.img.width)) * 2
b1, b2 := s.b[addr], s.b[addr+1]
@@ -178,11 +209,17 @@ func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA {
dst.Pix = dst.Pix[:l]
}
switch s.pixFmt {
case pixFmtRGB24:
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
return dst
case pixFmtBGR24:
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
return dst
case pixFmtRGB16:
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
return dst
case pixFmtBGR16:
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
return dst
default:
panic("unsupported pixel format")
@@ -199,8 +236,12 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
s := &shmImage{dp: dp}
switch {
case v.red_mask == 0xFF && v.green_mask == 0xFF00 && v.blue_mask == 0xFF0000:
s.pixFmt = pixFmtRGB24
case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF:
s.pixFmt = pixFmtBGR24
case v.red_mask == 0x1F && v.green_mask == 0x7E0 && v.blue_mask == 0xF800:
s.pixFmt = pixFmtRGB16
case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F:
s.pixFmt = pixFmtBGR16
default:
@@ -209,7 +250,7 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
return nil, errors.New("unsupported pixel format")
}
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.ulong(w*h*4+8), C.IPC_CREAT|0600)
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.size_t(w*h*4+8), C.IPC_CREAT|0600)
if s.shm.shmid == -1 {
return nil, errors.New("failed to get shared memory")
}
@@ -279,5 +320,5 @@ func (r *reader) Close() {
// cAlign64 is fot testing
func cAlign64(ptr uintptr) uintptr {
return uintptr(C.align64ForTest(C.ulong(uintptr(ptr))))
return uintptr(C.align64ForTest(C.size_t(uintptr(ptr))))
}

View File

@@ -47,10 +47,6 @@ func (d *dummy) Close() error {
}
func (d *dummy) VideoRecord(p prop.Media) (video.Reader, error) {
if p.FrameRate == 0 {
p.FrameRate = 30
}
colors := [][3]byte{
{235, 128, 128},
{210, 16, 146},
@@ -143,6 +139,7 @@ func (d dummy) Properties() []prop.Media {
Width: 640,
Height: 480,
FrameFormat: frame.FormatYUYV,
FrameRate: 30,
},
},
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
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.

View File

@@ -0,0 +1,22 @@
# VNC Library for Go
go-vnc is a VNC library for Go, initially supporting VNC clients but
with the goal of eventually implementing a VNC server.
This library implements [RFC 6143][rfc6143].
## RFCs
### Implemented
- **RFC **: [The Remote Framebuffer Protocol][rfc6143]
[rfc6143]: http://tools.ietf.org/html/rfc6143
## Usage & Installation
The library is installable via standard `go get`. The package name is `vnc`.
```
$ go get github.com/mitchellh/go-vnc
```
Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-vnc

View File

@@ -0,0 +1,494 @@
// Package vnc implements a VNC client.
//
// References:
// [PROTOCOL]: http://tools.ietf.org/html/rfc6143
package vnc
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"unicode"
)
type ClientConn struct {
c net.Conn
config *ClientConfig
// If the pixel format uses a color map, then this is the color
// map that is used. This should not be modified directly, since
// the data comes from the server.
ColorMap [256]Color
// Encodings supported by the client. This should not be modified
// directly. Instead, SetEncodings should be used.
Encs []Encoding
// Width of the frame buffer in pixels, sent from the server.
FrameBufferWidth uint16
// Height of the frame buffer in pixels, sent from the server.
FrameBufferHeight uint16
// Name associated with the desktop, sent from the server.
DesktopName string
// The pixel format associated with the connection. This shouldn't
// be modified. If you wish to set a new pixel format, use the
// SetPixelFormat method.
PixelFormat PixelFormat
}
// A ClientConfig structure is used to configure a ClientConn. After
// one has been passed to initialize a connection, it must not be modified.
type ClientConfig struct {
// A slice of ClientAuth methods. Only the first instance that is
// suitable by the server will be used to authenticate.
Auth []ClientAuth
// Exclusive determines whether the connection is shared with other
// clients. If true, then all other clients connected will be
// disconnected when a connection is established to the VNC server.
Exclusive bool
// The channel that all messages received from the server will be
// sent on. If the channel blocks, then the goroutine reading data
// from the VNC server may block indefinitely. It is up to the user
// of the library to ensure that this channel is properly read.
// If this is not set, then all messages will be discarded.
ServerMessageCh chan<- ServerMessage
// A slice of supported messages that can be read from the server.
// This only needs to contain NEW server messages, and doesn't
// need to explicitly contain the RFC-required messages.
ServerMessages []ServerMessage
}
func Client(c net.Conn, cfg *ClientConfig) (*ClientConn, error) {
conn := &ClientConn{
c: c,
config: cfg,
}
if err := conn.handshake(); err != nil {
conn.Close()
return nil, err
}
go conn.mainLoop()
return conn, nil
}
func (c *ClientConn) Close() error {
return c.c.Close()
}
// CutText tells the server that the client has new text in its cut buffer.
// The text string MUST only contain Latin-1 characters. This encoding
// is compatible with Go's native string format, but can only use up to
// unicode.MaxLatin values.
//
// See RFC 6143 Section 7.5.6
func (c *ClientConn) CutText(text string) error {
var buf bytes.Buffer
// This is the fixed size data we'll send
fixedData := []interface{}{
uint8(6),
uint8(0),
uint8(0),
uint8(0),
uint32(len(text)),
}
for _, val := range fixedData {
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
return err
}
}
for _, char := range text {
if char > unicode.MaxLatin1 {
return fmt.Errorf("Character '%d' is not valid Latin-1", char)
}
if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil {
return err
}
}
dataLength := 8 + len(text)
if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil {
return err
}
return nil
}
// Requests a framebuffer update from the server. There may be an indefinite
// time between the request and the actual framebuffer update being
// received.
//
// See RFC 6143 Section 7.5.3
func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, height uint16) error {
var buf bytes.Buffer
var incrementalByte uint8 = 0
if incremental {
incrementalByte = 1
}
data := []interface{}{
uint8(3),
incrementalByte,
x, y, width, height,
}
for _, val := range data {
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
return err
}
}
if _, err := c.c.Write(buf.Bytes()[0:10]); err != nil {
return err
}
return nil
}
// KeyEvent indiciates a key press or release and sends it to the server.
// The key is indicated using the X Window System "keysym" value. Use
// Google to find a reference of these values. To simulate a key press,
// you must send a key with both a down event, and a non-down event.
//
// See 7.5.4.
func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
var downFlag uint8 = 0
if down {
downFlag = 1
}
data := []interface{}{
uint8(4),
downFlag,
uint8(0),
uint8(0),
keysym,
}
for _, val := range data {
if err := binary.Write(c.c, binary.BigEndian, val); err != nil {
return err
}
}
return nil
}
// PointerEvent indicates that pointer movement or a pointer button
// press or release.
//
// The mask is a bitwise mask of various ButtonMask values. When a button
// is set, it is pressed, when it is unset, it is released.
//
// See RFC 6143 Section 7.5.5
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
var buf bytes.Buffer
data := []interface{}{
uint8(5),
uint8(mask),
x,
y,
}
for _, val := range data {
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
return err
}
}
if _, err := c.c.Write(buf.Bytes()[0:6]); err != nil {
return err
}
return nil
}
// SetEncodings sets the encoding types in which the pixel data can
// be sent from the server. After calling this method, the encs slice
// given should not be modified.
//
// See RFC 6143 Section 7.5.2
func (c *ClientConn) SetEncodings(encs []Encoding) error {
data := make([]interface{}, 3+len(encs))
data[0] = uint8(2)
data[1] = uint8(0)
data[2] = uint16(len(encs))
for i, enc := range encs {
data[3+i] = int32(enc.Type())
}
var buf bytes.Buffer
for _, val := range data {
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
return err
}
}
dataLength := 4 + (4 * len(encs))
if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil {
return err
}
c.Encs = encs
return nil
}
// SetPixelFormat sets the format in which pixel values should be sent
// in FramebufferUpdate messages from the server.
//
// See RFC 6143 Section 7.5.1
func (c *ClientConn) SetPixelFormat(format *PixelFormat) error {
var keyEvent [20]byte
keyEvent[0] = 0
pfBytes, err := writePixelFormat(format)
if err != nil {
return err
}
// Copy the pixel format bytes into the proper slice location
copy(keyEvent[4:], pfBytes)
// Send the data down the connection
if _, err := c.c.Write(keyEvent[:]); err != nil {
return err
}
// Reset the color map as according to RFC.
var newColorMap [256]Color
c.ColorMap = newColorMap
return nil
}
const pvLen = 12 // ProtocolVersion message length.
func parseProtocolVersion(pv []byte) (uint, uint, error) {
var major, minor uint
if len(pv) < pvLen {
return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen)
}
l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor)
if l != 2 {
return 0, 0, fmt.Errorf("error parsing ProtocolVersion.")
}
if err != nil {
return 0, 0, err
}
return major, minor, nil
}
func (c *ClientConn) handshake() error {
var protocolVersion [pvLen]byte
// 7.1.1, read the ProtocolVersion message sent by the server.
if _, err := io.ReadFull(c.c, protocolVersion[:]); err != nil {
return err
}
maxMajor, maxMinor, err := parseProtocolVersion(protocolVersion[:])
if err != nil {
return err
}
if maxMajor < 3 {
return fmt.Errorf("unsupported major version, less than 3: %d", maxMajor)
}
if maxMinor < 3 {
return fmt.Errorf("unsupported minor version, less than 3: %d", maxMinor)
}
// Respond with the version we will support
if maxMinor<8 {
if _, err = c.c.Write([]byte("RFB 003.003\n")); err != nil {
return err
}
var numSecurityTypes uint32
if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil {
return err
}
if numSecurityTypes == 0 {
return fmt.Errorf("no security types: %s", c.readErrorReason())
}
}else{
if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil {
return err
}
// 7.1.2 Security Handshake from server
var numSecurityTypes uint8
if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil {
return err
}
if numSecurityTypes == 0 {
return fmt.Errorf("no security types: %s", c.readErrorReason())
}
securityTypes := make([]uint8, numSecurityTypes)
if err = binary.Read(c.c, binary.BigEndian, &securityTypes); err != nil {
return err
}
clientSecurityTypes := c.config.Auth
if clientSecurityTypes == nil {
clientSecurityTypes = []ClientAuth{new(ClientAuthNone)}
}
var auth ClientAuth
FindAuth:
for _, curAuth := range clientSecurityTypes {
for _, securityType := range securityTypes {
if curAuth.SecurityType() == securityType {
// We use the first matching supported authentication
auth = curAuth
break FindAuth
}
}
}
if auth == nil {
return fmt.Errorf("no suitable auth schemes found. server supported: %#v", securityTypes)
}
// Respond back with the security type we'll use
if err = binary.Write(c.c, binary.BigEndian, auth.SecurityType()); err != nil {
return err
}
if err = auth.Handshake(c.c); err != nil {
return err
}
// 7.1.3 SecurityResult Handshake
var securityResult uint32
if err = binary.Read(c.c, binary.BigEndian, &securityResult); err != nil {
return err
}
if securityResult == 1 {
return fmt.Errorf("security handshake failed: %s", c.readErrorReason())
}
}
// 7.3.1 ClientInit
var sharedFlag uint8 = 1
if c.config.Exclusive {
sharedFlag = 0
}
if err = binary.Write(c.c, binary.BigEndian, sharedFlag); err != nil {
return err
}
// 7.3.2 ServerInit
if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferWidth); err != nil {
return err
}
if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferHeight); err != nil {
return err
}
// Read the pixel format
if err = readPixelFormat(c.c, &c.PixelFormat); err != nil {
return err
}
var nameLength uint32
if err = binary.Read(c.c, binary.BigEndian, &nameLength); err != nil {
return err
}
nameBytes := make([]uint8, nameLength)
if err = binary.Read(c.c, binary.BigEndian, &nameBytes); err != nil {
return err
}
c.DesktopName = string(nameBytes)
return nil
}
// mainLoop reads messages sent from the server and routes them to the
// proper channels for users of the client to read.
func (c *ClientConn) mainLoop() {
defer c.Close()
// Build the map of available server messages
typeMap := make(map[uint8]ServerMessage)
defaultMessages := []ServerMessage{
new(FramebufferUpdateMessage),
new(SetColorMapEntriesMessage),
new(BellMessage),
new(ServerCutTextMessage),
}
for _, msg := range defaultMessages {
typeMap[msg.Type()] = msg
}
if c.config.ServerMessages != nil {
for _, msg := range c.config.ServerMessages {
typeMap[msg.Type()] = msg
}
}
for {
var messageType uint8
if err := binary.Read(c.c, binary.BigEndian, &messageType); err != nil {
break
}
msg, ok := typeMap[messageType]
if !ok {
// Unsupported message type! Bad!
break
}
parsedMsg, err := msg.Read(c, c.c)
if err != nil {
break
}
if c.config.ServerMessageCh == nil {
continue
}
c.config.ServerMessageCh <- parsedMsg
}
}
func (c *ClientConn) readErrorReason() string {
var reasonLen uint32
if err := binary.Read(c.c, binary.BigEndian, &reasonLen); err != nil {
return "<error>"
}
reason := make([]uint8, reasonLen)
if err := binary.Read(c.c, binary.BigEndian, &reason); err != nil {
return "<error>"
}
return string(reason)
}

View File

@@ -0,0 +1,124 @@
package vnc
import (
"net"
"crypto/des"
"encoding/binary"
)
// A ClientAuth implements a method of authenticating with a remote server.
type ClientAuth interface {
// SecurityType returns the byte identifier sent by the server to
// identify this authentication scheme.
SecurityType() uint8
// Handshake is called when the authentication handshake should be
// performed, as part of the general RFB handshake. (see 7.2.1)
Handshake(net.Conn) error
}
// ClientAuthNone is the "none" authentication. See 7.2.1
type ClientAuthNone byte
func (*ClientAuthNone) SecurityType() uint8 {
return 1
}
func (*ClientAuthNone) Handshake(net.Conn) error {
return nil
}
// PasswordAuth is VNC authentication, 7.2.2
type PasswordAuth struct {
Password string
}
func (p *PasswordAuth) SecurityType() uint8 {
return 2
}
func (p *PasswordAuth) Handshake(c net.Conn) error {
randomValue := make([]uint8, 16)
if err := binary.Read(c, binary.BigEndian, &randomValue); err != nil {
return err
}
crypted, err := p.encrypt(p.Password, randomValue)
if (err != nil) {
return err
}
if err := binary.Write(c, binary.BigEndian, &crypted); err != nil {
return err
}
return nil
}
func (p *PasswordAuth) reverseBits(b byte) byte {
var reverse = [256]int{
0, 128, 64, 192, 32, 160, 96, 224,
16, 144, 80, 208, 48, 176, 112, 240,
8, 136, 72, 200, 40, 168, 104, 232,
24, 152, 88, 216, 56, 184, 120, 248,
4, 132, 68, 196, 36, 164, 100, 228,
20, 148, 84, 212, 52, 180, 116, 244,
12, 140, 76, 204, 44, 172, 108, 236,
28, 156, 92, 220, 60, 188, 124, 252,
2, 130, 66, 194, 34, 162, 98, 226,
18, 146, 82, 210, 50, 178, 114, 242,
10, 138, 74, 202, 42, 170, 106, 234,
26, 154, 90, 218, 58, 186, 122, 250,
6, 134, 70, 198, 38, 166, 102, 230,
22, 150, 86, 214, 54, 182, 118, 246,
14, 142, 78, 206, 46, 174, 110, 238,
30, 158, 94, 222, 62, 190, 126, 254,
1, 129, 65, 193, 33, 161, 97, 225,
17, 145, 81, 209, 49, 177, 113, 241,
9, 137, 73, 201, 41, 169, 105, 233,
25, 153, 89, 217, 57, 185, 121, 249,
5, 133, 69, 197, 37, 165, 101, 229,
21, 149, 85, 213, 53, 181, 117, 245,
13, 141, 77, 205, 45, 173, 109, 237,
29, 157, 93, 221, 61, 189, 125, 253,
3, 131, 67, 195, 35, 163, 99, 227,
19, 147, 83, 211, 51, 179, 115, 243,
11, 139, 75, 203, 43, 171, 107, 235,
27, 155, 91, 219, 59, 187, 123, 251,
7, 135, 71, 199, 39, 167, 103, 231,
23, 151, 87, 215, 55, 183, 119, 247,
15, 143, 79, 207, 47, 175, 111, 239,
31, 159, 95, 223, 63, 191, 127, 255,
}
return byte(reverse[int(b)])
}
func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) {
keyBytes := []byte{0,0,0,0,0,0,0,0}
if len(key) > 8 {
key = key[:8]
}
for i := 0; i < len(key); i++ {
keyBytes[i] = p.reverseBits(key[i])
}
block, err := des.NewCipher(keyBytes)
if err != nil {
return nil, err
}
result1 := make([]byte, 8)
block.Encrypt(result1, bytes)
result2 := make([]byte, 8)
block.Encrypt(result2, bytes[8:])
crypted := append(result1, result2...)
return crypted, nil
}

View File

@@ -0,0 +1,6 @@
package vnc
// Color represents a single color in a color map.
type Color struct {
R, G, B uint16
}

View File

@@ -0,0 +1,217 @@
package vnc
import (
"bytes"
"compress/zlib"
"encoding/binary"
"io"
)
// An Encoding implements a method for encoding pixel data that is
// sent by the server to the client.
type Encoding interface {
// The number that uniquely identifies this encoding type.
Type() int32
// Read reads the contents of the encoded pixel data from the reader.
// This should return a new Encoding implementation that contains
// the proper data.
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.
//
// See RFC 6143 Section 7.7.1
type RawEncoding struct {
Colors []Color
RawPixel []uint32 //RGBA
}
func (*RawEncoding) Type() int32 {
return 0
}
func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) {
//fmt.Println("RawEncoding")
bytesPerPixel := c.PixelFormat.BPP / 8
pixelBytes := make([]uint8, bytesPerPixel)
var byteOrder binary.ByteOrder = binary.LittleEndian
if c.PixelFormat.BigEndian {
byteOrder = binary.BigEndian
}
colors := make([]Color, int(rect.Height)*int(rect.Width))
rawPixels := make([]uint32, int(rect.Height)*int(rect.Width))
for y := uint16(0); y < rect.Height; y++ {
for x := uint16(0); x < rect.Width; x++ {
if _, err := io.ReadFull(r, pixelBytes); err != nil {
return nil, err
}
var rawPixel uint32
if c.PixelFormat.BPP == 8 {
rawPixel = uint32(pixelBytes[0])
} else if c.PixelFormat.BPP == 16 {
rawPixel = uint32(byteOrder.Uint16(pixelBytes))
} else if c.PixelFormat.BPP == 32 {
rawPixel = byteOrder.Uint32(pixelBytes)
}
//rawPixels[int(y)*int(rect.Width)+int(x)]=rawPixel
color := &colors[int(y)*int(rect.Width)+int(x)]
if c.PixelFormat.TrueColor {
color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax))
color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax))
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 {
*color = c.ColorMap[rawPixel]
}
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)])
}
}
return &RawEncoding{colors, rawPixels}, nil
}
// 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.
type ZlibEncoding struct {
Colors []Color
RawPixel []uint32
ZStream *bytes.Buffer
ZReader io.ReadCloser
}
func (*ZlibEncoding) Type() int32 {
return 6
}
func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) {
//fmt.Println("ZlibEncoding")
bytesPerPixel := c.PixelFormat.BPP / 8
pixelBytes := make([]uint8, bytesPerPixel)
var byteOrder binary.ByteOrder = binary.LittleEndian
if c.PixelFormat.BigEndian {
byteOrder = binary.BigEndian
}
// Format
// 4 bytes | uint32 | length
// 'length' bytes | []byte | zlibData
// Read zlib length
var zipLength uint32
err := binary.Read(r, binary.BigEndian, &zipLength)
if err != nil {
return nil, err
}
// Read all compressed data
zBytes := make([]byte, zipLength)
if _, err := io.ReadFull(r, zBytes); err != nil {
return nil, err
}
// Create new zlib stream if needed
if ze.ZStream == nil {
// Create and save the buffer
ze.ZStream = new(bytes.Buffer)
ze.ZStream.Write(zBytes)
// Create a reader for the buffer
ze.ZReader, err = zlib.NewReader(ze.ZStream)
if err != nil {
return nil, err
}
// This is needed to avoid 'zlib missing header'
} else {
// Just append if already created
ze.ZStream.Write(zBytes)
}
// Calculate zlib decompressed size
sizeToRead := int(rect.Height) * int(rect.Width) * int(bytesPerPixel)
// Create buffer for bytes
colorBytes := make([]byte, sizeToRead)
// Read all data from zlib stream
read, err := io.ReadFull(ze.ZReader, colorBytes)
if read != sizeToRead || err != nil {
return nil, err
}
// Create buffer for raw encoding
colorReader := bytes.NewReader(colorBytes)
colors := make([]Color, int(rect.Height)*int(rect.Width))
rawPixels := make([]uint32, int(rect.Height)*int(rect.Width))
for y := uint16(0); y < rect.Height; y++ {
for x := uint16(0); x < rect.Width; x++ {
if _, err := io.ReadFull(colorReader, pixelBytes); err != nil {
return nil, err
}
var rawPixel uint32
if c.PixelFormat.BPP == 8 {
rawPixel = uint32(pixelBytes[0])
} else if c.PixelFormat.BPP == 16 {
rawPixel = uint32(byteOrder.Uint16(pixelBytes))
} else if c.PixelFormat.BPP == 32 {
rawPixel = byteOrder.Uint32(pixelBytes)
}
color := &colors[int(y)*int(rect.Width)+int(x)]
if c.PixelFormat.TrueColor {
color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax))
color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax))
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 {
*color = c.ColorMap[rawPixel]
}
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
}
func (ze *ZlibEncoding) Close() {
if ze.ZStream != nil {
ze.ZStream = nil
ze.ZReader.Close()
ze.ZReader = nil
}
}

View File

@@ -0,0 +1,151 @@
package vnc
import (
"bytes"
"encoding/binary"
"io"
)
// PixelFormat describes the way a pixel is formatted for a VNC connection.
//
// See RFC 6143 Section 7.4 for information on each of the fields.
type PixelFormat struct {
BPP uint8
Depth uint8
BigEndian bool
TrueColor bool
RedMax uint16
GreenMax uint16
BlueMax uint16
RedShift uint8
GreenShift uint8
BlueShift uint8
}
func readPixelFormat(r io.Reader, result *PixelFormat) error {
var rawPixelFormat [16]byte
if _, err := io.ReadFull(r, rawPixelFormat[:]); err != nil {
return err
}
var pfBoolByte uint8
brPF := bytes.NewReader(rawPixelFormat[:])
if err := binary.Read(brPF, binary.BigEndian, &result.BPP); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.Depth); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil {
return err
}
if pfBoolByte != 0 {
// Big endian is true
result.BigEndian = true
}
if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil {
return err
}
if pfBoolByte != 0 {
// True Color is true. So we also have to read all the color max & shifts.
result.TrueColor = true
if err := binary.Read(brPF, binary.BigEndian, &result.RedMax); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.GreenMax); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.BlueMax); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.RedShift); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.GreenShift); err != nil {
return err
}
if err := binary.Read(brPF, binary.BigEndian, &result.BlueShift); err != nil {
return err
}
}
return nil
}
func writePixelFormat(format *PixelFormat) ([]byte, error) {
var buf bytes.Buffer
// Byte 1
if err := binary.Write(&buf, binary.BigEndian, format.BPP); err != nil {
return nil, err
}
// Byte 2
if err := binary.Write(&buf, binary.BigEndian, format.Depth); err != nil {
return nil, err
}
var boolByte byte
if format.BigEndian {
boolByte = 1
} else {
boolByte = 0
}
// Byte 3 (BigEndian)
if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil {
return nil, err
}
if format.TrueColor {
boolByte = 1
} else {
boolByte = 0
}
// Byte 4 (TrueColor)
if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil {
return nil, err
}
// If we have true color enabled then we have to fill in the rest of the
// structure with the color values.
if format.TrueColor {
if err := binary.Write(&buf, binary.BigEndian, format.RedMax); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.BigEndian, format.GreenMax); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.BigEndian, format.BlueMax); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.BigEndian, format.RedShift); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.BigEndian, format.GreenShift); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.BigEndian, format.BlueShift); err != nil {
return nil, err
}
}
return buf.Bytes()[0:16], nil
}

View File

@@ -0,0 +1,16 @@
package vnc
// ButtonMask represents a mask of pointer presses/releases.
type ButtonMask uint8
// All available button mask components.
const (
ButtonLeft ButtonMask = 1 << iota
ButtonMiddle
ButtonRight
Button4
Button5
Button6
Button7
Button8
)

View File

@@ -0,0 +1,192 @@
package vnc
import (
"encoding/binary"
"fmt"
"io"
)
// A ServerMessage implements a message sent from the server to the client.
type ServerMessage interface {
// The type of the message that is sent down on the wire.
Type() uint8
// Read reads the contents of the message from the reader. At the point
// this is called, the message type has already been read from the reader.
// This should return a new ServerMessage that is the appropriate type.
Read(*ClientConn, io.Reader) (ServerMessage, error)
}
// FramebufferUpdateMessage consists of a sequence of rectangles of
// pixel data that the client should put into its framebuffer.
type FramebufferUpdateMessage struct {
Rectangles []Rectangle
}
// Rectangle represents a rectangle of pixel data.
type Rectangle struct {
X uint16
Y uint16
Width uint16
Height uint16
Enc Encoding
}
func (*FramebufferUpdateMessage) Type() uint8 {
return 0
}
func (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) {
// Read off the padding
var padding [1]byte
if _, err := io.ReadFull(r, padding[:]); err != nil {
return nil, err
}
var numRects uint16
if err := binary.Read(r, binary.BigEndian, &numRects); err != nil {
return nil, err
}
// Build the map of encodings supported
encMap := make(map[int32]Encoding)
for _, enc := range c.Encs {
encMap[enc.Type()] = enc
}
// We must always support the raw encoding
rawEnc := new(RawEncoding)
encMap[rawEnc.Type()] = rawEnc
rects := make([]Rectangle, numRects)
for i := uint16(0); i < numRects; i++ {
var encodingType int32
rect := &rects[i]
data := []interface{}{
&rect.X,
&rect.Y,
&rect.Width,
&rect.Height,
&encodingType,
}
for _, val := range data {
if err := binary.Read(r, binary.BigEndian, val); err != nil {
return nil, err
}
}
enc, ok := encMap[encodingType]
if !ok {
return nil, fmt.Errorf("unsupported encoding type: %d", encodingType)
}
var err error
rect.Enc, err = enc.Read(c, rect, r)
if err != nil {
return nil, err
}
}
return &FramebufferUpdateMessage{rects}, nil
}
// SetColorMapEntriesMessage is sent by the server to set values into
// the color map. This message will automatically update the color map
// for the associated connection, but contains the color change data
// if the consumer wants to read it.
//
// See RFC 6143 Section 7.6.2
type SetColorMapEntriesMessage struct {
FirstColor uint16
Colors []Color
}
func (*SetColorMapEntriesMessage) Type() uint8 {
return 1
}
func (*SetColorMapEntriesMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) {
// Read off the padding
var padding [1]byte
if _, err := io.ReadFull(r, padding[:]); err != nil {
return nil, err
}
var result SetColorMapEntriesMessage
if err := binary.Read(r, binary.BigEndian, &result.FirstColor); err != nil {
return nil, err
}
var numColors uint16
if err := binary.Read(r, binary.BigEndian, &numColors); err != nil {
return nil, err
}
result.Colors = make([]Color, numColors)
for i := uint16(0); i < numColors; i++ {
color := &result.Colors[i]
data := []interface{}{
&color.R,
&color.G,
&color.B,
}
for _, val := range data {
if err := binary.Read(r, binary.BigEndian, val); err != nil {
return nil, err
}
}
// Update the connection's color map
c.ColorMap[result.FirstColor+i] = *color
}
return &result, nil
}
// Bell signals that an audible bell should be made on the client.
//
// See RFC 6143 Section 7.6.3
type BellMessage byte
func (*BellMessage) Type() uint8 {
return 2
}
func (*BellMessage) Read(*ClientConn, io.Reader) (ServerMessage, error) {
return new(BellMessage), nil
}
// ServerCutTextMessage indicates the server has new text in the cut buffer.
//
// See RFC 6143 Section 7.6.4
type ServerCutTextMessage struct {
Text string
}
func (*ServerCutTextMessage) Type() uint8 {
return 3
}
func (*ServerCutTextMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) {
// Read off the padding
var padding [3]byte
if _, err := io.ReadFull(r, padding[:]); err != nil {
return nil, err
}
var textLength uint32
if err := binary.Read(r, binary.BigEndian, &textLength); err != nil {
return nil, err
}
textBytes := make([]uint8, textLength)
if err := binary.Read(r, binary.BigEndian, &textBytes); err != nil {
return nil, err
}
return &ServerCutTextMessage{string(textBytes)}, nil
}

View File

@@ -0,0 +1,180 @@
// Package videotest provides vncDevice video driver for testing.
package vncdriver
import (
"context"
"encoding/binary"
"fmt"
"image"
"io"
"net"
"sync"
"time"
"github.com/pion/mediadevices/pkg/driver/vncdriver/vnc"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type vncDevice struct {
closed <-chan struct{}
cancel func()
tick *time.Ticker
h, w int
rawPixel []byte
mutex sync.Mutex
vClient *vnc.ClientConn
vncAddr string
}
func NewVnc(vncAddr string) *vncDevice {
return &vncDevice{vncAddr: vncAddr}
}
func (d *vncDevice) PointerEvent(mask uint8, x, y uint16) {
if d.vClient != nil {
d.vClient.PointerEvent(vnc.ButtonMask(mask), x, y)
}
}
func (d *vncDevice) KeyEvent(keysym uint32, down bool) {
if d.vClient != nil {
d.vClient.KeyEvent(keysym, down)
}
}
func (d *vncDevice) Open() error {
if d.vClient != nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
d.closed = ctx.Done()
d.cancel = cancel
msg := make(chan vnc.ServerMessage, 1)
//auth:=new(vnc.PasswordAuth)
//auth.Password="####"
conf := vnc.ClientConfig{
//Auth: []vnc.ClientAuth{auth},
ServerMessageCh: msg,
Exclusive: false,
}
d.mutex.Lock()
defer d.mutex.Unlock()
conn, err := net.Dial("tcp", d.vncAddr)
if err != nil {
return err
}
d.vClient, err = vnc.Client(conn, &conf)
if err != nil {
return err
}
d.vClient.SetEncodings([]vnc.Encoding{
&vnc.ZlibEncoding{},
&vnc.RawEncoding{},
&vnc.CursorEncoding{},
})
d.w = int(d.vClient.FrameBufferWidth)
d.h = int(d.vClient.FrameBufferHeight)
d.rawPixel = make([]byte, d.h*d.w*4)
go func(ctx context.Context) {
c, cancel := context.WithCancel(ctx)
defer cancel()
if d.vClient == nil {
return
}
d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h))
for {
select {
case <-c.Done():
return
case msg := <-msg:
switch t := msg.(type) {
case *vnc.FramebufferUpdateMessage:
for _, rect := range t.Rectangles {
var pix []uint32
switch t := rect.Enc.(type) {
case *vnc.CursorEncoding:
//ignore remote cursor messages
continue
case *vnc.RawEncoding:
pix = t.RawPixel
case *vnc.ZlibEncoding:
pix = t.RawPixel
}
for y := int(rect.Y); y < int(rect.Height+rect.Y); y++ {
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))])
}
}
}
d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h))
break
default:
}
case <-time.After(10 * time.Second):
if d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) != nil {
d.cancel()
return
}
}
}
}(ctx)
return nil
}
func (d *vncDevice) Close() error {
d.cancel()
if d.tick != nil {
d.tick.Stop()
}
d.mutex.Lock()
defer d.mutex.Unlock()
if d.vClient != nil {
d.vClient.Close()
d.vClient = nil
}
return nil
}
func (d *vncDevice) VideoRecord(p prop.Media) (video.Reader, error) {
if p.FrameRate == 0 {
p.FrameRate = 30
}
tick := time.NewTicker(time.Duration(float32(time.Second) / p.FrameRate))
d.tick = tick
closed := d.closed
r := video.ReaderFunc(func() (image.Image, func(), error) {
select {
case <-closed:
fmt.Println("Stop Record Video By VideoRecord")
return nil, func() {}, io.EOF
default:
}
<-tick.C
return &image.RGBA{
Pix: d.rawPixel,
Stride: 4,
Rect: image.Rect(0, 0, d.w, d.h),
}, func() {}, nil
})
return r, nil
}
func (d *vncDevice) Properties() []prop.Media {
return []prop.Media{
{
Video: prop.Video{
Width: d.w,
Height: d.h,
FrameFormat: frame.FormatRGBA,
},
},
}
}

View File

@@ -1,14 +1,20 @@
package driver
import (
"github.com/google/uuid"
"github.com/pion/mediadevices/pkg/driver/availability"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
uuid "github.com/satori/go.uuid"
)
func wrapAdapter(a Adapter, info Info) Driver {
id := uuid.NewV4().String()
generator, err := uuid.NewRandom()
if err != nil {
panic(err)
}
id := generator.String()
d := &adapterWrapper{
Adapter: a,
id: id,
@@ -16,6 +22,10 @@ func wrapAdapter(a Adapter, info Info) Driver {
state: StateClosed,
}
if aa, ok := a.(AvailabilityAdapter); ok {
d.isAvailable = aa.IsAvailable
}
switch v := a.(type) {
case VideoRecorder:
// Only expose Driver and VideoRecorder interfaces
@@ -23,7 +33,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
r := &struct {
Driver
VideoRecorder
}{d, d}
AvailabilityAdapter
}{d, d, d}
return r
case AudioRecorder:
// Only expose Driver and AudioRecorder interfaces
@@ -31,7 +42,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
return &struct {
Driver
AudioRecorder
}{d, d}
AvailabilityAdapter
}{d, d, d}
default:
panic("adapter has to be either VideoRecorder/AudioRecorder")
}
@@ -41,9 +53,10 @@ type adapterWrapper struct {
Adapter
VideoRecorder
AudioRecorder
id string
info Info
state State
id string
info Info
state State
isAvailable func() (bool, error)
}
func (w *adapterWrapper) ID() string {
@@ -99,3 +112,10 @@ func (w *adapterWrapper) AudioRecord(p prop.Media) (r audio.Reader, err error) {
}
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
}
type availabilityAdapterMock struct{ videoAdapterMock }
func (a *availabilityAdapterMock) IsAvailable() (bool, error) { return true, nil }
func TestVideoWrapperState(t *testing.T) {
var a videoAdapterMock
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())
}
}
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 (
"bytes"
"errors"
"image"
"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) {
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
}
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

@@ -1,24 +0,0 @@
package frame
type Format string
const (
// FormatI420 https://www.fourcc.org/pixel-format/yuv-i420/
FormatI420 Format = "I420"
// FormatI444 is a YUV format without sub-sampling
FormatI444 Format = "I444"
// FormatNV21 https://www.fourcc.org/pixel-format/yuv-nv21/
FormatNV21 = "NV21"
// FormatYUY2 https://www.fourcc.org/pixel-format/yuv-yuy2/
FormatYUY2 = "YUY2"
// FormatUYVY https://www.fourcc.org/pixel-format/yuv-uyvy/
FormatUYVY = "UYVY"
// FormatRGBA https://www.fourcc.org/pixel-format/rgb-rgba/
FormatRGBA Format = "RGBA"
// FormatMJPEG https://www.fourcc.org/mjpg/
FormatMJPEG = "MJPEG"
)
const FormatYUYV = FormatYUY2

View File

@@ -4,21 +4,50 @@ import (
"fmt"
)
func NewDecoder(f Format) (Decoder, error) {
var decoder decoderFunc
type Format string
switch f {
case FormatI420:
decoder = decodeI420
case FormatNV21:
decoder = decodeNV21
case FormatYUY2:
decoder = decodeYUY2
case FormatUYVY:
decoder = decodeUYVY
case FormatMJPEG:
decoder = decodeMJPEG
default:
const (
// FormatI420 https://wiki.videolan.org/YUV#I420
FormatI420 Format = "I420"
// FormatI444 is a YUV format without sub-sampling
FormatI444 Format = "I444"
// FormatNV21 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
FormatNV21 = "NV21"
// FormatNV12 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
FormatNV12 = "NV12"
// FormatYUY2 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-yuyv.html
// YUY2 is what Windows calls YUYV
FormatYUY2 = "YUY2"
// 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"
// FormatRGBA https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-rgb.html
FormatRGBA Format = "RGBA"
// FormatMJPEG https://wiki.videolan.org/MJPEG
FormatMJPEG = "MJPEG"
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
FormatZ16 = "Z16"
)
var decoderMap = map[Format]decoderFunc{
FormatI420: decodeI420,
FormatNV21: decodeNV21,
FormatNV12: decodeNV12,
FormatYUY2: decodeYUY2,
FormatYUYV: decodeYUY2,
FormatUYVY: decodeUYVY,
FormatMJPEG: decodeMJPEG,
FormatZ16: decodeZ16,
}
func NewDecoder(f Format) (Decoder, error) {
decoder, ok := decoderMap[f]
if !ok {
return nil, fmt.Errorf("%s is not supported", f)
}

File diff suppressed because one or more lines are too long

View File

@@ -35,8 +35,8 @@ func decodeNV21(frame []byte, width, height int) (image.Image, func(), error) {
var cb, cr []byte
for i := yi; i < ci; i += 2 {
cb = append(cb, frame[i])
cr = append(cr, frame[i+1])
cr = append(cr, frame[i])
cb = append(cb, frame[i+1])
}
return &image.YCbCr{
@@ -49,3 +49,15 @@ func decodeNV21(frame []byte, width, height int) (image.Image, func(), error) {
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
}
func decodeNV12(frame []byte, width, height int) (image.Image, func(), error) {
img, release, err := decodeNV21(frame, width, height)
if err != nil {
return img, release, err
}
// The only difference between NV21 and NV12 is the chroma order, so simply swap them
yuv := img.(*image.YCbCr)
yuv.Cb, yuv.Cr = yuv.Cr, yuv.Cb
return yuv, release, err
}

View File

@@ -17,7 +17,7 @@ func decodeYUY2(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi {
if len(frame) < fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}
@@ -49,7 +49,7 @@ func decodeUYVY(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi {
if len(frame) < fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}

View File

@@ -12,7 +12,7 @@ func decodeYUY2(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi {
if len(frame) < fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}
@@ -47,7 +47,7 @@ func decodeUYVY(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi {
if len(frame) < fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}

View File

@@ -27,7 +27,11 @@ func TestDecodeYUY2(t *testing.T) {
Rect: image.Rect(0, 0, width, height),
}
img, _, err := decodeYUY2(input, width, height)
decoder, err := NewDecoder(FormatYUY2)
if err != nil {
t.Fatal(err)
}
img, _, err := decoder.Decode(input, width, height)
if err != nil {
t.Fatal(err)
}
@@ -56,7 +60,77 @@ func TestDecodeUYVY(t *testing.T) {
Rect: image.Rect(0, 0, width, height),
}
img, _, err := decodeUYVY(input, width, height)
decoder, err := NewDecoder(FormatUYVY)
if err != nil {
t.Fatal(err)
}
img, _, err := decoder.Decode(input, width, height)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, img) {
t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, img)
}
}
func TestDecodeNV21(t *testing.T) {
const (
width = 2
height = 2
)
input := []byte{
0x01, 0x03, 0x05, 0x07, // Y
// Cr Cb
0x82, 0x84,
}
expected := &image.YCbCr{
Y: []byte{0x01, 0x03, 0x05, 0x07},
YStride: width,
Cb: []byte{0x84},
Cr: []byte{0x82},
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}
decoder, err := NewDecoder(FormatNV21)
if err != nil {
t.Fatal(err)
}
img, _, err := decoder.Decode(input, width, height)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, img) {
t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, img)
}
}
func TestDecodeNV12(t *testing.T) {
const (
width = 2
height = 2
)
input := []byte{
0x01, 0x03, 0x05, 0x07, // Y
// Cb Cr
0x84, 0x82,
}
expected := &image.YCbCr{
Y: []byte{0x01, 0x03, 0x05, 0x07},
YStride: width,
Cb: []byte{0x84},
Cr: []byte{0x82},
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}
decoder, err := NewDecoder(FormatNV12)
if err != nil {
t.Fatal(err)
}
img, _, err := decoder.Decode(input, width, height)
if err != nil {
t.Fatal(err)
}

36
pkg/frame/z16.go Normal file
View File

@@ -0,0 +1,36 @@
package frame
import (
"encoding/binary"
"fmt"
"image"
"image/color"
)
func decodeZ16(frame []byte, width, height int) (image.Image, func(), error) {
expectedSize := 2 * (width * height)
if expectedSize != len(frame) {
return nil, func() {}, fmt.Errorf("frame length (%d) not expected size (%d)", len(frame), expectedSize)
}
img := image.NewGray16(image.Rect(0, 0, width, height))
/*
v4l specifies images in terms of series of lines which is perplexing because the
example in https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
seems to swap the subscript of each depth value.
Clear example:
Width: 3, Height: 4
[Z_low(x_0,y_0), Z_high(x_0,y_0), Z_low(x_1,y_0), Z_high(x_1,y_0), Z_low(x_2,y_0), Z_high(x_2,y_0),
Z_low(x_0,y_1), Z_high(x_0,y_1), Z_low(x_1,y_1), Z_high(x_1,y_1), Z_low(x_2,y_1), Z_high(x_2,y_1),
Z_low(x_0,y_2), Z_high(x_0,y_2), Z_low(x_1,y_2), Z_high(x_1,y_2), Z_low(x_2,y_2), Z_high(x_2,y_2),
Z_low(x_0,y_3), Z_high(x_0,y_3), Z_low(x_1,y_3), Z_high(x_1,y_3), Z_low(x_2,y_3), Z_high(x_2,y_3)]
*/
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
idx := 2 * (x + (y * width))
z := binary.LittleEndian.Uint16(frame[idx : idx+2])
img.SetGray16(x, y, color.Gray16{Y: z})
}
}
return img, func() {}, nil
}

45
pkg/frame/z16_test.go Normal file
View File

@@ -0,0 +1,45 @@
package frame
import (
"image"
"image/color"
"reflect"
"testing"
)
func TestDecodeZ16(t *testing.T) {
const (
width = 2
height = 3
)
decoder, err := NewDecoder(FormatZ16)
if err != nil {
t.Fatal(err)
}
img, _, err := decoder.Decode([]byte{0x00}, width, height)
if err == nil {
t.Errorf("expected to get a frame length mismatch")
}
input := []byte{
0x0c, 0x00, 0x20, 0x03,
0xa3, 0x01, 0x10, 0x00,
0x56, 0x09, 0x5d, 0x00,
}
expected := image.NewGray16(image.Rect(0, 0, width, height))
expected.Stride = width * 2
expected.SetGray16(0, 0, color.Gray16{Y: 12})
expected.SetGray16(1, 0, color.Gray16{Y: 800})
expected.SetGray16(0, 1, color.Gray16{Y: 419})
expected.SetGray16(1, 1, color.Gray16{Y: 16})
expected.SetGray16(0, 2, color.Gray16{Y: 2390})
expected.SetGray16(1, 2, color.Gray16{Y: 93})
img, _, err = decoder.Decode(input, width, height)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, img) {
t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, img)
}
}

View File

@@ -141,7 +141,9 @@ func (broadcaster *Broadcaster) NewReader(copyFn func(interface{}) interface{})
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
})
}

Some files were not shown because too many files have changed in this diff Show More